summaryrefslogtreecommitdiff
path: root/data/patches/0002-Bug-1013064-part-2-Store-sensitive-FxA-credentials-i.patch
diff options
context:
space:
mode:
authorRuben Rodriguez <ruben@gnu.org>2015-03-08 15:14:03 +0000
committerRuben Rodriguez <ruben@gnu.org>2015-03-08 15:14:03 +0000
commit9a0fd32cf6e2ada37675bc743532c5004b16e5e3 (patch)
treed66faa3279486d2e8ad72bf70e7ef522460372a0 /data/patches/0002-Bug-1013064-part-2-Store-sensitive-FxA-credentials-i.patch
parent2732de330618bc29b7ab07f4004d8e84f698cf95 (diff)
Added patches to fix build bugs for Windows, MacOS and Android
Diffstat (limited to 'data/patches/0002-Bug-1013064-part-2-Store-sensitive-FxA-credentials-i.patch')
-rw-r--r--data/patches/0002-Bug-1013064-part-2-Store-sensitive-FxA-credentials-i.patch551
1 files changed, 551 insertions, 0 deletions
diff --git a/data/patches/0002-Bug-1013064-part-2-Store-sensitive-FxA-credentials-i.patch b/data/patches/0002-Bug-1013064-part-2-Store-sensitive-FxA-credentials-i.patch
new file mode 100644
index 0000000..bbd6f8d
--- /dev/null
+++ b/data/patches/0002-Bug-1013064-part-2-Store-sensitive-FxA-credentials-i.patch
@@ -0,0 +1,551 @@
+From 4a92f9ee1ba35989f82a24bba18806f8616a5be8 Mon Sep 17 00:00:00 2001
+From: Mark Hammond <mhammond@skippinet.com.au>
+Date: Sat, 14 Jun 2014 14:33:20 +1000
+Subject: Bug 1013064 (part 2) - Store sensitive FxA credentials in the login
+ manager. r=ckarlof,dveditz
+
+---
+ services/fxaccounts/FxAccounts.jsm | 170 ++++++++++++++++++
+ services/fxaccounts/FxAccountsCommon.js | 13 ++
+ services/fxaccounts/moz.build | 5 +-
+ .../tests/xpcshell/test_loginmgr_storage.js | 196 +++++++++++++++++++++
+ services/fxaccounts/tests/xpcshell/xpcshell.ini | 2 +
+ services/sync/modules/util.js | 12 +-
+ 6 files changed, 396 insertions(+), 2 deletions(-)
+ create mode 100644 services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js
+
+diff --git a/services/fxaccounts/FxAccounts.jsm b/services/fxaccounts/FxAccounts.jsm
+index cc521b8..e9328dd 100644
+--- a/services/fxaccounts/FxAccounts.jsm
++++ b/services/fxaccounts/FxAccounts.jsm
+@@ -298,17 +298,22 @@ function FxAccountsInternal() {
+ // able to abort all work on the first sign-in process. The currentTimer and
+ // currentAccountState are used for this purpose.
+ // (XXX - should the timer be directly on the currentAccountState?)
+ this.currentTimer = null;
+ this.currentAccountState = new AccountState(this);
+
+ // We don't reference |profileDir| in the top-level module scope
+ // as we may be imported before we know where it is.
++ // We only want the fancy new LoginManagerStorage on desktop.
++#if defined(MOZ_B2G)
+ this.signedInUserStorage = new JSONStorage({
++#else
++ this.signedInUserStorage = new LoginManagerStorage({
++#endif
+ filename: DEFAULT_STORAGE_FILENAME,
+ baseDir: OS.Constants.Path.profileDir,
+ });
+ }
+
+ /**
+ * The internal API's prototype.
+ */
+@@ -896,16 +901,181 @@ JSONStorage.prototype = {
+ .then(CommonUtils.writeJSON.bind(null, contents, this.path));
+ },
+
+ get: function() {
+ return CommonUtils.readJSON(this.path);
+ }
+ };
+
++/**
++ * LoginManagerStorage constructor that creates instances that may set/get
++ * from a combination of a clear-text JSON file and stored securely in
++ * the nsILoginManager.
++ *
++ * @param options {
++ * filename: of the plain-text file to write to
++ * baseDir: directory where the file resides
++ * }
++ * @return instance
++ */
++
++function LoginManagerStorage(options) {
++ // we reuse the JSONStorage for writing the plain-text stuff.
++ this.jsonStorage = new JSONStorage(options);
++}
++
++LoginManagerStorage.prototype = {
++ // The fields in the credentials JSON object that are stored in plain-text
++ // in the profile directory. All other fields are stored in the login manager,
++ // and thus are only available when the master-password is unlocked.
++
++ // a hook point for testing.
++ get _isLoggedIn() {
++ return Services.logins.isLoggedIn;
++ },
++
++ // Clear any data from the login manager. Returns true if the login manager
++ // was unlocked (even if no existing logins existed) or false if it was
++ // locked (meaning we don't even know if it existed or not.)
++ _clearLoginMgrData: Task.async(function* () {
++ try { // Services.logins might be third-party and broken...
++ yield Services.logins.initializationPromise;
++ if (!this._isLoggedIn) {
++ return false;
++ }
++ let logins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null, FXA_PWDMGR_REALM);
++ for (let login of logins) {
++ Services.logins.removeLogin(login);
++ }
++ return true;
++ } catch (ex) {
++ log.error("Failed to clear login data: ${}", ex);
++ return false;
++ }
++ }),
++
++ set: Task.async(function* (contents) {
++ if (!contents) {
++ // User is signing out - write the null to the json file.
++ yield this.jsonStorage.set(contents);
++
++ // And nuke it from the login manager.
++ let cleared = yield this._clearLoginMgrData();
++ if (!cleared) {
++ // just log a message - we verify that the email address matches when
++ // we reload it, so having a stale entry doesn't really hurt.
++ log.info("not removing credentials from login manager - not logged in");
++ }
++ return;
++ }
++
++ // We are saving actual data.
++ // Split the data into 2 chunks - one to go to the plain-text, and the
++ // other to write to the login manager.
++ let toWriteJSON = {version: contents.version};
++ let accountDataJSON = toWriteJSON.accountData = {};
++ let toWriteLoginMgr = {version: contents.version};
++ let accountDataLoginMgr = toWriteLoginMgr.accountData = {};
++ for (let [name, value] of Iterator(contents.accountData)) {
++ if (FXA_PWDMGR_PLAINTEXT_FIELDS.indexOf(name) >= 0) {
++ accountDataJSON[name] = value;
++ } else {
++ accountDataLoginMgr[name] = value;
++ }
++ }
++ yield this.jsonStorage.set(toWriteJSON);
++
++ try { // Services.logins might be third-party and broken...
++ // and the stuff into the login manager.
++ yield Services.logins.initializationPromise;
++ // If MP is locked we silently fail - the user may need to re-auth
++ // next startup.
++ if (!this._isLoggedIn) {
++ log.info("not saving credentials to login manager - not logged in");
++ return;
++ }
++ // write the rest of the data to the login manager.
++ let loginInfo = new Components.Constructor(
++ "@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo, "init");
++ let login = new loginInfo(FXA_PWDMGR_HOST,
++ null, // aFormSubmitURL,
++ FXA_PWDMGR_REALM, // aHttpRealm,
++ contents.accountData.email, // aUsername
++ JSON.stringify(toWriteLoginMgr), // aPassword
++ "", // aUsernameField
++ "");// aPasswordField
++
++ let existingLogins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null,
++ FXA_PWDMGR_REALM);
++ if (existingLogins.length) {
++ Services.logins.modifyLogin(existingLogins[0], login);
++ } else {
++ Services.logins.addLogin(login);
++ }
++ } catch (ex) {
++ log.error("Failed to save data to the login manager: ${}", ex);
++ }
++ }),
++
++ get: Task.async(function* () {
++ // we need to suck some data from the .json file in the profile dir and
++ // some other from the login manager.
++ let data = yield this.jsonStorage.get();
++ if (!data) {
++ // no user logged in, nuke the storage data incase we couldn't remove
++ // it previously and then we are done.
++ yield this._clearLoginMgrData();
++ return null;
++ }
++
++ // if we have encryption keys it must have been saved before we
++ // used the login manager, so re-save it.
++ if (data.accountData.kA || data.accountData.kB || data.keyFetchToken) {
++ log.info("account data needs migration to the login manager.");
++ yield this.set(data);
++ }
++
++ try { // Services.logins might be third-party and broken...
++ // read the data from the login manager and merge it for return.
++ yield Services.logins.initializationPromise;
++
++ if (!this._isLoggedIn) {
++ log.info("returning partial account data as the login manager is locked.");
++ return data;
++ }
++
++ let logins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null, FXA_PWDMGR_REALM);
++ if (logins.length == 0) {
++ // This could happen if the MP was locked when we wrote the data.
++ log.info("Can't find the rest of the credentials in the login manager");
++ return data;
++ }
++ let login = logins[0];
++ if (login.username == data.accountData.email) {
++ let lmData = JSON.parse(login.password);
++ if (lmData.version == data.version) {
++ // Merge the login manager data
++ copyObjectProperties(lmData.accountData, data.accountData);
++ } else {
++ log.info("version field in the login manager doesn't match - ignoring it");
++ yield this._clearLoginMgrData();
++ }
++ } else {
++ log.info("username in the login manager doesn't match - ignoring it");
++ yield this._clearLoginMgrData();
++ }
++ } catch (ex) {
++ log.error("Failed to get data from the login manager: ${}", ex);
++ }
++ return data;
++ }),
++
++}
++
+ // A getter for the instance to export
+ XPCOMUtils.defineLazyGetter(this, "fxAccounts", function() {
+ let a = new FxAccounts();
+
+ // XXX Bug 947061 - We need a strategy for resuming email verification after
+ // browser restart
+ a.loadAndPoll();
+
+diff --git a/services/fxaccounts/FxAccountsCommon.js b/services/fxaccounts/FxAccountsCommon.js
+index 2bef093..476e528 100644
+--- a/services/fxaccounts/FxAccountsCommon.js
++++ b/services/fxaccounts/FxAccountsCommon.js
+@@ -170,10 +170,23 @@ SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_AUTH_NONCE] = ERROR_INVALID_AUTH
+ SERVER_ERRNO_TO_ERROR[ERRNO_ENDPOINT_NO_LONGER_SUPPORTED] = ERROR_ENDPOINT_NO_LONGER_SUPPORTED;
+ SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_LOGIN_METHOD] = ERROR_INCORRECT_LOGIN_METHOD;
+ SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_KEY_RETRIEVAL_METHOD] = ERROR_INCORRECT_KEY_RETRIEVAL_METHOD;
+ SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_API_VERSION] = ERROR_INCORRECT_API_VERSION;
+ SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_EMAIL_CASE] = ERROR_INCORRECT_EMAIL_CASE;
+ SERVER_ERRNO_TO_ERROR[ERRNO_SERVICE_TEMP_UNAVAILABLE] = ERROR_SERVICE_TEMP_UNAVAILABLE;
+ SERVER_ERRNO_TO_ERROR[ERRNO_UNKNOWN_ERROR] = ERROR_UNKNOWN;
+
++// FxAccounts has the ability to "split" the credentials between a plain-text
++// JSON file in the profile dir and in the login manager.
++// These constants relate to that.
++
++// The fields we save in the plaintext JSON.
++// See bug 1013064 comments 23-25 for why the sessionToken is "safe"
++this.FXA_PWDMGR_PLAINTEXT_FIELDS = ["email", "verified", "authAt",
++ "sessionToken", "uid"];
++// The pseudo-host we use in the login manager
++this.FXA_PWDMGR_HOST = "chrome://FirefoxAccounts";
++// The realm we use in the login manager.
++this.FXA_PWDMGR_REALM = "Firefox Accounts credentials";
++
+ // Allow this file to be imported via Components.utils.import().
+ this.EXPORTED_SYMBOLS = Object.keys(this);
+diff --git a/services/fxaccounts/moz.build b/services/fxaccounts/moz.build
+index f959714..1bb8c09 100644
+--- a/services/fxaccounts/moz.build
++++ b/services/fxaccounts/moz.build
+@@ -5,16 +5,19 @@
+ # file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+ PARALLEL_DIRS += ['interfaces']
+
+ TEST_DIRS += ['tests']
+
+ EXTRA_JS_MODULES += [
+ 'Credentials.jsm',
+- 'FxAccounts.jsm',
+ 'FxAccountsClient.jsm',
+ 'FxAccountsCommon.js'
+ ]
+
++EXTRA_PP_JS_MODULES += [
++ 'FxAccounts.jsm',
++]
++
+ # For now, we will only be using the FxA manager in B2G.
+ if CONFIG['MOZ_B2G']:
+ EXTRA_JS_MODULES += ['FxAccountsManager.jsm']
+diff --git a/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js b/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js
+new file mode 100644
+index 0000000..297b256
+--- /dev/null
++++ b/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js
+@@ -0,0 +1,196 @@
++/* Any copyright is dedicated to the Public Domain.
++ * http://creativecommons.org/publicdomain/zero/1.0/ */
++
++"use strict";
++
++// Tests for FxAccounts, storage and the master password.
++
++// Stop us hitting the real auth server.
++Services.prefs.setCharPref("identity.fxaccounts.auth.uri", "http://localhost");
++
++Cu.import("resource://gre/modules/Services.jsm");
++Cu.import("resource://gre/modules/FxAccounts.jsm");
++Cu.import("resource://gre/modules/FxAccountsClient.jsm");
++Cu.import("resource://gre/modules/FxAccountsCommon.js");
++Cu.import("resource://gre/modules/osfile.jsm");
++Cu.import("resource://services-common/utils.js");
++Cu.import("resource://gre/modules/FxAccountsCommon.js");
++
++initTestLogging("Trace");
++// See verbose logging from FxAccounts.jsm
++Services.prefs.setCharPref("identity.fxaccounts.loglevel", "DEBUG");
++
++function run_test() {
++ run_next_test();
++}
++
++function getLoginMgrData() {
++ let logins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null, FXA_PWDMGR_REALM);
++ if (logins.length == 0) {
++ return null;
++ }
++ Assert.equal(logins.length, 1, "only 1 login available");
++ return logins[0];
++}
++
++add_task(function test_simple() {
++ let fxa = new FxAccounts({});
++
++ let creds = {
++ email: "test@example.com",
++ sessionToken: "sessionToken",
++ kA: "the kA value",
++ kB: "the kB value",
++ verified: true
++ };
++ yield fxa.setSignedInUser(creds);
++
++ // This should have stored stuff in both the .json file in the profile
++ // dir, and the login dir.
++ let path = OS.Path.join(OS.Constants.Path.profileDir, "signedInUser.json");
++ let data = yield CommonUtils.readJSON(path);
++
++ Assert.strictEqual(data.accountData.email, creds.email, "correct email in the clear text");
++ Assert.strictEqual(data.accountData.sessionToken, creds.sessionToken, "correct sessionToken in the clear text");
++ Assert.strictEqual(data.accountData.verified, creds.verified, "correct verified flag");
++
++ Assert.ok(!("kA" in data.accountData), "kA not stored in clear text");
++ Assert.ok(!("kB" in data.accountData), "kB not stored in clear text");
++
++ let login = getLoginMgrData();
++ Assert.strictEqual(login.username, creds.email, "email matches");
++ let loginData = JSON.parse(login.password);
++ Assert.strictEqual(loginData.version, data.version, "same version flag in both places");
++ Assert.strictEqual(loginData.accountData.kA, creds.kA, "correct kA in the login mgr");
++ Assert.strictEqual(loginData.accountData.kB, creds.kB, "correct kB in the login mgr");
++
++ Assert.ok(!("email" in loginData), "email not stored in the login mgr json");
++ Assert.ok(!("sessionToken" in loginData), "sessionToken not stored in the login mgr json");
++ Assert.ok(!("verified" in loginData), "verified not stored in the login mgr json");
++
++ yield fxa.signOut(/* localOnly = */ true);
++ Assert.strictEqual(getLoginMgrData(), null, "login mgr data deleted on logout");
++});
++
++add_task(function test_MPLocked() {
++ let fxa = new FxAccounts({});
++
++ let creds = {
++ email: "test@example.com",
++ sessionToken: "sessionToken",
++ kA: "the kA value",
++ kB: "the kB value",
++ verified: true
++ };
++
++ // tell the storage that the MP is locked.
++ fxa.internal.signedInUserStorage.__defineGetter__("_isLoggedIn", function() false);
++ yield fxa.setSignedInUser(creds);
++
++ // This should have stored stuff in the .json, and the login manager stuff
++ // will not exist.
++ let path = OS.Path.join(OS.Constants.Path.profileDir, "signedInUser.json");
++ let data = yield CommonUtils.readJSON(path);
++
++ Assert.strictEqual(data.accountData.email, creds.email, "correct email in the clear text");
++ Assert.strictEqual(data.accountData.sessionToken, creds.sessionToken, "correct sessionToken in the clear text");
++ Assert.strictEqual(data.accountData.verified, creds.verified, "correct verified flag");
++
++ Assert.ok(!("kA" in data.accountData), "kA not stored in clear text");
++ Assert.ok(!("kB" in data.accountData), "kB not stored in clear text");
++
++ Assert.strictEqual(getLoginMgrData(), null, "login mgr data doesn't exist");
++ yield fxa.signOut(/* localOnly = */ true)
++});
++
++add_task(function test_consistentWithMPEdgeCases() {
++ let fxa = new FxAccounts({});
++
++ let creds1 = {
++ email: "test@example.com",
++ sessionToken: "sessionToken",
++ kA: "the kA value",
++ kB: "the kB value",
++ verified: true
++ };
++
++ let creds2 = {
++ email: "test2@example.com",
++ sessionToken: "sessionToken2",
++ kA: "the kA value2",
++ kB: "the kB value2",
++ verified: false,
++ };
++
++ // Log a user in while MP is unlocked.
++ yield fxa.setSignedInUser(creds1);
++
++ // tell the storage that the MP is locked - this will prevent logout from
++ // being able to clear the data.
++ fxa.internal.signedInUserStorage.__defineGetter__("_isLoggedIn", function() false);
++
++ // now set the second credentials.
++ yield fxa.setSignedInUser(creds2);
++
++ // We should still have creds1 data in the login manager.
++ let login = getLoginMgrData();
++ Assert.strictEqual(login.username, creds1.email);
++ // and that we do have the first kA in the login manager.
++ Assert.strictEqual(JSON.parse(login.password).accountData.kA, creds1.kA,
++ "stale data still in login mgr");
++
++ // Make a new FxA instance (otherwise the values in memory will be used.)
++ // Because we haven't overridden _isLoggedIn for this new instance it will
++ // treat the MP as unlocked.
++ let fxa = new FxAccounts({});
++
++ let accountData = yield fxa.getSignedInUser();
++ Assert.strictEqual(accountData.email, creds2.email);
++ // we should have no kA at all.
++ Assert.strictEqual(accountData.kA, undefined, "stale kA wasn't used");
++ yield fxa.signOut(/* localOnly = */ true)
++});
++
++add_task(function test_migration() {
++ // manually write out the full creds data to the JSON - this will look like
++ // old data that needs migration.
++ let creds = {
++ email: "test@example.com",
++ sessionToken: "sessionToken",
++ kA: "the kA value",
++ kB: "the kB value",
++ verified: true
++ };
++ let toWrite = {
++ version: 1,
++ accountData: creds,
++ };
++
++ let path = OS.Path.join(OS.Constants.Path.profileDir, "signedInUser.json");
++ let data = yield CommonUtils.writeJSON(toWrite, path);
++
++ // Create an FxA object - and tell it to load the data.
++ let fxa = new FxAccounts({});
++ data = yield fxa.getSignedInUser();
++
++ Assert.deepEqual(data, creds, "we should have everything available");
++
++ // now sniff the data on disk - it should have been magically migrated.
++ data = yield CommonUtils.readJSON(path);
++
++ Assert.strictEqual(data.accountData.email, creds.email, "correct email in the clear text");
++ Assert.strictEqual(data.accountData.sessionToken, creds.sessionToken, "correct sessionToken in the clear text");
++ Assert.strictEqual(data.accountData.verified, creds.verified, "correct verified flag");
++
++ Assert.ok(!("kA" in data.accountData), "kA not stored in clear text");
++ Assert.ok(!("kB" in data.accountData), "kB not stored in clear text");
++
++ // and it should magically be in the login manager.
++ let login = getLoginMgrData();
++ Assert.strictEqual(login.username, creds.email);
++ // and that we do have the first kA in the login manager.
++ Assert.strictEqual(JSON.parse(login.password).accountData.kA, creds.kA,
++ "kA was migrated");
++
++ yield fxa.signOut(/* localOnly = */ true)
++});
+diff --git a/services/fxaccounts/tests/xpcshell/xpcshell.ini b/services/fxaccounts/tests/xpcshell/xpcshell.ini
+index 9d9bffe..4448d76 100644
+--- a/services/fxaccounts/tests/xpcshell/xpcshell.ini
++++ b/services/fxaccounts/tests/xpcshell/xpcshell.ini
+@@ -1,10 +1,12 @@
+ [DEFAULT]
+ head = head.js ../../../common/tests/unit/head_helpers.js ../../../common/tests/unit/head_http.js
+ tail =
+
+ [test_accounts.js]
+ [test_client.js]
+ [test_credentials.js]
++[test_loginmgr_storage.js]
++skip-if = appname == 'b2g' # login manager storage only used on desktop.
+ [test_manager.js]
+ run-if = appname == 'b2g'
+ reason = FxAccountsManager is only available for B2G for now
+diff --git a/services/sync/modules/util.js b/services/sync/modules/util.js
+index 194e0b2..4691428 100644
+--- a/services/sync/modules/util.js
++++ b/services/sync/modules/util.js
+@@ -14,16 +14,23 @@ Cu.import("resource://services-common/async.js", this);
+ Cu.import("resource://services-crypto/utils.js");
+ Cu.import("resource://services-sync/constants.js");
+ Cu.import("resource://gre/modules/Preferences.jsm");
+ Cu.import("resource://gre/modules/Services.jsm", this);
+ Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+ Cu.import("resource://gre/modules/osfile.jsm", this);
+ Cu.import("resource://gre/modules/Task.jsm", this);
+
++// FxAccountsCommon.js doesn't use a "namespace", so create one here.
++XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function() {
++ let FxAccountsCommon = {};
++ Cu.import("resource://gre/modules/FxAccountsCommon.js", FxAccountsCommon);
++ return FxAccountsCommon;
++});
++
+ /*
+ * Utility functions
+ */
+
+ this.Utils = {
+ // Alias in functions from CommonUtils. These previously were defined here.
+ // In the ideal world, references to these would be removed.
+ nextTick: CommonUtils.nextTick,
+@@ -589,18 +596,21 @@ this.Utils = {
+ * reset when we drop sync credentials, etc.
+ */
+ getSyncCredentialsHosts: function() {
+ // This is somewhat expensive and the result static, so we cache the result.
+ if (this._syncCredentialsHosts) {
+ return this._syncCredentialsHosts;
+ }
+ let result = new Set();
+- // the legacy sync host.
++ // the legacy sync host
+ result.add(PWDMGR_HOST);
++ // the FxA host
++ result.add(FxAccountsCommon.FXA_PWDMGR_HOST);
++ //
+ // The FxA hosts - these almost certainly all have the same hostname, but
+ // better safe than sorry...
+ for (let prefName of ["identity.fxaccounts.remote.force_auth.uri",
+ "identity.fxaccounts.remote.signup.uri",
+ "identity.fxaccounts.remote.signin.uri",
+ "identity.fxaccounts.settings.uri"]) {
+ let prefVal;
+ try {
+--
+1.8.3.msysgit.0
+