diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:22:09 +0000 |
commit | 43a97878ce14b72f0981164f87f2e35e14151312 (patch) | |
tree | 620249daf56c0258faa40cbdcf9cfba06de2a846 /toolkit/components/passwordmgr/crypto-SDR.js | |
parent | Initial commit. (diff) | |
download | firefox-upstream.tar.xz firefox-upstream.zip |
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | toolkit/components/passwordmgr/crypto-SDR.js | 315 |
1 files changed, 315 insertions, 0 deletions
diff --git a/toolkit/components/passwordmgr/crypto-SDR.js b/toolkit/components/passwordmgr/crypto-SDR.js new file mode 100644 index 0000000000..ca2869187f --- /dev/null +++ b/toolkit/components/passwordmgr/crypto-SDR.js @@ -0,0 +1,315 @@ +/* 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/. */ + +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +const lazy = {}; + +ChromeUtils.defineModuleGetter( + lazy, + "LoginHelper", + "resource://gre/modules/LoginHelper.jsm" +); + +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); +}); + +const EXPORTED_SYMBOLS = ["LoginManagerCrypto_SDR"]; |