gecko-dev/toolkit/components/passwordmgr/OSCrypto_win.js

246 строки
9.4 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";
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();
},
};