зеркало из https://github.com/mozilla/gecko-dev.git
247 строки
9.3 KiB
JavaScript
247 строки
9.3 KiB
JavaScript
/* 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";
|
|
|
|
ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
ChromeUtils.defineModuleGetter(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";
|
|
|
|
// dataArray is an array of bytes
|
|
let dataArray = new Array(data.length * 2);
|
|
for (let i = 0; i < data.length; i++) {
|
|
let c = data.charCodeAt(i);
|
|
let lo = c & 0xFF;
|
|
let hi = (c & 0xFF00) >> 8;
|
|
dataArray[i * 2] = lo;
|
|
dataArray[i * 2 + 1] = hi;
|
|
}
|
|
|
|
// 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();
|
|
},
|
|
};
|