Bug 499417 - Refactor login manager's crypto code. r=vladimir, r=zpao
authorJustin Dolske <dolske@mozilla.com>
Sun, 13 Dec 2009 17:04:43 -0800
changeset 35689 ec5877cfb2178a0c1ce3ce79e58831c1fe376b53
parent 35688 faf866398f1760da768b9a45b600e84378275823
child 35690 e2f9cde1e66d427834e800fcf4a66d951c96ea4e
push id10681
push userjdolske@mozilla.com
push dateMon, 14 Dec 2009 01:05:05 +0000
treeherdermozilla-central@ec5877cfb217 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvladimir, zpao
bugs499417
milestone1.9.3a1pre
Bug 499417 - Refactor login manager's crypto code. r=vladimir, r=zpao
browser/installer/package-manifest.in
toolkit/components/passwordmgr/public/Makefile.in
toolkit/components/passwordmgr/public/nsILoginManagerCrypto.idl
toolkit/components/passwordmgr/src/Makefile.in
toolkit/components/passwordmgr/src/crypto-SDR.js
toolkit/components/passwordmgr/src/storage-mozStorage.js
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -276,16 +276,17 @@
 @BINPATH@/components/nsSearchService.js
 @BINPATH@/components/nsSearchSuggestions.js
 @BINPATH@/components/nsTryToClose.js
 @BINPATH@/components/nsLoginInfo.js
 @BINPATH@/components/nsLoginManager.js
 @BINPATH@/components/nsLoginManagerPrompter.js
 @BINPATH@/components/storage-Legacy.js
 @BINPATH@/components/storage-mozStorage.js
+@BINPATH@/components/crypto-SDR.js
 @BINPATH@/components/jsconsole-clhandler.js
 #ifdef MOZ_GTK2
 @BINPATH@/components/nsFilePicker.js
 #endif
 @BINPATH@/components/nsHelperAppDlg.js
 @BINPATH@/components/nsDownloadManagerUI.js
 @BINPATH@/components/nsProxyAutoConfig.js
 @BINPATH@/components/NetworkGeolocationProvider.js
--- a/toolkit/components/passwordmgr/public/Makefile.in
+++ b/toolkit/components/passwordmgr/public/Makefile.in
@@ -46,11 +46,12 @@ XPIDL_MODULE  = loginmgr
 
 XPIDLSRCS = \
 		nsILoginInfo.idl \
 		nsILoginMetaInfo.idl \
 		nsILoginManager.idl \
 		nsILoginManagerStorage.idl \
 		nsILoginManagerPrompter.idl \
 		nsILoginManagerIEMigrationHelper.idl \
+		nsILoginManagerCrypto.idl \
 		$(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/toolkit/components/passwordmgr/public/nsILoginManagerCrypto.idl
@@ -0,0 +1,73 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Justin Dolske <dolske@mozilla.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(1ddbe67d-a216-4915-9e0a-3e1b95b7126c)]
+
+interface nsILoginManagerCrypto : nsISupports {
+
+    /**
+     * encrypt
+     *
+     * @param plainText
+     *        The string to be encrypted.
+     *
+     * Encrypts the specified string, returning the ciphertext value.
+     *
+     * NOTE: The current implemention of this inferface simply uses NSS/PSM's
+     * "Secret Decoder Ring" service. It is not recommended for general
+     * purpose encryption/decryption.
+     *
+     * Can throw if the user cancels entry of their master password.
+     */
+    AString encrypt(in AString plainText);
+
+    /**
+     * decrypt
+     *
+     * @param cipherText
+     *        The string to be decrypted.
+     *
+     * Decrypts the specified string, returning the plaintext value.
+     *
+     * Can throw if the user cancels entry of their master password, or if the
+     * cipherText value can not be successfully decrypted (eg, if it was
+     * encrypted with some other key).
+     */
+    AString decrypt(in AString cipherText);
+};
--- a/toolkit/components/passwordmgr/src/Makefile.in
+++ b/toolkit/components/passwordmgr/src/Makefile.in
@@ -44,12 +44,13 @@ include $(DEPTH)/config/autoconf.mk
 MODULE = loginmgr
 
 EXTRA_COMPONENTS = \
 			nsLoginManager.js \
 			nsLoginManagerPrompter.js \
 			nsLoginInfo.js \
 			storage-Legacy.js \
 			storage-mozStorage.js \
+			crypto-SDR.js \
 			$(NULL)
 
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/toolkit/components/passwordmgr/src/crypto-SDR.js
@@ -0,0 +1,205 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *  Justin Dolske <dolske@mozilla.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function LoginManagerCrypto_SDR() {
+    this.init();
+};
+
+LoginManagerCrypto_SDR.prototype = {
+
+    classDescription  : "LoginManagerCrypto_SDR",
+    contractID : "@mozilla.org/login-manager/crypto/SDR;1",
+    classID : Components.ID("{dc6c2976-0f73-4f1f-b9ff-3d72b4e28309}"),
+    QueryInterface : XPCOMUtils.generateQI([Ci.nsILoginManagerCrypto]),
+
+    __logService : null, // Console logging service, used for debugging.
+    get _logService() {
+        if (!this.__logService)
+            this.__logService = Cc["@mozilla.org/consoleservice;1"].
+                                getService(Ci.nsIConsoleService);
+        return this.__logService;
+    },
+
+    __decoderRing : null,  // nsSecretDecoderRing service
+    get _decoderRing() {
+        if (!this.__decoderRing)
+            this.__decoderRing = Cc["@mozilla.org/security/sdr;1"].
+                                 getService(Ci.nsISecretDecoderRing);
+        return this.__decoderRing;
+    },
+
+    __utfConverter : null, // UCS2 <--> UTF8 string conversion
+    get _utfConverter() {
+        if (!this.__utfConverter) {
+            this.__utfConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
+                                  createInstance(Ci.nsIScriptableUnicodeConverter);
+            this.__utfConverter.charset = "UTF-8";
+        }
+        return this.__utfConverter;
+    },
+
+    _utfConverterReset : function() {
+        this.__utfConverter = null;
+    },
+
+    __observerService : null,
+    get _observerService() {
+        if (!this.__observerService)
+            this.__observerService = Cc["@mozilla.org/observer-service;1"].
+                                     getService(Ci.nsIObserverService);
+        return this.__observerService;
+    },
+
+    _debug : false, // mirrors signon.debug
+
+
+    /*
+     * log
+     *
+     * Internal function for logging debug messages to the Error Console.
+     */
+    log : function (message) {
+        if (!this._debug)
+            return;
+        dump("PwMgr cryptoSDR: " + message + "\n");
+        this._logService.logStringMessage("PwMgr cryptoSDR: " + message);
+    },
+
+
+    init : function () {
+        // Connect to the correct preferences branch.
+        this._prefBranch = Cc["@mozilla.org/preferences-service;1"].
+                           getService(Ci.nsIPrefService);
+        this._prefBranch = this._prefBranch.getBranch("signon.");
+        this._prefBranch.QueryInterface(Ci.nsIPrefBranch2);
+
+        this._debug = this._prefBranch.getBoolPref("debug");
+
+        // Check to see if the internal PKCS#11 token has been initialized.
+        // If not, set a blank password.
+        let tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"].
+                      getService(Ci.nsIPK11TokenDB);
+
+        let token = tokenDB.getInternalKeyToken();
+        if (token.needsUserInit) {
+            this.log("Initializing key3.db with default blank password.");
+            token.initPassword("");
+        }
+    },
+
+
+    /*
+     * encrypt
+     *
+     * Encrypts the specified string, using the SecretDecoderRing.
+     *
+     * Returns the encrypted string, or throws an exception if there was a
+     * problem.
+     */
+    encrypt : function (plainText) {
+        let cipherText = null;
+
+        try {
+            let plainOctet = this._utfConverter.ConvertFromUnicode(plainText);
+            plainOctet += this._utfConverter.Finish();
+            cipherText = this._decoderRing.encryptString(plainOctet);
+        } catch (e) {
+            this.log("Failed to encrypt string. (" + e.name + ")");
+            // If the user clicks Cancel, we get NS_ERROR_FAILURE.
+            // (unlike decrypting, which gets NS_ERROR_NOT_AVAILABLE).
+            if (e.result == Cr.NS_ERROR_FAILURE)
+                throw Components.Exception("User canceled master password entry", Cr.NS_ERROR_ABORT);
+            else
+                throw Components.Exception("Couldn't encrypt string", Cr.NS_ERROR_FAILURE);
+        }
+        return cipherText;
+    },
+
+
+    /*
+     * decrypt
+     *
+     * Decrypts the specified string, using the SecretDecoderRing.
+     *
+     * Returns the decrypted string, or throws an exception if there was a
+     * problem.
+     */
+    decrypt : function (cipherText) {
+        let plainText = null;
+
+        try {
+            let plainOctet;
+            if (cipherText.charAt(0) == '~') {
+                // The old Wallet file format obscured entries by
+                // base64-encoding them. These entries are signaled by a
+                // leading '~' character.
+                plainOctet = atob(cipherText.substring(1));
+            } else {
+                plainOctet = this._decoderRing.decryptString(cipherText);
+            }
+            plainText = this._utfConverter.ConvertToUnicode(plainOctet);
+        } catch (e) {
+            this.log("Failed to decrypt string: " + cipherText +
+                " (" + e.name + ")");
+
+            // In the unlikely event the converter threw, reset it.
+            this._utfConverterReset();
+
+            // If the user clicks Cancel, we get NS_ERROR_NOT_AVAILABLE.
+            // If the cipherText is bad / wrong key, we get NS_ERROR_FAILURE
+            // Wrong passwords are handled by the decoderRing reprompting;
+            // we get no notification.
+            if (e.result == Cr.NS_ERROR_NOT_AVAILABLE)
+                throw Components.Exception("User canceled master password entry", Cr.NS_ERROR_ABORT);
+            else
+                throw Components.Exception("Couldn't decrypt string", Cr.NS_ERROR_FAILURE);
+        }
+
+        return plainText;
+    }
+}; // end of nsLoginManagerCrypto_SDR implementation
+
+let component = [LoginManagerCrypto_SDR];
+function NSGetModule(compMgr, fileSpec) {
+    return XPCOMUtils.generateModule(component);
+}
--- a/toolkit/components/passwordmgr/src/storage-mozStorage.js
+++ b/toolkit/components/passwordmgr/src/storage-mozStorage.js
@@ -35,16 +35,17 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
+const Cr = Components.results;
 
 const DB_VERSION = 3; // The database schema version
 
 const ENCTYPE_BASE64 = 0;
 const ENCTYPE_SDR = 1;
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
@@ -60,36 +61,22 @@ LoginManagerStorage_mozStorage.prototype
     __logService : null, // Console logging service, used for debugging.
     get _logService() {
         if (!this.__logService)
             this.__logService = Cc["@mozilla.org/consoleservice;1"].
                                 getService(Ci.nsIConsoleService);
         return this.__logService;
     },
 
-    __decoderRing : null,  // nsSecretDecoderRing service
-    get _decoderRing() {
-        if (!this.__decoderRing)
-            this.__decoderRing = Cc["@mozilla.org/security/sdr;1"].
-                                 getService(Ci.nsISecretDecoderRing);
-        return this.__decoderRing;
-    },
-
-    __utfConverter : null, // UCS2 <--> UTF8 string conversion
-    get _utfConverter() {
-        if (!this.__utfConverter) {
-            this.__utfConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
-                                  createInstance(Ci.nsIScriptableUnicodeConverter);
-            this.__utfConverter.charset = "UTF-8";
-        }
-        return this.__utfConverter;
-    },
-
-    _utfConverterReset : function() {
-        this.__utfConverter = null;
+    __crypto : null,  // nsILoginManagerCrypto service
+    get _crypto() {
+        if (!this.__crypto)
+            this.__crypto = Cc["@mozilla.org/login-manager/crypto/SDR;1"].
+                            getService(Ci.nsILoginManagerCrypto);
+        return this.__crypto;
     },
 
     __profileDir: null,  // nsIFile for the user's profile dir
     get _profileDir() {
         if (!this.__profileDir)
             this.__profileDir = Cc["@mozilla.org/file/directory_service;1"].
                                 getService(Ci.nsIProperties).
                                 get("ProfD", Ci.nsIFile);
@@ -214,27 +201,16 @@ LoginManagerStorage_mozStorage.prototype
         // Connect to the correct preferences branch.
         this._prefBranch = Cc["@mozilla.org/preferences-service;1"].
                            getService(Ci.nsIPrefService);
         this._prefBranch = this._prefBranch.getBranch("signon.");
         this._prefBranch.QueryInterface(Ci.nsIPrefBranch2);
 
         this._debug = this._prefBranch.getBoolPref("debug");
 
-        // Check to see if the internal PKCS#11 token has been initialized.
-        // If not, set a blank password.
-        let tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"].
-                      getService(Ci.nsIPK11TokenDB);
-
-        let token = tokenDB.getInternalKeyToken();
-        if (token.needsUserInit) {
-            this.log("Initializing key3.db with default blank password.");
-            token.initPassword("");
-        }
-
         let isFirstRun;
         try {
             // If initWithFile is calling us, _signonsFile may already be set.
             if (!this._signonsFile) {
                 // Initialize signons.sqlite
                 this._signonsFile = this._profileDir.clone();
                 this._signonsFile.append("signons.sqlite");
             }
@@ -271,29 +247,25 @@ LoginManagerStorage_mozStorage.prototype
 
 
     /*
      * _addLogin
      *
      * Private function wrapping core addLogin functionality.
      */
     _addLogin : function (login, isEncrypted) {
-        let userCanceled, encUsername, encPassword;
+        let encUsername, encPassword;
 
         // Throws if there are bogus values.
         this._checkLoginValues(login);
 
-        if (isEncrypted) {
+        if (isEncrypted)
             [encUsername, encPassword] = [login.username, login.password];
-        } else {
-            // Get the encrypted value of the username and password.
-            [encUsername, encPassword, userCanceled] = this._encryptLogin(login);
-            if (userCanceled)
-                throw "User canceled master password entry, login not added.";
-        }
+        else
+            [encUsername, encPassword] = this._encryptLogin(login);
 
         // Clone the login, so we don't modify the caller's object.
         let loginClone = login.clone();
 
         // Initialize the nsILoginMetaInfo fields, unless the caller gave us values
         loginClone.QueryInterface(Ci.nsILoginMetaInfo);
         if (loginClone.guid) {
             if (!this._isGuidUnique(loginClone.guid))
@@ -428,19 +400,17 @@ LoginManagerStorage_mozStorage.prototype
         } else {
             throw "newLoginData needs an expected interface!";
         }
 
         // Throws if there are bogus values.
         this._checkLoginValues(newLogin);
 
         // Get the encrypted value of the username and password.
-        let [encUsername, encPassword, userCanceled] = this._encryptLogin(newLogin);
-        if (userCanceled)
-            throw "User canceled master password entry, login not modified.";
+        let [encUsername, encPassword] = this._encryptLogin(newLogin);
 
         let query =
             "UPDATE moz_logins " +
             "SET hostname = :hostname, " +
                 "httpRealm = :httpRealm, " +
                 "formSubmitURL = :formSubmitURL, " +
                 "usernameField = :usernameField, " +
                 "passwordField = :passwordField, " +
@@ -479,24 +449,20 @@ LoginManagerStorage_mozStorage.prototype
 
 
     /*
      * getAllLogins
      *
      * Returns an array of nsILoginInfo.
      */
     getAllLogins : function (count) {
-        let userCanceled;
         let [logins, ids] = this._searchLogins({});
 
         // decrypt entries for caller.
-        [logins, userCanceled] = this._decryptLogins(logins);
-
-        if (userCanceled)
-            throw "User canceled Master Password entry";
+        logins = this._decryptLogins(logins);
 
         this.log("_getAllLogins: returning " + logins.length + " logins.");
         if (count)
             count.value = logins.length; // needed for XPCOM
         return logins;
     },
 
 
@@ -504,17 +470,17 @@ LoginManagerStorage_mozStorage.prototype
      * getAllEncryptedLogins
      *
      * Not implemented. This interface was added to extract logins from the
      * legacy storage module without decrypting them. Now that logins are in
      * mozStorage, if the encrypted data is really needed it can be easily
      * obtained with SQL and the mozStorage APIs.
      */
     getAllEncryptedLogins : function (count) {
-        throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+        throw Cr.NS_ERROR_NOT_IMPLEMENTED;
     },
 
 
     /*
      * searchLogins
      *
      * Public wrapper around _searchLogins to convert the nsIPropertyBag to a
      * JavaScript object and decrypt the results.
@@ -527,22 +493,18 @@ LoginManagerStorage_mozStorage.prototype
         let propEnum = matchData.enumerator;
         while (propEnum.hasMoreElements()) {
             let prop = propEnum.getNext().QueryInterface(Ci.nsIProperty);
             realMatchData[prop.name] = prop.value;
         }
 
         let [logins, ids] = this._searchLogins(realMatchData);
 
-        let userCanceled;
         // Decrypt entries found for the caller.
-        [logins, userCanceled] = this._decryptLogins(logins);
-
-        if (userCanceled)
-        throw "User canceled Master Password entry";
+        logins = this._decryptLogins(logins);
 
         count.value = logins.length; // needed for XPCOM
         return logins;
     },
 
 
     /*
      * _searchLogins
@@ -711,36 +673,29 @@ LoginManagerStorage_mozStorage.prototype
     },
 
 
     /*
      * findLogins
      *
      */
     findLogins : function (count, hostname, formSubmitURL, httpRealm) {
-        let userCanceled;
         let loginData = {
             hostname: hostname,
             formSubmitURL: formSubmitURL,
             httpRealm: httpRealm
         };
         let matchData = { };
         for each (field in ["hostname", "formSubmitURL", "httpRealm"])
           if (loginData[field] != '')
               matchData[field] = loginData[field];
         let [logins, ids] = this._searchLogins(matchData);
 
         // Decrypt entries found for the caller.
-        [logins, userCanceled] = this._decryptLogins(logins);
-
-        // We want to throw in this case, so that the Login Manager
-        // knows to stop processing forms on the page so the user isn't
-        // prompted multiple times.
-        if (userCanceled)
-            throw "User canceled Master Password entry";
+        logins = this._decryptLogins(logins);
 
         this.log("_findLogins: returning " + logins.length + " logins");
         count.value = logins.length; // needed for XPCOM
         return logins;
     },
 
 
     /*
@@ -813,21 +768,17 @@ LoginManagerStorage_mozStorage.prototype
         let id = null;
         let foundLogin = null;
 
         // The specified login isn't encrypted, so we need to ensure
         // the logins we're comparing with are decrypted. We decrypt one entry
         // at a time, lest _decryptLogins return fewer entries and screw up
         // indices between the two.
         for (let i = 0; i < logins.length; i++) {
-            let [[decryptedLogin], userCanceled] =
-                        this._decryptLogins([logins[i]]);
-
-            if (userCanceled)
-                throw "User canceled master password entry.";
+            let [decryptedLogin] = this._decryptLogins([logins[i]]);
 
             if (!decryptedLogin || !decryptedLogin.equals(login))
                 continue;
 
             // We've found a match, set id and break
             foundLogin = decryptedLogin;
             id = ids[i];
             break;
@@ -1065,30 +1016,23 @@ LoginManagerStorage_mozStorage.prototype
     /*
      * _encryptLogin
      *
      * Returns the encrypted username and password for the specified login,
      * and a boolean indicating if the user canceled the master password entry
      * (in which case no encrypted values are returned).
      */
     _encryptLogin : function (login) {
-        let encUsername, encPassword, userCanceled;
-        [encUsername, userCanceled] = this._encrypt(login.username);
-        if (userCanceled)
-            return [null, null, true];
-
-        [encPassword, userCanceled] = this._encrypt(login.password);
-        // Probably can't hit this case, but for completeness...
-        if (userCanceled)
-            return [null, null, true];
+        let encUsername = this._crypto.encrypt(login.username);
+        let encPassword = this._crypto.encrypt(login.password);
 
         if (!this._base64checked)
             this._reencryptBase64Logins();
 
-        return [encUsername, encPassword, false];
+        return [encUsername, encPassword];
     },
 
 
     /*
      * _decryptLogins
      *
      * Decrypts username and password fields in the provided array of
      * logins.
@@ -1096,47 +1040,36 @@ LoginManagerStorage_mozStorage.prototype
      * The entries specified by the array will be decrypted, if possible.
      * An array of successfully decrypted logins will be returned. The return
      * value should be given to external callers (since still-encrypted
      * entries are useless), whereas internal callers generally don't want
      * to lose unencrypted entries (eg, because the user clicked Cancel
      * instead of entering their master password)
      */
     _decryptLogins : function (logins) {
-        let result = [], userCanceled = false;
+        let result = [];
 
         for each (let login in logins) {
-            let decryptedUsername, decryptedPassword;
-
-            [decryptedUsername, userCanceled] = this._decrypt(login.username);
-
-            if (userCanceled)
-                break;
-
-            [decryptedPassword, userCanceled] = this._decrypt(login.password);
-
-            // Probably can't hit this case, but for completeness...
-            if (userCanceled)
-                break;
-
-            // If decryption failed (corrupt entry?) skip it.
-            // Note that we allow password-only logins, so username can be "".
-            if (decryptedUsername == null || !decryptedPassword)
-                continue;
-
-            login.username = decryptedUsername;
-            login.password = decryptedPassword;
-
+            try {
+                login.username = this._crypto.decrypt(login.username);
+                login.password = this._crypto.decrypt(login.password);
+            } catch (e) {
+                // If decryption failed (corrupt entry?), just skip it.
+                // Rethrow other errors (like canceling entry of a master pw)
+                if (e.result == Cr.NS_ERROR_FAILURE)
+                    continue;
+                throw e;
+            }
             result.push(login);
         }
 
-        if (!this._base64checked && !userCanceled)
+        if (!this._base64checked)
             this._reencryptBase64Logins();
 
-        return [result, userCanceled];
+        return result;
     },
 
 
     /*
      * _reencryptBase64Logins
      *
      * Checks the signons DB for any logins using the old wallet-style base64
      * obscuring of the username/password, instead of proper encryption. We're
@@ -1151,26 +1084,27 @@ LoginManagerStorage_mozStorage.prototype
         this.log("Reencrypting Base64 logins");
         this._dbConnection.beginTransaction();
         try {
             let [logins, ids] = this._searchLogins({ encType: ENCTYPE_BASE64 });
 
             if (!logins.length)
                 return;
 
-            let userCancelled;
-            [logins, userCanceled] = this._decryptLogins(logins);
-            if (userCanceled)
+            try {
+                logins = this._decryptLogins(logins);
+            } catch (e) {
+                // User might have canceled master password entry, just ignore.
                 return;
+            }
 
             let encUsername, encPassword, stmt;
             for each (let login in logins) {
-                [encUsername, encPassword, userCanceled] = this._encryptLogin(login);
-                if (userCanceled)
-                    throw "User canceled master password entry, login not modified.";
+                [encUsername, encPassword] = this._encryptLogin(login);
+
                 let query =
                     "UPDATE moz_logins " +
                     "SET encryptedUsername = :encryptedUsername, " +
                         "encryptedPassword = :encryptedPassword, " +
                         "encType = :encType " +
                     "WHERE guid = :guid";
                 let params = {
                     encryptedUsername: encUsername,
@@ -1191,96 +1125,16 @@ LoginManagerStorage_mozStorage.prototype
         } catch (e) {
             this.log("_reencryptBase64Logins failed: " + e);
         } finally {
             this._dbConnection.commitTransaction();
         }
     },
 
 
-    /*
-     * _encrypt
-     *
-     * Encrypts the specified string, using the SecretDecoderRing.
-     *
-     * Returns [cipherText, userCanceled] where:
-     *  cipherText   -- the encrypted string, or null if it failed.
-     *  userCanceled -- if the encryption failed, this is true if the
-     *                  user selected Cancel when prompted to enter their
-     *                  Master Password. The caller should bail out, and not
-     *                  not request that more things be encrypted (which
-     *                  results in prompting the user for a Master Password
-     *                  over and over.)
-     */
-    _encrypt : function (plainText) {
-        let cipherText = null, userCanceled = false;
-
-        try {
-            let plainOctet = this._utfConverter.ConvertFromUnicode(plainText);
-            plainOctet += this._utfConverter.Finish();
-            cipherText = this._decoderRing.encryptString(plainOctet);
-        } catch (e) {
-            this.log("Failed to encrypt string. (" + e.name + ")");
-            // If the user clicks Cancel, we get NS_ERROR_FAILURE.
-            // (unlike decrypting, which gets NS_ERROR_NOT_AVAILABLE).
-            if (e.result == Components.results.NS_ERROR_FAILURE)
-                userCanceled = true;
-        }
-
-        return [cipherText, userCanceled];
-    },
-
-
-    /*
-     * _decrypt
-     *
-     * Decrypts the specified string, using the SecretDecoderRing.
-     *
-     * Returns [plainText, userCanceled] where:
-     *  plainText    -- the decrypted string, or null if it failed.
-     *  userCanceled -- if the decryption failed, this is true if the
-     *                  user selected Cancel when prompted to enter their
-     *                  Master Password. The caller should bail out, and not
-     *                  not request that more things be decrypted (which
-     *                  results in prompting the user for a Master Password
-     *                  over and over.)
-     */
-    _decrypt : function (cipherText) {
-        let plainText = null, userCanceled = false;
-
-        try {
-            let plainOctet;
-            if (cipherText.charAt(0) == '~') {
-                // The old Wallet file format obscured entries by
-                // base64-encoding them. These entries are signaled by a
-                // leading '~' character.
-                plainOctet = atob(cipherText.substring(1));
-            } else {
-                plainOctet = this._decoderRing.decryptString(cipherText);
-            }
-            plainText = this._utfConverter.ConvertToUnicode(plainOctet);
-        } catch (e) {
-            this.log("Failed to decrypt string: " + cipherText +
-                " (" + e.name + ")");
-
-            // In the unlikely event the converter threw, reset it.
-            this._utfConverterReset();
-
-            // If the user clicks Cancel, we get NS_ERROR_NOT_AVAILABLE.
-            // If the cipherText is bad / wrong key, we get NS_ERROR_FAILURE
-            // Wrong passwords are handled by the decoderRing reprompting;
-            // we get no notification.
-            if (e.result == Components.results.NS_ERROR_NOT_AVAILABLE)
-                userCanceled = true;
-        }
-
-        return [plainText, userCanceled];
-    },
-
-
     //**************************************************************************//
     // Database Creation & Access
 
     /*
      * _dbCreateStatement
      *
      * Creates a statement, wraps it, and then does parameter replacement
      * Returns the wrapped statement for execution.  Will use memoization
@@ -1318,17 +1172,17 @@ LoginManagerStorage_mozStorage.prototype
             // database has not been created yet.
             let version = this._dbConnection.schemaVersion;
             if (version == 0) {
                 this._dbCreate();
                 isFirstRun = true;
             } else if (version != DB_VERSION) {
                 this._dbMigrate(version);
             }
-        } catch (e if e.result == Components.results.NS_ERROR_FILE_CORRUPTED) {
+        } catch (e if e.result == Cr.NS_ERROR_FILE_CORRUPTED) {
             // Database is corrupted, so we backup the database, then throw
             // causing initialization to fail and a new db to be created next use
             this._dbCleanup(true);
             throw e;
         }
         return isFirstRun;
     },
 
@@ -1372,17 +1226,17 @@ LoginManagerStorage_mozStorage.prototype
             // User's DB is newer. Sanity check that our expected columns are
             // present, and if so mark the lower version and merrily continue
             // on. If the columns are borked, something is wrong so blow away
             // the DB and start from scratch. [Future incompatible upgrades
             // should swtich to a different table or file.]
 
             if (!this._dbAreExpectedColumnsPresent())
                 throw Components.Exception("DB is missing expected columns",
-                                           Components.results.NS_ERROR_FILE_CORRUPTED);
+                                           Cr.NS_ERROR_FILE_CORRUPTED);
 
             // Change the stored version to the current version. If the user
             // runs the newer code again, it will see the lower version number
             // and re-upgrade (to fixup any entries the old code added).
             this._dbConnection.schemaVersion = DB_VERSION;
             return;
         }