diff options
Diffstat (limited to 'toolkit/components/passwordmgr/OSCrypto_win.js')
-rw-r--r-- | toolkit/components/passwordmgr/OSCrypto_win.js | 245 |
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(); + }, +}; |