/* 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(); }, };