summaryrefslogtreecommitdiffstats
path: root/toolkit/components/passwordmgr/crypto-SDR.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/passwordmgr/crypto-SDR.sys.mjs')
-rw-r--r--toolkit/components/passwordmgr/crypto-SDR.sys.mjs309
1 files changed, 309 insertions, 0 deletions
diff --git a/toolkit/components/passwordmgr/crypto-SDR.sys.mjs b/toolkit/components/passwordmgr/crypto-SDR.sys.mjs
new file mode 100644
index 0000000000..da3ffe9b58
--- /dev/null
+++ b/toolkit/components/passwordmgr/crypto-SDR.sys.mjs
@@ -0,0 +1,309 @@
+/* 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/. */
+
+import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
+});
+
+export function LoginManagerCrypto_SDR() {
+ this.init();
+}
+
+LoginManagerCrypto_SDR.prototype = {
+ classID: Components.ID("{dc6c2976-0f73-4f1f-b9ff-3d72b4e28309}"),
+ QueryInterface: ChromeUtils.generateQI(["nsILoginManagerCrypto"]),
+
+ __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() {
+ this.__utfConverter = null;
+ },
+
+ _uiBusy: false,
+
+ init() {
+ // 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(plainText) {
+ let cipherText = null;
+
+ let wasLoggedIn = this.isLoggedIn;
+ let canceledMP = false;
+
+ this._uiBusy = !wasLoggedIn;
+ try {
+ let plainOctet = this._utfConverter.ConvertFromUnicode(plainText);
+ plainOctet += this._utfConverter.Finish();
+ cipherText = this._decoderRing.encryptString(plainOctet);
+ } catch (e) {
+ this.log(`Failed to encrypt string with error ${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) {
+ canceledMP = true;
+ throw Components.Exception(
+ "User canceled primary password entry",
+ Cr.NS_ERROR_ABORT
+ );
+ } else {
+ throw Components.Exception(
+ "Couldn't encrypt string",
+ Cr.NS_ERROR_FAILURE
+ );
+ }
+ } finally {
+ this._uiBusy = false;
+ // If we triggered a primary password prompt, notify observers.
+ if (!wasLoggedIn && this.isLoggedIn) {
+ this._notifyObservers("passwordmgr-crypto-login");
+ } else if (canceledMP) {
+ this._notifyObservers("passwordmgr-crypto-loginCanceled");
+ }
+ }
+ return cipherText;
+ },
+
+ /*
+ * encryptMany
+ *
+ * Encrypts the specified strings, using the SecretDecoderRing.
+ *
+ * Returns a promise which resolves with the the encrypted strings,
+ * or throws/rejects with an error if there was a problem.
+ */
+ async encryptMany(plaintexts) {
+ if (!Array.isArray(plaintexts) || !plaintexts.length) {
+ throw Components.Exception(
+ "Need at least one plaintext to encrypt",
+ Cr.NS_ERROR_INVALID_ARG
+ );
+ }
+
+ let cipherTexts;
+
+ let wasLoggedIn = this.isLoggedIn;
+ let canceledMP = false;
+
+ this._uiBusy = !wasLoggedIn;
+ try {
+ cipherTexts = await this._decoderRing.asyncEncryptStrings(plaintexts);
+ } catch (e) {
+ this.log(`Failed to encrypt strings with error ${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) {
+ canceledMP = true;
+ throw Components.Exception(
+ "User canceled primary password entry",
+ Cr.NS_ERROR_ABORT
+ );
+ } else {
+ throw Components.Exception(
+ "Couldn't encrypt strings",
+ Cr.NS_ERROR_FAILURE
+ );
+ }
+ } finally {
+ this._uiBusy = false;
+ // If we triggered a primary password prompt, notify observers.
+ if (!wasLoggedIn && this.isLoggedIn) {
+ this._notifyObservers("passwordmgr-crypto-login");
+ } else if (canceledMP) {
+ this._notifyObservers("passwordmgr-crypto-loginCanceled");
+ }
+ }
+ return cipherTexts;
+ },
+
+ /*
+ * decrypt
+ *
+ * Decrypts the specified string, using the SecretDecoderRing.
+ *
+ * Returns the decrypted string, or throws an exception if there was a
+ * problem.
+ */
+ decrypt(cipherText) {
+ let plainText = null;
+
+ let wasLoggedIn = this.isLoggedIn;
+ let canceledMP = false;
+
+ this._uiBusy = !wasLoggedIn;
+ try {
+ let plainOctet;
+ plainOctet = this._decoderRing.decryptString(cipherText);
+ plainText = this._utfConverter.ConvertToUnicode(plainOctet);
+ } catch (e) {
+ this.log(
+ `Failed to decrypt cipher text of length ${cipherText.length} with error ${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) {
+ canceledMP = true;
+ throw Components.Exception(
+ "User canceled primary password entry",
+ Cr.NS_ERROR_ABORT
+ );
+ } else {
+ throw Components.Exception(
+ "Couldn't decrypt string",
+ Cr.NS_ERROR_FAILURE
+ );
+ }
+ } finally {
+ this._uiBusy = false;
+ // If we triggered a primary password prompt, notify observers.
+ if (!wasLoggedIn && this.isLoggedIn) {
+ this._notifyObservers("passwordmgr-crypto-login");
+ } else if (canceledMP) {
+ this._notifyObservers("passwordmgr-crypto-loginCanceled");
+ }
+ }
+
+ return plainText;
+ },
+
+ /**
+ * Decrypts the specified strings, using the SecretDecoderRing.
+ *
+ * @resolve {string[]} The decrypted strings. If a string cannot
+ * be decrypted, the empty string is returned for that instance.
+ * Callers will need to use decrypt() to determine if the encrypted
+ * string is invalid or intentionally empty. Throws/reject with
+ * an error if there was a problem.
+ */
+ async decryptMany(cipherTexts) {
+ if (!Array.isArray(cipherTexts) || !cipherTexts.length) {
+ throw Components.Exception(
+ "Need at least one ciphertext to decrypt",
+ Cr.NS_ERROR_INVALID_ARG
+ );
+ }
+
+ let plainTexts = [];
+
+ let wasLoggedIn = this.isLoggedIn;
+ let canceledMP = false;
+
+ this._uiBusy = !wasLoggedIn;
+ try {
+ plainTexts = await this._decoderRing.asyncDecryptStrings(cipherTexts);
+ } catch (e) {
+ this.log(`Failed to decrypt strings with error ${e.name}.`);
+ // 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) {
+ canceledMP = true;
+ throw Components.Exception(
+ "User canceled primary password entry",
+ Cr.NS_ERROR_ABORT
+ );
+ } else {
+ throw Components.Exception(
+ "Couldn't decrypt strings: " + e.result,
+ Cr.NS_ERROR_FAILURE
+ );
+ }
+ } finally {
+ this._uiBusy = false;
+ // If we triggered a primary password prompt, notify observers.
+ if (!wasLoggedIn && this.isLoggedIn) {
+ this._notifyObservers("passwordmgr-crypto-login");
+ } else if (canceledMP) {
+ this._notifyObservers("passwordmgr-crypto-loginCanceled");
+ }
+ }
+ return plainTexts;
+ },
+
+ /*
+ * uiBusy
+ */
+ get uiBusy() {
+ return this._uiBusy;
+ },
+
+ /*
+ * isLoggedIn
+ */
+ get isLoggedIn() {
+ let tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"].getService(
+ Ci.nsIPK11TokenDB
+ );
+ let token = tokenDB.getInternalKeyToken();
+ return !token.hasPassword || token.isLoggedIn();
+ },
+
+ /*
+ * defaultEncType
+ */
+ get defaultEncType() {
+ return Ci.nsILoginManagerCrypto.ENCTYPE_SDR;
+ },
+
+ /*
+ * _notifyObservers
+ */
+ _notifyObservers(topic) {
+ this.log(`Prompted for a primary password, notifying for ${topic}`);
+ Services.obs.notifyObservers(null, topic);
+ },
+}; // end of nsLoginManagerCrypto_SDR implementation
+
+XPCOMUtils.defineLazyGetter(LoginManagerCrypto_SDR.prototype, "log", () => {
+ let logger = lazy.LoginHelper.createLogger("Login crypto");
+ return logger.log.bind(logger);
+});