diff options
Diffstat (limited to 'services/fxaccounts/tests/xpcshell')
4 files changed, 203 insertions, 7 deletions
diff --git a/services/fxaccounts/tests/xpcshell/test_accounts.js b/services/fxaccounts/tests/xpcshell/test_accounts.js index 239adb206f..1dcbb2d2f2 100644 --- a/services/fxaccounts/tests/xpcshell/test_accounts.js +++ b/services/fxaccounts/tests/xpcshell/test_accounts.js @@ -44,7 +44,7 @@ initTestLogging("Trace"); var log = Log.repository.getLogger("Services.FxAccounts.test"); log.level = Log.Level.Debug; -// See verbose logging from FxAccounts.jsm and jwcrypto.jsm. +// See verbose logging from FxAccounts.sys.mjs and jwcrypto.sys.mjs. Services.prefs.setStringPref("identity.fxaccounts.loglevel", "Trace"); Log.repository.getLogger("FirefoxAccounts").level = Log.Level.Trace; Services.prefs.setStringPref("services.crypto.jwcrypto.log.level", "Debug"); @@ -217,7 +217,7 @@ function MockFxAccounts() { }, }); // and for convenience so we don't have to touch as many lines in this test - // when we refactored FxAccounts.jsm :) + // when we refactored FxAccounts.sys.mjs :) result.setSignedInUser = function (creds) { return result._internal.setSignedInUser(creds); }; diff --git a/services/fxaccounts/tests/xpcshell/test_keys.js b/services/fxaccounts/tests/xpcshell/test_keys.js index 6e650a1609..9a25ca90f3 100644 --- a/services/fxaccounts/tests/xpcshell/test_keys.js +++ b/services/fxaccounts/tests/xpcshell/test_keys.js @@ -99,6 +99,128 @@ add_task(async function test_derive_multiple_keys_at_once() { }); }); +add_task(function test_check_valid_scoped_keys() { + const keys = new FxAccountsKeys(null); + add_task(function test_missing_key_data() { + const scopedKeys = { + "https://identity.mozilla.com/apps/oldsync": { + kty: "oct", + kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw", + scope: "https://identity.mozilla.com/apps/oldsync", + }, + }; + Assert.equal(keys.validScopedKeys(scopedKeys), false); + }); + add_task(function test_unexpected_scope() { + const scopedKeys = { + "https://identity.mozilla.com/apps/oldsync": { + kty: "oct", + kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw", + k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang", + scope: "UnexpectedScope", + }, + }; + Assert.equal(keys.validScopedKeys(scopedKeys), false); + }); + add_task(function test_not_oct_key() { + const scopedKeys = { + "https://identity.mozilla.com/apps/oldsync": { + // Should be "oct"! + kty: "EC", + kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw", + k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang", + scope: "https://identity.mozilla.com/apps/oldsync", + }, + }; + Assert.equal(keys.validScopedKeys(scopedKeys), false); + }); + add_task(function test_invalid_kid_not_timestamp() { + const scopedKeys = { + "https://identity.mozilla.com/apps/oldsync": { + kty: "oct", + // Does not have the timestamp! + kid: "IqQv4onc7VcVE1kTQkyyOw", + k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang", + scope: "https://identity.mozilla.com/apps/oldsync", + }, + }; + Assert.equal(keys.validScopedKeys(scopedKeys), false); + }); + add_task(function test_invalid_kid_not_valid_timestamp() { + const scopedKeys = { + "https://identity.mozilla.com/apps/oldsync": { + kty: "oct", + // foo is not a valid timestamp! + kid: "foo-IqQv4onc7VcVE1kTQkyyOw", + k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang", + scope: "https://identity.mozilla.com/apps/oldsync", + }, + }; + Assert.equal(keys.validScopedKeys(scopedKeys), false); + }); + add_task(function test_invalid_kid_not_b64_fingerprint() { + const scopedKeys = { + "https://identity.mozilla.com/apps/oldsync": { + kty: "oct", + // fingerprint not a valid base64 encoded string. + kid: "1510726318123-notvalidb64][", + k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang", + scope: "https://identity.mozilla.com/apps/oldsync", + }, + }; + Assert.equal(keys.validScopedKeys(scopedKeys), false); + }); + add_task(function test_invalid_k_not_base64() { + const scopedKeys = { + "https://identity.mozilla.com/apps/oldsync": { + kty: "oct", + kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw", + k: "notavalidb64[]", + scope: "https://identity.mozilla.com/apps/oldsync", + }, + }; + Assert.equal(keys.validScopedKeys(scopedKeys), false); + }); + + add_task(function test_multiple_scoped_keys_one_invalid() { + const scopedKeys = { + // Valid + "https://identity.mozilla.com/apps/otherscope": { + kty: "oct", + kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw", + k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang", + scope: "https://identity.mozilla.com/apps/otherscope", + }, + // Invalid + "https://identity.mozilla.com/apps/oldsync": { + kty: "oct", + kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw", + k: "notavalidb64[]", + scope: "https://identity.mozilla.com/apps/oldsync", + }, + }; + Assert.equal(keys.validScopedKeys(scopedKeys), false); + }); + + add_task(function test_valid_scopedkeys() { + const scopedKeys = { + "https://identity.mozilla.com/apps/oldsync": { + kty: "oct", + kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw", + k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang", + scope: "https://identity.mozilla.com/apps/oldsync", + }, + "https://identity.mozilla.com/apps/otherscope": { + kty: "oct", + kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw", + k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang", + scope: "https://identity.mozilla.com/apps/otherscope", + }, + }; + Assert.equal(keys.validScopedKeys(scopedKeys), true); + }); +}); + add_task(async function test_rejects_bad_scoped_key_data() { const keys = new FxAccountsKeys(null); const uid = "aeaa1725c7a24ff983c6295725d5fc9b"; diff --git a/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js b/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js index 5b80035418..1fdfecca61 100644 --- a/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js +++ b/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js @@ -5,7 +5,7 @@ // Tests for FxAccounts, storage and the master password. -// See verbose logging from FxAccounts.jsm +// See verbose logging from FxAccounts.sys.mjs Services.prefs.setStringPref("identity.fxaccounts.loglevel", "Trace"); const { FxAccounts } = ChromeUtils.importESModule( diff --git a/services/fxaccounts/tests/xpcshell/test_oauth_flow.js b/services/fxaccounts/tests/xpcshell/test_oauth_flow.js index ef5102ae17..a5f53b6100 100644 --- a/services/fxaccounts/tests/xpcshell/test_oauth_flow.js +++ b/services/fxaccounts/tests/xpcshell/test_oauth_flow.js @@ -8,6 +8,7 @@ const { FxAccountsOAuth, ERROR_INVALID_SCOPES, + ERROR_INVALID_SCOPED_KEYS, ERROR_INVALID_STATE, ERROR_SYNC_SCOPE_NOT_GRANTED, ERROR_NO_KEYS_JWE, @@ -22,6 +23,7 @@ const { SCOPE_PROFILE, FX_OAUTH_CLIENT_ID } = ChromeUtils.importESModule( ChromeUtils.defineESModuleGetters(this, { jwcrypto: "resource://services-crypto/jwcrypto.sys.mjs", + FxAccountsKeys: "resource://gre/modules/FxAccountsKeys.sys.mjs", }); initTestLogging("Trace"); @@ -159,17 +161,21 @@ add_task(function test_complete_oauth_flow() { const oauthCode = "fake oauth code"; const sessionToken = "01abcef12"; const plainTextScopedKeys = { - kid: "fake key id", - k: "fake key", - kty: "oct", + "https://identity.mozilla.com/apps/oldsync": { + kty: "oct", + kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw", + k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang", + scope: "https://identity.mozilla.com/apps/oldsync", + }, }; const fakeAccessToken = "fake access token"; const fakeRefreshToken = "fake refresh token"; // Then, we initialize a fake http client, we'll add our fake oauthToken call // once we have started the oauth flow (so we have the public keys!) const fxaClient = {}; + const fxaKeys = new FxAccountsKeys(null); // Then, we initialize our oauth object with the given client and begin a new flow - const oauth = new FxAccountsOAuth(fxaClient); + const oauth = new FxAccountsOAuth(fxaClient, fxaKeys); const queryParams = await oauth.beginOAuthFlow(scopes); // Now that we have the public keys in `keys_jwk`, we use it to generate a JWE // representing our scoped keys @@ -271,4 +277,72 @@ add_task(function test_complete_oauth_flow() { // Finally, we verify that all stored flows were cleared Assert.equal(oauth.numOfFlows(), 0); }); + add_task(async function test_complete_oauth_invalid_scoped_keys() { + // First, we initialize some fake values we would typically get + // from outside our system + const scopes = [SCOPE_PROFILE, SCOPE_OLD_SYNC]; + const oauthCode = "fake oauth code"; + const sessionToken = "01abcef12"; + const invalidScopedKeys = { + "https://identity.mozilla.com/apps/oldsync": { + // ====== This is an invalid key type! Should be "oct", so we will raise an error once we realize + kty: "EC", + kid: "1510726318123-IqQv4onc7VcVE1kTQkyyOw", + k: "DW_ll5GwX6SJ5GPqJVAuMUP2t6kDqhUulc2cbt26xbTcaKGQl-9l29FHAQ7kUiJETma4s9fIpEHrt909zgFang", + scope: "https://identity.mozilla.com/apps/oldsync", + }, + }; + const fakeAccessToken = "fake access token"; + const fakeRefreshToken = "fake refresh token"; + // Then, we initialize a fake http client, we'll add our fake oauthToken call + // once we have started the oauth flow (so we have the public keys!) + const fxaClient = {}; + const fxaKeys = new FxAccountsKeys(null); + // Then, we initialize our oauth object with the given client and begin a new flow + const oauth = new FxAccountsOAuth(fxaClient, fxaKeys); + const queryParams = await oauth.beginOAuthFlow(scopes); + // Now that we have the public keys in `keys_jwk`, we use it to generate a JWE + // representing our scoped keys + const keysJwk = queryParams.keys_jwk; + const decodedKeysJwk = JSON.parse( + new TextDecoder().decode( + ChromeUtils.base64URLDecode(keysJwk, { padding: "reject" }) + ) + ); + delete decodedKeysJwk.key_ops; + const jwe = await jwcrypto.generateJWE( + decodedKeysJwk, + new TextEncoder().encode(JSON.stringify(invalidScopedKeys)) + ); + // We also grab the stored PKCE verifier that the oauth object stored internally + // to verify that we correctly send it as a part of our HTTP request + const storedVerifier = oauth.getFlow(queryParams.state).verifier; + + // Now we initialize our mock of the HTTP request, it verifies we passed in all the correct + // parameters and returns what we'd expect a healthy HTTP Response would look like + fxaClient.oauthToken = (sessionTokenHex, code, verifier, clientId) => { + Assert.equal(sessionTokenHex, sessionToken); + Assert.equal(code, oauthCode); + Assert.equal(verifier, storedVerifier); + Assert.equal(clientId, queryParams.client_id); + const response = { + access_token: fakeAccessToken, + refresh_token: fakeRefreshToken, + scope: scopes.join(" "), + keys_jwe: jwe, + }; + return Promise.resolve(response); + }; + + // Then, we call the completeOAuthFlow function, and get back our access token, + // refresh token and scopedKeys + try { + await oauth.completeOAuthFlow(sessionToken, oauthCode, queryParams.state); + Assert.fail( + "Should have thrown an error because the scoped keys are not valid" + ); + } catch (err) { + Assert.equal(err.message, ERROR_INVALID_SCOPED_KEYS); + } + }); }); |