diff options
-rw-r--r-- | browser/components/migration/ChromeProfileMigrator.js | 4 | ||||
-rw-r--r-- | toolkit/components/passwordmgr/OSCrypto_win.js | 186 |
2 files changed, 147 insertions, 43 deletions
diff --git a/browser/components/migration/ChromeProfileMigrator.js b/browser/components/migration/ChromeProfileMigrator.js index 1e399c127..ef2730b51 100644 --- a/browser/components/migration/ChromeProfileMigrator.js +++ b/browser/components/migration/ChromeProfileMigrator.js @@ -366,7 +366,9 @@ function GetWindowsPasswordsResource(aProfileFolder) { _rowToLoginInfo(row) { let loginInfo = { username: row.getResultByName("username_value"), - password: crypto.decryptData(row.getResultByName("password_value")), + password: crypto. + decryptData(crypto.arrayToString(row.getResultByName("password_value")), + null), hostName: NetUtil.newURI(row.getResultByName("origin_url")).prePath, submitURL: null, httpRealm: null, diff --git a/toolkit/components/passwordmgr/OSCrypto_win.js b/toolkit/components/passwordmgr/OSCrypto_win.js index 61d4deb87..2bc206a85 100644 --- a/toolkit/components/passwordmgr/OSCrypto_win.js +++ b/toolkit/components/passwordmgr/OSCrypto_win.js @@ -11,14 +11,27 @@ 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: ctypes.uint32_t}, - {pbData: ctypes.uint8_t.ptr} + {cbData: wintypes.DWORD}, + {pbData: wintypes.PVOID} ]); try { @@ -29,31 +42,30 @@ function OSCrypto() { this._functions.set("CryptProtectData", this._libs.get("crypt32").declare("CryptProtectData", ctypes.winapi_abi, - ctypes.uint32_t, + wintypes.DWORD, this._structs.DATA_BLOB.ptr, - ctypes.voidptr_t, - ctypes.voidptr_t, - ctypes.voidptr_t, - ctypes.voidptr_t, - ctypes.uint32_t, + 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, - ctypes.uint32_t, + wintypes.DWORD, this._structs.DATA_BLOB.ptr, - ctypes.voidptr_t, - ctypes.voidptr_t, - ctypes.voidptr_t, - ctypes.voidptr_t, - ctypes.uint32_t, + 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, - ctypes.uint32_t, - ctypes.voidptr_t)); + wintypes.DWORD, + wintypes.PVOID)); } catch (ex) { Cu.reportError(ex); this.finalize(); @@ -62,18 +74,101 @@ function OSCrypto() { } OSCrypto.prototype = { /** - * Decrypt an array of numbers using the windows CryptUnprotectData API. - * @param {number[]} array - the encrypted array that needs to be decrypted. - * @returns {string} the decryption of the array. + * 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. */ - decryptData(array) { + 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 = [toHexString(hash.charCodeAt(i)) for (i in hash)].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 = ctypes.uint8_t.array(array.length)(array); + 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, - null, null, null, 0, - outData.address()); + entropyParam, + null, null, FLAGS_NOT_SET, + outData.address()); if (status === 0) { throw new Error("decryptData failed: " + status); } @@ -81,32 +176,43 @@ OSCrypto.prototype = { // convert byte array to JS string. let len = outData.cbData; let decrypted = ctypes.cast(outData.pbData, - ctypes.uint8_t.array(len).ptr).contents; + 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} string - the string that is going to be encrypted. - * @returns {number[]} the encrypted string encoded as an array of numbers. + * @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(string) { - let encryptedData = []; - let decryptedData = ctypes.uint8_t.array(string.length)(); + encryptData(data, entropy = null) { + let encryptedData = ""; + let decryptedData = wintypes.BYTE.array(data.length)(this.stringToArray(data)); - for (let i = 0; i < string.length; i++) { - decryptedData[i] = string.charCodeAt(i); + 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 inData = new this._structs.DATA_BLOB(string.length, decryptedData); - let outData = new this._structs.DATA_BLOB(); let status = this._functions.get("CryptProtectData")(inData.address(), null, - null, null, null, 0, + entropyParam, + null, null, FLAGS_NOT_SET, outData.address()); if (status === 0) { throw new Error("encryptData failed: " + status); @@ -115,12 +221,8 @@ OSCrypto.prototype = { // convert byte array to JS string. let len = outData.cbData; let encrypted = ctypes.cast(outData.pbData, - ctypes.uint8_t.array(len).ptr).contents; - - for (let i = 0; i < len; i++) { - encryptedData.push(encrypted[i]); - } - + wintypes.BYTE.array(len).ptr).contents; + encryptedData = this.arrayToString(encrypted); this._functions.get("LocalFree")(outData.pbData); return encryptedData; }, |