--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountUtils.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountUtils.java
@@ -94,28 +94,21 @@ public class FxAccountUtils {
* @return x modulo N in hexadecimal.
*/
public static String hexModN(BigInteger x, BigInteger N) {
int byteLength = (N.bitLength() + 7) / 8;
int hexLength = 2 * byteLength;
return Utils.byte2Hex(Utils.hex2Byte((x.mod(N)).toString(16), byteLength), hexLength);
}
- /**
- * The first engineering milestone of PICL (Profile-in-the-Cloud) was
- * comprised of Sync 1.1 fronted by a Firefox Account. The sync key was
- * generated from the Firefox Account password-derived kB value using this
- * method.
- */
- public static KeyBundle generateSyncKeyBundle(final byte[] kB) throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException {
+ public static KeyBundle generateSyncKeyBundle(final byte[] kSync) throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException {
byte[] encryptionKey = new byte[32];
byte[] hmacKey = new byte[32];
- byte[] derived = HKDF.derive(kB, new byte[0], FxAccountUtils.KW("oldsync"), 2*32);
- System.arraycopy(derived, 0*32, encryptionKey, 0, 1*32);
- System.arraycopy(derived, 1*32, hmacKey, 0, 1*32);
+ System.arraycopy(kSync, 0*32, encryptionKey, 0, 1*32);
+ System.arraycopy(kSync, 1*32, hmacKey, 0, 1*32);
return new KeyBundle(encryptionKey, hmacKey);
}
/**
* Firefox Accounts are password authenticated, but clients should not store
* the plain-text password for any amount of time. Equivalent, but slightly
* more secure, is the quickly client-side stretched password.
* <p>
@@ -165,16 +158,26 @@ public class FxAccountUtils {
byte[] kB = new byte[CRYPTO_KEY_LENGTH_BYTES];
for (int i = 0; i < wrapkB.length; i++) {
kB[i] = (byte) (wrapkB[i] ^ unwrapkB[i]);
}
return kB;
}
/**
+ * The first engineering milestone of PICL (Profile-in-the-Cloud) was
+ * comprised of Sync 1.1 fronted by a Firefox Account. The sync key was
+ * generated from the Firefox Account password-derived kB value using this
+ * method.
+ */
+ public static byte[] deriveSyncKey(byte[] kB) throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException {
+ return HKDF.derive(kB, new byte[0], FxAccountUtils.KW("oldsync"), 2*32);
+ }
+
+ /**
* The token server accepts an X-Client-State header, which is the
* lowercase-hex-encoded first 16 bytes of the SHA-256 hash of the
* bytes of kB.
* @param kB a byte array, expected to be 32 bytes long.
* @return a 32-character string.
* @throws NoSuchAlgorithmException
*/
public static String computeClientState(byte[] kB) throws NoSuchAlgorithmException {
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/Cohabiting.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/Cohabiting.java
@@ -9,22 +9,22 @@ import org.mozilla.gecko.browserid.Brows
import org.mozilla.gecko.browserid.JSONWebTokenUtils;
import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.ExecuteDelegate;
import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.LogMessage;
import org.mozilla.gecko.sync.ExtendedJSONObject;
public class Cohabiting extends TokensAndKeysState {
private static final String LOG_TAG = Cohabiting.class.getSimpleName();
- public Cohabiting(String email, String uid, byte[] sessionToken, byte[] kA, byte[] kB, BrowserIDKeyPair keyPair) {
- super(StateLabel.Cohabiting, email, uid, sessionToken, kA, kB, keyPair);
+ /* package-private */ Cohabiting(String email, String uid, byte[] sessionToken, byte[] kSync, String kXCS, BrowserIDKeyPair keyPair) {
+ super(StateLabel.Cohabiting, email, uid, sessionToken, kSync, kXCS, keyPair);
}
public Married withCertificate(String certificate) {
- return new Married(email, uid, sessionToken, kA, kB, keyPair, certificate);
+ return new Married(email, uid, sessionToken, kSync, kXCS, keyPair, certificate);
}
@Override
public void execute(final ExecuteDelegate delegate) {
delegate.getClient().sign(sessionToken, keyPair.getPublic().toJSONObject(), delegate.getCertificateDurationInMilliseconds(),
new BaseRequestDelegate<String>(this, delegate) {
@Override
public void handleSuccess(String certificate) {
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/Engaged.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/Engaged.java
@@ -52,32 +52,39 @@ public class Engaged extends State {
delegate.handleTransition(new LocalError(e), new Doghouse(email, uid, verified));
return;
}
final BrowserIDKeyPair keyPair = theKeyPair;
delegate.getClient().keys(keyFetchToken, new BaseRequestDelegate<TwoKeys>(this, delegate) {
@Override
public void handleSuccess(TwoKeys result) {
- byte[] kB;
+ final byte[] kB;
+ final byte[] kSync;
+ final String kXCS;
try {
kB = FxAccountUtils.unwrapkB(unwrapkB, result.wrapkB);
+ // Only the derived keys move forward.
+ kSync = FxAccountUtils.deriveSyncKey(kB);
+ kXCS = FxAccountUtils.computeClientState(kB);
if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
FxAccountUtils.pii(LOG_TAG, "Fetched kA: " + Utils.byte2Hex(result.kA));
FxAccountUtils.pii(LOG_TAG, "And wrapkB: " + Utils.byte2Hex(result.wrapkB));
- FxAccountUtils.pii(LOG_TAG, "Giving kB : " + Utils.byte2Hex(kB));
+ FxAccountUtils.pii(LOG_TAG, "Unwrapped kB: " + Utils.byte2Hex(kB));
+ FxAccountUtils.pii(LOG_TAG, "Giving derived kSync: " + Utils.byte2Hex(kSync));
+ FxAccountUtils.pii(LOG_TAG, "Giving derived kXCS: " + kXCS);
}
} catch (Exception e) {
delegate.handleTransition(new RemoteError(e), new Separated(email, uid, verified));
return;
}
Transition transition = verified
? new LogMessage("keys succeeded")
: new AccountVerified();
- delegate.handleTransition(transition, new Cohabiting(email, uid, sessionToken, result.kA, kB, keyPair));
+ delegate.handleTransition(transition, new Cohabiting(email, uid, sessionToken, kSync, kXCS, keyPair));
}
});
}
@Override
public Action getNeededAction() {
if (!verified) {
return Action.NeedsVerification;
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/Married.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/Married.java
@@ -22,28 +22,21 @@ import org.mozilla.gecko.sync.ExtendedJS
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.sync.crypto.KeyBundle;
public class Married extends TokensAndKeysState {
private static final String LOG_TAG = Married.class.getSimpleName();
protected final String certificate;
- protected final String clientState;
- public Married(String email, String uid, byte[] sessionToken, byte[] kA, byte[] kB, BrowserIDKeyPair keyPair, String certificate) {
- super(StateLabel.Married, email, uid, sessionToken, kA, kB, keyPair);
+ public Married(String email, String uid, byte[] sessionToken, byte[] kSync, String kXCS, BrowserIDKeyPair keyPair, String certificate) {
+ super(StateLabel.Married, email, uid, sessionToken, kSync, kXCS, keyPair);
Utils.throwIfNull(certificate);
this.certificate = certificate;
- try {
- this.clientState = FxAccountUtils.computeClientState(kB);
- } catch (NoSuchAlgorithmException e) {
- // This should never occur.
- throw new IllegalStateException("Unable to compute client state from kB.");
- }
}
@Override
public ExtendedJSONObject toJSONObject() {
ExtendedJSONObject o = super.toJSONObject();
// Fields are non-null by constructor.
o.put("certificate", certificate);
return o;
@@ -95,23 +88,22 @@ public class Married extends TokensAndKe
}
} catch (Exception e) {
FxAccountUtils.pii(LOG_TAG, "Got exception dumping assertion debug info.");
}
return assertion;
}
public KeyBundle getSyncKeyBundle() throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException {
- // TODO Document this choice for deriving from kB.
- return FxAccountUtils.generateSyncKeyBundle(kB);
+ return FxAccountUtils.generateSyncKeyBundle(kSync);
}
public String getClientState() {
if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
- FxAccountUtils.pii(LOG_TAG, "Client state: " + this.clientState);
+ FxAccountUtils.pii(LOG_TAG, "Client state: " + this.kXCS);
}
- return this.clientState;
+ return this.kXCS;
}
public Cohabiting makeCohabitingState() {
- return new Cohabiting(email, uid, sessionToken, kA, kB, keyPair);
+ return new Cohabiting(email, uid, sessionToken, kSync, kXCS, keyPair);
}
}
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/State.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/State.java
@@ -4,23 +4,23 @@
package org.mozilla.gecko.fxa.login;
import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.ExecuteDelegate;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.Utils;
public abstract class State {
- public static final long CURRENT_VERSION = 3L;
+ private static final long CURRENT_VERSION = 4L; // Bug 1426305.
public static final class NotASessionTokenState extends Exception {
private static final long serialVersionUID = 8628129091996684799L;
- public NotASessionTokenState(String message) {
+ /* package-private */ NotASessionTokenState(String message) {
super(message);
}
}
public enum StateLabel {
Engaged,
Cohabiting,
Married,
@@ -32,17 +32,17 @@ public abstract class State {
public enum Action {
NeedsUpgrade,
NeedsPassword,
NeedsVerification,
NeedsFinishMigrating,
None,
}
- protected final StateLabel stateLabel;
+ /* package-private */ final StateLabel stateLabel;
public final String email;
public final String uid;
public final boolean verified;
public State(StateLabel stateLabel, String email, String uid, boolean verified) {
Utils.throwIfNull(email, uid);
this.stateLabel = stateLabel;
this.email = email;
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/StateFactory.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/StateFactory.java
@@ -1,14 +1,16 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.fxa.login;
+import java.io.UnsupportedEncodingException;
+import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.fxa.FxAccountUtils;
import org.mozilla.gecko.browserid.BrowserIDKeyPair;
import org.mozilla.gecko.browserid.DSACryptoImplementation;
import org.mozilla.gecko.browserid.RSACryptoImplementation;
@@ -42,62 +44,68 @@ public class StateFactory {
return RSACryptoImplementation.fromJSONObject(o);
}
protected static BrowserIDKeyPair keyPairFromJSONObjectV2(ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException {
// V2 key pairs are DSA.
return DSACryptoImplementation.fromJSONObject(o);
}
- public static State fromJSONObject(StateLabel stateLabel, ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException, NonObjectJSONException {
+ public static State fromJSONObject(StateLabel stateLabel, ExtendedJSONObject o) throws InvalidKeySpecException, InvalidKeyException, UnsupportedEncodingException, NoSuchAlgorithmException, NonObjectJSONException {
Long version = o.getLong("version");
if (version == null) {
throw new IllegalStateException("version must not be null");
}
final int v = version.intValue();
+ // The most common case is the most recent version.
+ if (v == 4) {
+ return fromJSONObjectV4(stateLabel, o);
+ }
if (v == 3) {
- // The most common case is the most recent version.
return fromJSONObjectV3(stateLabel, o);
}
if (v == 2) {
return fromJSONObjectV2(stateLabel, o);
}
if (v == 1) {
final State state = fromJSONObjectV1(stateLabel, o);
return migrateV1toV2(stateLabel, state);
}
throw new IllegalStateException("version must be in {1, 2}");
}
- protected static State fromJSONObjectV1(StateLabel stateLabel, ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException, NonObjectJSONException {
+ protected static State fromJSONObjectV1(StateLabel stateLabel, ExtendedJSONObject o) throws InvalidKeySpecException, InvalidKeyException, UnsupportedEncodingException, NoSuchAlgorithmException, NonObjectJSONException {
+ byte[] kB;
switch (stateLabel) {
case Engaged:
return new Engaged(
o.getString("email"),
o.getString("uid"),
o.getBoolean("verified"),
Utils.hex2Byte(o.getString("unwrapkB")),
Utils.hex2Byte(o.getString("sessionToken")),
Utils.hex2Byte(o.getString("keyFetchToken")));
case Cohabiting:
+ kB = Utils.hex2Byte(o.getString("kB"));
return new Cohabiting(
o.getString("email"),
o.getString("uid"),
Utils.hex2Byte(o.getString("sessionToken")),
- Utils.hex2Byte(o.getString("kA")),
- Utils.hex2Byte(o.getString("kB")),
+ FxAccountUtils.deriveSyncKey(kB),
+ FxAccountUtils.computeClientState(kB),
keyPairFromJSONObjectV1(o.getObject("keyPair")));
case Married:
+ kB = Utils.hex2Byte(o.getString("kB"));
return new Married(
o.getString("email"),
o.getString("uid"),
Utils.hex2Byte(o.getString("sessionToken")),
- Utils.hex2Byte(o.getString("kA")),
- Utils.hex2Byte(o.getString("kB")),
+ FxAccountUtils.deriveSyncKey(kB),
+ FxAccountUtils.computeClientState(kB),
keyPairFromJSONObjectV1(o.getObject("keyPair")),
o.getString("certificate"));
case Separated:
return new Separated(
o.getString("email"),
o.getString("uid"),
o.getBoolean("verified"));
case Doghouse:
@@ -106,100 +114,131 @@ public class StateFactory {
o.getString("uid"),
o.getBoolean("verified"));
default:
throw new IllegalStateException("unrecognized state label: " + stateLabel);
}
}
/**
- * Exactly the same as {@link fromJSONObjectV1}, except that all key pairs are DSA key pairs.
+ * Exactly the same as {@link StateFactory#fromJSONObjectV1}, except that all key pairs are DSA key pairs.
*/
- protected static State fromJSONObjectV2(StateLabel stateLabel, ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException, NonObjectJSONException {
+ private static State fromJSONObjectV2(StateLabel stateLabel, ExtendedJSONObject o) throws InvalidKeySpecException, InvalidKeyException, UnsupportedEncodingException, NoSuchAlgorithmException, NonObjectJSONException {
+ byte[] kB;
switch (stateLabel) {
case Cohabiting:
+ kB = Utils.hex2Byte(o.getString("kB"));
return new Cohabiting(
o.getString("email"),
o.getString("uid"),
Utils.hex2Byte(o.getString("sessionToken")),
- Utils.hex2Byte(o.getString("kA")),
- Utils.hex2Byte(o.getString("kB")),
+ FxAccountUtils.deriveSyncKey(kB),
+ FxAccountUtils.computeClientState(kB),
keyPairFromJSONObjectV2(o.getObject("keyPair")));
case Married:
+ kB = Utils.hex2Byte(o.getString("kB"));
return new Married(
o.getString("email"),
o.getString("uid"),
Utils.hex2Byte(o.getString("sessionToken")),
- Utils.hex2Byte(o.getString("kA")),
- Utils.hex2Byte(o.getString("kB")),
+ FxAccountUtils.deriveSyncKey(kB),
+ FxAccountUtils.computeClientState(kB),
keyPairFromJSONObjectV2(o.getObject("keyPair")),
o.getString("certificate"));
default:
return fromJSONObjectV1(stateLabel, o);
}
}
/**
- * Exactly the same as {@link fromJSONObjectV2}, except that there's a new
+ * Exactly the same as {@link StateFactory#fromJSONObjectV2}, except that there's a new
* MigratedFromSyncV11 state.
*/
- protected static State fromJSONObjectV3(StateLabel stateLabel, ExtendedJSONObject o) throws InvalidKeySpecException, NoSuchAlgorithmException, NonObjectJSONException {
+ private static State fromJSONObjectV3(StateLabel stateLabel, ExtendedJSONObject o) throws InvalidKeySpecException, InvalidKeyException, UnsupportedEncodingException, NoSuchAlgorithmException, NonObjectJSONException {
switch (stateLabel) {
case MigratedFromSync11:
return new MigratedFromSync11(
o.getString("email"),
o.getString("uid"),
o.getBoolean("verified"),
o.getString("password"));
default:
return fromJSONObjectV2(stateLabel, o);
}
}
- protected static void logMigration(State from, State to) {
+ /**
+ * Exactly the same as {@link StateFactory#fromJSONObjectV3}, except instead of kB it contains
+ * derived kSync and kXCS.
+ */
+ private static State fromJSONObjectV4(StateLabel stateLabel, ExtendedJSONObject o) throws InvalidKeySpecException, InvalidKeyException, UnsupportedEncodingException, NoSuchAlgorithmException, NonObjectJSONException {
+ switch (stateLabel) {
+ case Cohabiting:
+ return new Cohabiting(
+ o.getString("email"),
+ o.getString("uid"),
+ Utils.hex2Byte(o.getString("sessionToken")),
+ Utils.hex2Byte(o.getString("kSync")),
+ o.getString("kXCS"),
+ keyPairFromJSONObjectV2(o.getObject("keyPair")));
+ case Married:
+ return new Married(
+ o.getString("email"),
+ o.getString("uid"),
+ Utils.hex2Byte(o.getString("sessionToken")),
+ Utils.hex2Byte(o.getString("kSync")),
+ o.getString("kXCS"),
+ keyPairFromJSONObjectV2(o.getObject("keyPair")),
+ o.getString("certificate"));
+ default:
+ return fromJSONObjectV3(stateLabel, o);
+ }
+ }
+
+ private static void logMigration(State from, State to) {
if (!FxAccountUtils.LOG_PERSONAL_INFORMATION) {
return;
}
try {
FxAccountUtils.pii(LOG_TAG, "V1 persisted state is: " + from.toJSONObject().toJSONString());
} catch (Exception e) {
Logger.warn(LOG_TAG, "Error producing JSON representation of V1 state.", e);
}
FxAccountUtils.pii(LOG_TAG, "Generated new V2 state: " + to.toJSONObject().toJSONString());
}
- protected static State migrateV1toV2(StateLabel stateLabel, State state) throws NoSuchAlgorithmException {
+ private static State migrateV1toV2(StateLabel stateLabel, State state) throws NoSuchAlgorithmException {
if (state == null) {
// This should never happen, but let's be careful.
Logger.error(LOG_TAG, "Got null state in migrateV1toV2; returning null.");
return state;
}
Logger.info(LOG_TAG, "Migrating V1 persisted State to V2; stateLabel: " + stateLabel);
// In V1, we use an RSA keyPair. In V2, we use a DSA keyPair. Only
// Cohabiting and Married states have a persisted keyPair at all; all
// other states need no conversion at all.
switch (stateLabel) {
case Cohabiting: {
// In the Cohabiting state, we can just generate a new key pair and move on.
final Cohabiting cohabiting = (Cohabiting) state;
final BrowserIDKeyPair keyPair = generateKeyPair();
- final State migrated = new Cohabiting(cohabiting.email, cohabiting.uid, cohabiting.sessionToken, cohabiting.kA, cohabiting.kB, keyPair);
+ final State migrated = new Cohabiting(cohabiting.email, cohabiting.uid, cohabiting.sessionToken, cohabiting.kSync, cohabiting.kXCS, keyPair);
logMigration(cohabiting, migrated);
return migrated;
}
case Married: {
// In the Married state, we cannot only change the key pair: the stored
// certificate signs the public key of the now obsolete key pair. We
// regress to the Cohabiting state; the next time we sync, we should
// advance back to Married.
final Married married = (Married) state;
final BrowserIDKeyPair keyPair = generateKeyPair();
- final State migrated = new Cohabiting(married.email, married.uid, married.sessionToken, married.kA, married.kB, keyPair);
+ final State migrated = new Cohabiting(married.email, married.uid, married.sessionToken, married.kSync, married.kXCS, keyPair);
logMigration(married, migrated);
return migrated;
}
default:
// Otherwise, V1 and V2 states are identical.
return state;
}
}
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/TokensAndKeysState.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/login/TokensAndKeysState.java
@@ -5,36 +5,36 @@
package org.mozilla.gecko.fxa.login;
import org.mozilla.gecko.browserid.BrowserIDKeyPair;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.Utils;
public abstract class TokensAndKeysState extends State {
protected final byte[] sessionToken;
- protected final byte[] kA;
- protected final byte[] kB;
+ /* package-private */ final byte[] kSync;
+ /* package-private */ final String kXCS;
protected final BrowserIDKeyPair keyPair;
- public TokensAndKeysState(StateLabel stateLabel, String email, String uid, byte[] sessionToken, byte[] kA, byte[] kB, BrowserIDKeyPair keyPair) {
+ /* package-private */ TokensAndKeysState(StateLabel stateLabel, String email, String uid, byte[] sessionToken, byte[] kSync, String kXCS, BrowserIDKeyPair keyPair) {
super(stateLabel, email, uid, true);
- Utils.throwIfNull(sessionToken, kA, kB, keyPair);
+ Utils.throwIfNull(sessionToken, kSync, kXCS, keyPair);
this.sessionToken = sessionToken;
- this.kA = kA;
- this.kB = kB;
+ this.kSync = kSync;
+ this.kXCS = kXCS;
this.keyPair = keyPair;
}
@Override
public ExtendedJSONObject toJSONObject() {
ExtendedJSONObject o = super.toJSONObject();
// Fields are non-null by constructor.
o.put("sessionToken", Utils.byte2Hex(sessionToken));
- o.put("kA", Utils.byte2Hex(kA));
- o.put("kB", Utils.byte2Hex(kB));
+ o.put("kSync", Utils.byte2Hex(kSync));
+ o.put("kXCS", kXCS);
o.put("keyPair", keyPair.toJSONObject());
return o;
}
@Override
public byte[] getSessionToken() {
return sessionToken;
}
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/background/fxa/test/TestFxAccountUtils.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/background/fxa/test/TestFxAccountUtils.java
@@ -69,17 +69,18 @@ public class TestFxAccountUtils {
String expectedVHex = "00173ffa0263e63ccfd6791b8ee2a40f048ec94cd95aa8a3125726f9805e0c8283c658dc0b607fbb25db68e68e93f2658483049c68af7e8214c49fde2712a775b63e545160d64b00189a86708c69657da7a1678eda0cd79f86b8560ebdb1ffc221db360eab901d643a75bf1205070a5791230ae56466b8c3c1eb656e19b794f1ea0d2a077b3a755350208ea0118fec8c4b2ec344a05c66ae1449b32609ca7189451c259d65bd15b34d8729afdb5faff8af1f3437bbdc0c3d0b069a8ab2a959c90c5a43d42082c77490f3afcc10ef5648625c0605cdaace6c6fdc9e9a7e6635d619f50af7734522470502cab26a52a198f5b00a279858916507b0b4e9ef9524d6";
Assert.assertEquals(expectedVHex, FxAccountUtils.hexModN(v, SRPConstants._2048.N));
}
@Test
public void testGenerateSyncKeyBundle() throws Exception {
byte[] kB = Utils.hex2Byte("d02d8fe39f28b601159c543f2deeb8f72bdf2043e8279aa08496fbd9ebaea361");
- KeyBundle bundle = FxAccountUtils.generateSyncKeyBundle(kB);
+ byte[] kSync = FxAccountUtils.deriveSyncKey(kB);
+ KeyBundle bundle = FxAccountUtils.generateSyncKeyBundle(kSync);
Assert.assertEquals("rsLwECkgPYeGbYl92e23FskfIbgld9TgeifEaB9ZwTI=", Base64.encodeBase64String(bundle.getEncryptionKey()));
Assert.assertEquals("fs75EseCD/VOLodlIGmwNabBjhTYBHFCe7CGIf0t8Tw=", Base64.encodeBase64String(bundle.getHMACKey()));
}
@Test
public void testGeneration() throws Exception {
byte[] quickStretchedPW = FxAccountUtils.generateQuickStretchedPW(
Utils.hex2Byte("616e6472c3a9406578616d706c652e6f7267"),
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/fxa/login/TestStateFactory.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/fxa/login/TestStateFactory.java
@@ -1,16 +1,17 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.fxa.login;
import junit.framework.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mozilla.gecko.background.fxa.FxAccountUtils;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.browserid.BrowserIDKeyPair;
import org.mozilla.gecko.browserid.DSACryptoImplementation;
import org.mozilla.gecko.fxa.login.State.StateLabel;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.Utils;
@RunWith(TestRunner.class)
@@ -19,53 +20,54 @@ public class TestStateFactory {
public void testGetStateV3() throws Exception {
MigratedFromSync11 migrated = new MigratedFromSync11("email", "uid", true, "password");
// For the current version, we expect to read back what we wrote.
ExtendedJSONObject o;
State state;
o = migrated.toJSONObject();
- Assert.assertEquals(3, o.getLong("version").intValue());
+ Assert.assertEquals(4, o.getLong("version").intValue());
state = StateFactory.fromJSONObject(migrated.stateLabel, o);
Assert.assertEquals(StateLabel.MigratedFromSync11, state.stateLabel);
Assert.assertEquals(o, state.toJSONObject());
// Null passwords are OK.
MigratedFromSync11 migratedNullPassword = new MigratedFromSync11("email", "uid", true, null);
o = migratedNullPassword.toJSONObject();
- Assert.assertEquals(3, o.getLong("version").intValue());
+ Assert.assertEquals(4, o.getLong("version").intValue());
state = StateFactory.fromJSONObject(migratedNullPassword.stateLabel, o);
Assert.assertEquals(StateLabel.MigratedFromSync11, state.stateLabel);
Assert.assertEquals(o, state.toJSONObject());
}
@Test
public void testGetStateV2() throws Exception {
byte[] sessionToken = Utils.generateRandomBytes(32);
- byte[] kA = Utils.generateRandomBytes(32);
byte[] kB = Utils.generateRandomBytes(32);
+ byte[] kSync = FxAccountUtils.deriveSyncKey(kB);
+ String kXCS = FxAccountUtils.computeClientState(kB);
BrowserIDKeyPair keyPair = DSACryptoImplementation.generateKeyPair(512);
- Cohabiting cohabiting = new Cohabiting("email", "uid", sessionToken, kA, kB, keyPair);
+ Cohabiting cohabiting = new Cohabiting("email", "uid", sessionToken, kSync, kXCS, keyPair);
String certificate = "certificate";
- Married married = new Married("email", "uid", sessionToken, kA, kB, keyPair, certificate);
+ Married married = new Married("email", "uid", sessionToken, kSync, kXCS, keyPair, certificate);
// For the current version, we expect to read back what we wrote.
ExtendedJSONObject o;
State state;
o = married.toJSONObject();
- Assert.assertEquals(3, o.getLong("version").intValue());
+ Assert.assertEquals(4, o.getLong("version").intValue());
state = StateFactory.fromJSONObject(married.stateLabel, o);
Assert.assertEquals(StateLabel.Married, state.stateLabel);
Assert.assertEquals(o, state.toJSONObject());
o = cohabiting.toJSONObject();
- Assert.assertEquals(3, o.getLong("version").intValue());
+ Assert.assertEquals(4, o.getLong("version").intValue());
state = StateFactory.fromJSONObject(cohabiting.stateLabel, o);
Assert.assertEquals(StateLabel.Cohabiting, state.stateLabel);
Assert.assertEquals(o, state.toJSONObject());
}
@Test
public void testGetStateV1() throws Exception {
// We can't rely on generating correct V1 objects (since the generation code