summaryrefslogtreecommitdiffstats
path: root/toolkit/components/passwordmgr/OSCrypto_win.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/passwordmgr/OSCrypto_win.js')
-rw-r--r--toolkit/components/passwordmgr/OSCrypto_win.js245
1 files changed, 245 insertions, 0 deletions
diff --git a/toolkit/components/passwordmgr/OSCrypto_win.js b/toolkit/components/passwordmgr/OSCrypto_win.js
new file mode 100644
index 000000000..0f52f4269
--- /dev/null
+++ b/toolkit/components/passwordmgr/OSCrypto_win.js
@@ -0,0 +1,245 @@
+/* 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/. */
+
+"use strict";
+
+var { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "ctypes", "resource://gre/modules/ctypes.jsm");
+
+const FLAGS_NOT_SET = 0;
+
+const wintypes = {
+ BOOL: ctypes.bool,
+ BYTE: ctypes.uint8_t,
+ DWORD: ctypes.uint32_t,
+ PBYTE: ctypes.unsigned_char.ptr,
+ PCHAR: ctypes.char.ptr,
+ PDWORD: ctypes.uint32_t.ptr,
+ PVOID: ctypes.voidptr_t,
+ WORD: ctypes.uint16_t,
+};
+
+function OSCrypto() {
+ this._structs = {};
+ this._functions = new Map();
+ this._libs = new Map();
+ this._structs.DATA_BLOB = new ctypes.StructType("DATA_BLOB",
+ [
+ {cbData: wintypes.DWORD},
+ {pbData: wintypes.PVOID}
+ ]);
+
+ try {
+
+ this._libs.set("crypt32", ctypes.open("Crypt32"));
+ this._libs.set("kernel32", ctypes.open("Kernel32"));
+
+ this._functions.set("CryptProtectData",
+ this._libs.get("crypt32").declare("CryptProtectData",
+ ctypes.winapi_abi,
+ wintypes.DWORD,
+ this._structs.DATA_BLOB.ptr,
+ wintypes.PVOID,
+ wintypes.PVOID,
+ wintypes.PVOID,
+ wintypes.PVOID,
+ wintypes.DWORD,
+ this._structs.DATA_BLOB.ptr));
+ this._functions.set("CryptUnprotectData",
+ this._libs.get("crypt32").declare("CryptUnprotectData",
+ ctypes.winapi_abi,
+ wintypes.DWORD,
+ this._structs.DATA_BLOB.ptr,
+ wintypes.PVOID,
+ wintypes.PVOID,
+ wintypes.PVOID,
+ wintypes.PVOID,
+ wintypes.DWORD,
+ this._structs.DATA_BLOB.ptr));
+ this._functions.set("LocalFree",
+ this._libs.get("kernel32").declare("LocalFree",
+ ctypes.winapi_abi,
+ wintypes.DWORD,
+ wintypes.PVOID));
+ } catch (ex) {
+ Cu.reportError(ex);
+ this.finalize();
+ throw ex;
+ }
+}
+OSCrypto.prototype = {
+ /**
+ * Convert an array containing only two bytes unsigned numbers to a string.
+ * @param {number[]} arr - the array that needs to be converted.
+ * @returns {string} the string representation of the array.
+ */
+ arrayToString(arr) {
+ let str = "";
+ for (let i = 0; i < arr.length; i++) {
+ str += String.fromCharCode(arr[i]);
+ }
+ return str;
+ },
+
+ /**
+ * Convert a string to an array.
+ * @param {string} str - the string that needs to be converted.
+ * @returns {number[]} the array representation of the string.
+ */
+ stringToArray(str) {
+ let arr = [];
+ for (let i = 0; i < str.length; i++) {
+ arr.push(str.charCodeAt(i));
+ }
+ return arr;
+ },
+
+ /**
+ * Calculate the hash value used by IE as the name of the registry value where login details are
+ * stored.
+ * @param {string} data - the string value that needs to be hashed.
+ * @returns {string} the hash value of the string.
+ */
+ getIELoginHash(data) {
+ // return the two-digit hexadecimal code for a byte
+ function toHexString(charCode) {
+ return ("00" + charCode.toString(16)).slice(-2);
+ }
+
+ // the data needs to be encoded in null terminated UTF-16
+ data += "\0";
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
+ createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-16";
+ // result is an out parameter,
+ // result.value will contain the array length
+ let result = {};
+ // dataArray is an array of bytes
+ let dataArray = converter.convertToByteArray(data, result);
+ // calculation of SHA1 hash value
+ let cryptoHash = Cc["@mozilla.org/security/hash;1"].
+ createInstance(Ci.nsICryptoHash);
+ cryptoHash.init(cryptoHash.SHA1);
+ cryptoHash.update(dataArray, dataArray.length);
+ let hash = cryptoHash.finish(false);
+
+ let tail = 0; // variable to calculate value for the last 2 bytes
+ // convert to a character string in hexadecimal notation
+ for (let c of hash) {
+ tail += c.charCodeAt(0);
+ }
+ hash += String.fromCharCode(tail % 256);
+
+ // convert the binary hash data to a hex string.
+ let hashStr = Array.from(hash, (c, i) => toHexString(hash.charCodeAt(i))).join("");
+ return hashStr.toUpperCase();
+ },
+
+ /**
+ * Decrypt a string using the windows CryptUnprotectData API.
+ * @param {string} data - the encrypted string that needs to be decrypted.
+ * @param {?string} entropy - the entropy value of the decryption (could be null). Its value must
+ * be the same as the one used when the data was encrypted.
+ * @returns {string} the decryption of the string.
+ */
+ decryptData(data, entropy = null) {
+ let array = this.stringToArray(data);
+ let decryptedData = "";
+ let encryptedData = wintypes.BYTE.array(array.length)(array);
+ let inData = new this._structs.DATA_BLOB(encryptedData.length, encryptedData);
+ let outData = new this._structs.DATA_BLOB();
+ let entropyParam;
+ if (entropy) {
+ let entropyArray = this.stringToArray(entropy);
+ entropyArray.push(0);
+ let entropyData = wintypes.WORD.array(entropyArray.length)(entropyArray);
+ let optionalEntropy = new this._structs.DATA_BLOB(entropyData.length * 2,
+ entropyData);
+ entropyParam = optionalEntropy.address();
+ } else {
+ entropyParam = null;
+ }
+
+ let status = this._functions.get("CryptUnprotectData")(inData.address(), null,
+ entropyParam,
+ null, null, FLAGS_NOT_SET,
+ outData.address());
+ if (status === 0) {
+ throw new Error("decryptData failed: " + status);
+ }
+
+ // convert byte array to JS string.
+ let len = outData.cbData;
+ let decrypted = ctypes.cast(outData.pbData,
+ wintypes.BYTE.array(len).ptr).contents;
+ for (let i = 0; i < decrypted.length; i++) {
+ decryptedData += String.fromCharCode(decrypted[i]);
+ }
+
+ this._functions.get("LocalFree")(outData.pbData);
+ return decryptedData;
+ },
+
+ /**
+ * Encrypt a string using the windows CryptProtectData API.
+ * @param {string} data - the string that is going to be encrypted.
+ * @param {?string} entropy - the entropy value of the encryption (could be null). Its value must
+ * be the same as the one that is going to be used for the decryption.
+ * @returns {string} the encrypted string.
+ */
+ encryptData(data, entropy = null) {
+ let encryptedData = "";
+ let decryptedData = wintypes.BYTE.array(data.length)(this.stringToArray(data));
+
+ let inData = new this._structs.DATA_BLOB(data.length, decryptedData);
+ let outData = new this._structs.DATA_BLOB();
+ let entropyParam;
+ if (!entropy) {
+ entropyParam = null;
+ } else {
+ let entropyArray = this.stringToArray(entropy);
+ entropyArray.push(0);
+ let entropyData = wintypes.WORD.array(entropyArray.length)(entropyArray);
+ let optionalEntropy = new this._structs.DATA_BLOB(entropyData.length * 2,
+ entropyData);
+ entropyParam = optionalEntropy.address();
+ }
+
+ let status = this._functions.get("CryptProtectData")(inData.address(), null,
+ entropyParam,
+ null, null, FLAGS_NOT_SET,
+ outData.address());
+ if (status === 0) {
+ throw new Error("encryptData failed: " + status);
+ }
+
+ // convert byte array to JS string.
+ let len = outData.cbData;
+ let encrypted = ctypes.cast(outData.pbData,
+ wintypes.BYTE.array(len).ptr).contents;
+ encryptedData = this.arrayToString(encrypted);
+ this._functions.get("LocalFree")(outData.pbData);
+ return encryptedData;
+ },
+
+ /**
+ * Must be invoked once after last use of any of the provided helpers.
+ */
+ finalize() {
+ this._structs = {};
+ this._functions.clear();
+ for (let lib of this._libs.values()) {
+ try {
+ lib.close();
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+ this._libs.clear();
+ },
+};