Bug 1838710: Removed old Sync key migrations. r=markh

Differential Revision: https://phabricator.services.mozilla.com/D181473
This commit is contained in:
Tarik Eshaq 2023-06-21 18:04:06 +00:00
parent da7273bd10
commit 59bace8c22
2 changed files with 47 additions and 145 deletions

View file

@ -95,18 +95,6 @@ export class FxAccountsKeys {
return true;
}
// For sync-related scopes, we might have stored the keys in a legacy format.
if (scope == SCOPE_OLD_SYNC) {
if (userData.kSync && userData.kXCS) {
return true;
}
}
// `kB` is deprecated, but if we have it, we can use it to derive any scoped key.
if (userData.kB) {
return true;
}
// If we have a `keyFetchToken` we can fetch `kB`.
if (userData.keyFetchToken) {
return true;
@ -252,7 +240,7 @@ export class FxAccountsKeys {
*/
async _migrateOrFetchKeys(currentState, userData) {
// If the required scopes are present in `scopedKeys`, then we know that we've
// previously applied all the other migrations below
// previously applied all earlier migrations
// so we are safe to delete deprecated fields that older migrations
// might have depended on.
if (
@ -264,42 +252,6 @@ export class FxAccountsKeys {
return this._removeDeprecatedKeys(currentState, userData);
}
// Bug 1661407 - migrate from legacy storage of keys as top-level account
// data fields, to storing them as scoped keys in the `scopedKeys` object.
if (
LEGACY_DERIVED_KEYS_NAMES.every(name => userData.hasOwnProperty(name))
) {
log.info("Migrating from legacy key fields to scopedKeys.");
const scopedKeys = userData.scopedKeys || {};
await currentState.updateUserAccountData({
scopedKeys: {
...scopedKeys,
...(await this._deriveScopedKeysFromAccountData(userData)),
},
});
userData = await currentState.getUserAccountData();
return userData;
}
// Bug 1426306 - Migrate from kB to derived keys.
if (userData.kB) {
log.info("Migrating kB to derived keys.");
const { uid, kB, sessionToken } = userData;
const scopedKeysMetadata = await this._fetchScopedKeysMetadata(
sessionToken
);
await currentState.updateUserAccountData({
uid,
...(await this._deriveKeys(
uid,
CommonUtils.hexToBytes(kB),
scopedKeysMetadata
)),
kA: null, // Remove kA and kB from storage.
kB: null,
});
userData = await currentState.getUserAccountData();
return userData;
}
// Otherwise, we need to fetch from the network and unwrap.
if (!userData.sessionToken) {
throw new Error("No sessionToken");
@ -541,44 +493,6 @@ export class FxAccountsKeys {
return scopedKeys;
}
/**
* Derive the `scopedKeys` data field based on current account data.
*
* This is a backwards-compatibility convenience for users who are already signed in to Firefox
* and have legacy fields like `kSync` and `kXCS` in their top-level account data, but do not have
* the newer `scopedKeys` field. We populate it with the scoped keys for sync.
*
*/
async _deriveScopedKeysFromAccountData(userData) {
const scopedKeysMetadata = await this._fetchScopedKeysMetadata(
userData.sessionToken
);
const scopedKeys = userData.scopedKeys || {};
for (const scope of LEGACY_DERIVED_KEY_SCOPES) {
if (scopedKeysMetadata.hasOwnProperty(scope)) {
let kid, key;
if (scope == SCOPE_OLD_SYNC) {
({ kXCS: kid, kSync: key } = userData);
} else {
// Should never happen, but a nice internal consistency check.
throw new Error(`Unexpected legacy key-bearing scope: ${scope}`);
}
if (!kid || !key) {
throw new Error(
`Account is missing legacy key fields for scope: ${scope}`
);
}
scopedKeys[scope] = await this._formatLegacyScopedKey(
CommonUtils.hexToArrayBuffer(kid),
CommonUtils.hexToArrayBuffer(key),
scope,
scopedKeysMetadata[scope]
);
}
}
return scopedKeys;
}
/**
* Derive a scoped key for an individual OAuth scope.
*

View file

@ -701,59 +701,6 @@ add_test(function test_getKeyForScope() {
});
});
add_task(async function test_getKeyForScope_kb_migration() {
let fxa = new MockFxAccounts();
let user = getTestUser("eusebius");
user.verified = true;
// Set-up the deprecated set of keys.
user.kA = "e0245ab7f10e483470388e0a28f0a03379a3b417174fb2b42feab158b4ac2dbd";
user.kB = "eaf9570b7219a4187d3d6bf3cec2770c2e0719b7cc0dfbb38243d6f1881675e9";
await fxa.setSignedInUser(user);
await fxa.keys.getKeyForScope(SCOPE_OLD_SYNC);
let newUser = await fxa._internal.getUserAccountData();
Assert.equal(newUser.kA, null);
Assert.equal(newUser.kB, null);
Assert.deepEqual(newUser.scopedKeys, {
"https://identity.mozilla.com/apps/oldsync": {
kid: "1234567890123-IqQv4onc7VcVE1kTQkyyOw",
k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang",
kty: "oct",
},
});
// These hex values were manually confirmed to be equivalent to the b64 values above.
Assert.equal(
newUser.kSync,
"0d6fe59791b05fa489e463ea25502e3143f6b7a903aa152e95cd9c6eddbac5b4" +
"dc68a19097ef65dbd147010ee45222444e66b8b3d7c8a441ebb7dd3dce015a9e"
);
Assert.equal(newUser.kXCS, "22a42fe289dced5715135913424cb23b");
});
add_task(async function test_getKeyForScope_scopedKeys_migration() {
let fxa = new MockFxAccounts();
let user = getTestUser("eusebius");
user.verified = true;
// Set-up the keys in deprecated fields.
user.kSync = MOCK_ACCOUNT_KEYS.kSync;
user.kXCS = MOCK_ACCOUNT_KEYS.kXCS;
Assert.equal(user.scopedKeys, null);
await fxa.setSignedInUser(user);
await fxa.keys.getKeyForScope(SCOPE_OLD_SYNC);
let newUser = await fxa._internal.getUserAccountData();
Assert.equal(newUser.kA, null);
Assert.equal(newUser.kB, null);
// It should have correctly formatted the corresponding scoped keys.
const expectedScopedKeys = { ...MOCK_ACCOUNT_KEYS.scopedKeys };
Assert.deepEqual(newUser.scopedKeys, expectedScopedKeys);
// And left the existing key fields unchanged.
Assert.equal(newUser.kSync, user.kSync);
Assert.equal(newUser.kXCS, user.kXCS);
});
add_task(
async function test_getKeyForScope_scopedKeys_migration_removes_deprecated_high_level_keys() {
let fxa = new MockFxAccounts();
@ -886,8 +833,8 @@ add_task(async function test_getKeyForScope_invalid_token() {
await fxa._internal.abortExistingFlow();
});
// This is the exact same test vectors as
// https://github.com/mozilla/fxa-crypto-relier/blob/f94f441159029a645a474d4b6439c38308da0bb0/test/deriver/ScopedKeys.js#L58
// Test vectors from
// https://wiki.mozilla.org/Identity/AttachedServices/KeyServerProtocol#Test_Vectors
add_task(async function test_getKeyForScope_oldsync() {
let fxa = new MockFxAccounts();
let client = fxa._internal.fxAccountsClient;
@ -900,22 +847,63 @@ add_task(async function test_getKeyForScope_oldsync() {
keyRotationTimestamp: 1510726317123,
},
});
// We mock the server returning the wrapKB from our test vectors
client.accountKeys = async () => {
return {
wrapKB: CommonUtils.hexToBytes(
"404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"
),
};
};
// We set the user to have the keyFetchToken and unwrapBKey from our test vectors
let user = {
...getTestUser("eusebius"),
uid: "aeaa1725c7a24ff983c6295725d5fc9b",
kB: "eaf9570b7219a4187d3d6bf3cec2770c2e0719b7cc0dfbb38243d6f1881675e9",
keyFetchToken:
"808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
unwrapBKey:
"6ea660be9c89ec355397f89afb282ea0bf21095760c8c5009bbcc894155bbe2a",
sessionToken: "mock session token, used in metadata request",
verified: true,
};
await fxa.setSignedInUser(user);
// We derive, persist and return the sync key
const key = await fxa.keys.getKeyForScope(SCOPE_OLD_SYNC);
// We verify the key returned matches what we would expect from the test vectors
// kb = 2ee722fdd8ccaa721bdeb2d1b76560efef705b04349d9357c3e592cf4906e075 (from test vectors)
//
// kid can be verified by "${keyRotationTimestamp}-${sha256(kb)[0:16]}"
//
// k can be verified by HKDF(kb, undefined, "identity.mozilla.com/picl/v1/oldsync", 64)
Assert.deepEqual(key, {
scope: SCOPE_OLD_SYNC,
kid: "1510726317123-IqQv4onc7VcVE1kTQkyyOw",
k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang",
kid: "1510726317123-BAik7hEOdpGnPZnPBSdaTg",
k: "fwM5VZu0Spf5XcFRZYX2zk6MrqZP7zvovCBcvuKwgYMif3hz98FHmIVa3qjKjrW0J244Zj-P5oWaOcQbvypmpw",
kty: "oct",
});
});
add_task(async function test_getScopedKeys_cached_key() {
let fxa = new MockFxAccounts();
let user = {
...getTestUser("eusebius"),
uid: "aeaa1725c7a24ff983c6295725d5fc9b",
verified: true,
...MOCK_ACCOUNT_KEYS,
};
await fxa.setSignedInUser(user);
let key = await fxa.keys.getKeyForScope(SCOPE_OLD_SYNC);
Assert.deepEqual(key, {
scope: SCOPE_OLD_SYNC,
...MOCK_ACCOUNT_KEYS.scopedKeys[SCOPE_OLD_SYNC],
});
});
add_task(async function test_getScopedKeys_unavailable_scope() {
let fxa = new MockFxAccounts();
let user = {