mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 682069 - Password Import from IE not available. r=dolske r=mattn
This commit is contained in:
parent
ecb2f187fc
commit
1436f2c0ee
@ -390,7 +390,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,
|
||||
|
@ -9,6 +9,7 @@ const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
const Cr = Components.results;
|
||||
|
||||
const kLoginsKey = "Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storage2";
|
||||
const kMainKey = "Software\\Microsoft\\Internet Explorer\\Main";
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
@ -17,11 +18,17 @@ Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource:///modules/MigrationUtils.jsm");
|
||||
Cu.import("resource:///modules/MSMigrationUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ctypes",
|
||||
"resource://gre/modules/ctypes.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
|
||||
"resource://gre/modules/PlacesUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "OSCrypto",
|
||||
"resource://gre/modules/OSCrypto.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
|
||||
"resource://gre/modules/WindowsRegistry.jsm");
|
||||
|
||||
Cu.importGlobalProperties(["URL"]);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Resources
|
||||
|
||||
@ -116,6 +123,270 @@ History.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
// IE password migrator supporting windows from XP until 7 and IE from 7 until 11
|
||||
function IE7FormPasswords () {
|
||||
}
|
||||
|
||||
IE7FormPasswords.prototype = {
|
||||
type: MigrationUtils.resourceTypes.PASSWORDS,
|
||||
|
||||
get exists() {
|
||||
try {
|
||||
let nsIWindowsRegKey = Ci.nsIWindowsRegKey;
|
||||
let key = Cc["@mozilla.org/windows-registry-key;1"].
|
||||
createInstance(nsIWindowsRegKey);
|
||||
key.open(nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, kLoginsKey,
|
||||
nsIWindowsRegKey.ACCESS_READ);
|
||||
let count = key.valueCount;
|
||||
key.close();
|
||||
return count > 0;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
migrate(aCallback) {
|
||||
let historyEnumerator = Cc["@mozilla.org/profile/migrator/iehistoryenumerator;1"].
|
||||
createInstance(Ci.nsISimpleEnumerator);
|
||||
let uris = []; // the uris of the websites that are going to be migrated
|
||||
while (historyEnumerator.hasMoreElements()) {
|
||||
let entry = historyEnumerator.getNext().QueryInterface(Ci.nsIPropertyBag2);
|
||||
let uri = entry.get("uri").QueryInterface(Ci.nsIURI);
|
||||
// MSIE stores some types of URLs in its history that we don't handle, like HTMLHelp
|
||||
// and others. Since we are not going to import the logins that are performed in these URLs
|
||||
// we can just skip them.
|
||||
if (["http", "https", "ftp"].indexOf(uri.scheme) == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uris.push(uri);
|
||||
}
|
||||
this._migrateURIs(uris);
|
||||
aCallback(true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Migrate the logins that were saved for the uris arguments.
|
||||
* @param {nsIURI[]} uris - the uris that are going to be migrated.
|
||||
*/
|
||||
_migrateURIs(uris) {
|
||||
this.ctypesHelpers = new MSMigrationUtils.CtypesHelpers();
|
||||
this._crypto = new OSCrypto();
|
||||
let nsIWindowsRegKey = Ci.nsIWindowsRegKey;
|
||||
let key = Cc["@mozilla.org/windows-registry-key;1"].
|
||||
createInstance(nsIWindowsRegKey);
|
||||
key.open(nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, kLoginsKey,
|
||||
nsIWindowsRegKey.ACCESS_READ);
|
||||
|
||||
let urlsSet = new Set(); // set of the already processed urls.
|
||||
// number of the successfully decrypted registry values
|
||||
let successfullyDecryptedValues = 0;
|
||||
/* The logins are stored in the registry, where the key is a hashed URL and its
|
||||
* value contains the encrypted details for all logins for that URL.
|
||||
*
|
||||
* First iterate through IE history, hashing each URL and looking for a match. If
|
||||
* found, decrypt the value, using the URL as a salt. Finally add any found logins
|
||||
* to the Firefox password manager.
|
||||
*/
|
||||
|
||||
for (let uri of uris) {
|
||||
try {
|
||||
// remove the query and the ref parts of the URL
|
||||
let urlObject = new URL(uri.spec);
|
||||
let url = urlObject.origin + urlObject.pathname;
|
||||
// if the current url is already processed, it should be skipped
|
||||
if (urlsSet.has(url)) {
|
||||
continue;
|
||||
}
|
||||
urlsSet.add(url);
|
||||
// hash value of the current uri
|
||||
let hashStr = this._crypto.getIELoginHash(url);
|
||||
if (!key.hasValue(hashStr)) {
|
||||
continue;
|
||||
}
|
||||
let value = key.readBinaryValue(hashStr);
|
||||
// if no value was found, the uri is skipped
|
||||
if (value == null) {
|
||||
continue;
|
||||
}
|
||||
let data;
|
||||
try {
|
||||
// the url is used as salt to decrypt the registry value
|
||||
data = this._crypto.decryptData(value, url, true);
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
// extract the login details from the decrypted data
|
||||
let ieLogins = this._extractDetails(data, uri);
|
||||
// if at least a credential was found in the current data, successfullyDecryptedValues should
|
||||
// be incremented by one
|
||||
if (ieLogins.length) {
|
||||
successfullyDecryptedValues++;
|
||||
}
|
||||
this._addLogins(ieLogins);
|
||||
} catch (e) {
|
||||
Cu.reportError("Error while importing logins for " + uri.spec + ": " + e);
|
||||
}
|
||||
}
|
||||
// if the number of the imported values is less than the number of values in the key, it means
|
||||
// that not all the values were imported and an error should be reported
|
||||
if (successfullyDecryptedValues < key.valueCount) {
|
||||
Cu.reportError("We failed to decrypt and import some logins. " +
|
||||
"This is likely because we didn't find the URLs where these " +
|
||||
"passwords were submitted in the IE history and which are needed to be used " +
|
||||
"as keys in the decryption.");
|
||||
}
|
||||
|
||||
key.close();
|
||||
this._crypto.finalize();
|
||||
this.ctypesHelpers.finalize();
|
||||
},
|
||||
|
||||
_crypto: null,
|
||||
|
||||
/**
|
||||
* Add the logins to the password manager.
|
||||
* @param {Object[]} logins - array of the login details.
|
||||
*/
|
||||
_addLogins(ieLogins) {
|
||||
function addLogin(login, existingLogins) {
|
||||
// Add the login only if it doesn't already exist
|
||||
// if the login is not already available, it s going to be added or merged with another
|
||||
// login
|
||||
if (existingLogins.some(l => login.matches(l, true))) {
|
||||
return;
|
||||
}
|
||||
let isUpdate = false; // the login is just an update for an old one
|
||||
for (let existingLogin of existingLogins) {
|
||||
if (login.username == existingLogin.username && login.password != existingLogin.password) {
|
||||
// if a login with the same username and different password already exists and it's older
|
||||
// than the current one, that login needs to be updated using the current one details
|
||||
if (login.timePasswordChanged > existingLogin.timePasswordChanged) {
|
||||
// Bug 1187190: Password changes should be propagated depending on timestamps.
|
||||
|
||||
// the existing login password and timestamps should be updated
|
||||
let propBag = Cc["@mozilla.org/hash-property-bag;1"].
|
||||
createInstance(Ci.nsIWritablePropertyBag);
|
||||
propBag.setProperty("password", login.password);
|
||||
propBag.setProperty("timePasswordChanged", login.timePasswordChanged);
|
||||
Services.logins.modifyLogin(existingLogin, propBag);
|
||||
// make sure not to add the new login
|
||||
isUpdate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// if the new login is not an update, add it.
|
||||
if (!isUpdate) {
|
||||
Services.logins.addLogin(login);
|
||||
}
|
||||
}
|
||||
|
||||
for (let ieLogin of ieLogins) {
|
||||
try {
|
||||
let login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo);
|
||||
|
||||
login.init(ieLogin.url, "", null,
|
||||
ieLogin.username, ieLogin.password, "", "");
|
||||
login.QueryInterface(Ci.nsILoginMetaInfo);
|
||||
login.timeCreated = ieLogin.creation;
|
||||
login.timeLastUsed = ieLogin.creation;
|
||||
login.timePasswordChanged = ieLogin.creation;
|
||||
// login.timesUsed is going to set to the default value 1
|
||||
// Add the login only if there's not an existing entry
|
||||
let existingLogins = Services.logins.findLogins({}, login.hostname, "", null);
|
||||
addLogin(login, existingLogins);
|
||||
} catch (e) {
|
||||
Cu.reportError(e);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Extract the details of one or more logins from the raw decrypted data.
|
||||
* @param {string} data - the decrypted data containing raw information.
|
||||
* @param {nsURI} uri - the nsURI of page where the login has occur.
|
||||
* @returns {Object[]} array of objects where each of them contains the username, password, URL,
|
||||
* and creation time representing all the logins found in the data arguments.
|
||||
*/
|
||||
_extractDetails(data, uri) {
|
||||
// the structure of the header of the IE7 decrypted data for all the logins sharing the same URL
|
||||
let loginData = new ctypes.StructType("loginData", [
|
||||
// Bytes 0-3 are not needed and not documented
|
||||
{"unknown1": ctypes.uint32_t},
|
||||
// Bytes 4-7 are the header size
|
||||
{"headerSize": ctypes.uint32_t},
|
||||
// Bytes 8-11 are the data size
|
||||
{"dataSize": ctypes.uint32_t},
|
||||
// Bytes 12-19 are not needed and not documented
|
||||
{"unknown2": ctypes.uint32_t},
|
||||
{"unknown3": ctypes.uint32_t},
|
||||
// Bytes 20-23 are the data count: each username and password is considered as a data
|
||||
{"dataMax": ctypes.uint32_t},
|
||||
// Bytes 24-35 are not needed and not documented
|
||||
{"unknown4": ctypes.uint32_t},
|
||||
{"unknown5": ctypes.uint32_t},
|
||||
{"unknown6": ctypes.uint32_t}
|
||||
]);
|
||||
|
||||
// the structure of a IE7 decrypted login item
|
||||
let loginItem = new ctypes.StructType("loginItem", [
|
||||
// Bytes 0-3 are the offset of the username
|
||||
{"usernameOffset": ctypes.uint32_t},
|
||||
// Bytes 4-11 are the date
|
||||
{"loDateTime": ctypes.uint32_t},
|
||||
{"hiDateTime": ctypes.uint32_t},
|
||||
// Bytes 12-15 are not needed and not documented
|
||||
{"foo": ctypes.uint32_t},
|
||||
// Bytes 16-19 are the offset of the password
|
||||
{"passwordOffset": ctypes.uint32_t},
|
||||
// Bytes 20-31 are not needed and not documented
|
||||
{"unknown1": ctypes.uint32_t},
|
||||
{"unknown2": ctypes.uint32_t},
|
||||
{"unknown3": ctypes.uint32_t}
|
||||
]);
|
||||
|
||||
let url = uri.prePath;
|
||||
let results = [];
|
||||
let arr = this._crypto.stringToArray(data);
|
||||
// convert data to ctypes.unsigned_char.array(arr.length)
|
||||
let cdata = ctypes.unsigned_char.array(arr.length)(arr);
|
||||
// Bytes 0-35 contain the loginData data structure for all the logins sharing the same URL
|
||||
let currentLoginData = ctypes.cast(cdata, loginData);
|
||||
let headerSize = currentLoginData.headerSize;
|
||||
let currentInfoIndex = loginData.size;
|
||||
// pointer to the current login item
|
||||
let currentLoginItemPointer = ctypes.cast(cdata.addressOfElement(currentInfoIndex),
|
||||
loginItem.ptr);
|
||||
// currentLoginData.dataMax is the data count: each username and password is considered as
|
||||
// a data. So, the number of logins is the number of data dived by 2
|
||||
let numLogins = currentLoginData.dataMax / 2;
|
||||
for (let n = 0; n < numLogins; n++) {
|
||||
// Bytes 0-31 starting from currentInfoIndex contain the loginItem data structure for the
|
||||
// current login
|
||||
let currentLoginItem = currentLoginItemPointer.contents;
|
||||
let creation = this.ctypesHelpers.
|
||||
fileTimeToSecondsSinceEpoch(currentLoginItem.hiDateTime,
|
||||
currentLoginItem.loDateTime) * 1000;
|
||||
let currentResult = {
|
||||
creation: creation,
|
||||
url: url,
|
||||
};
|
||||
// The username is UTF-16 and null-terminated.
|
||||
currentResult.username =
|
||||
ctypes.cast(cdata.addressOfElement(headerSize + 12 + currentLoginItem.usernameOffset),
|
||||
ctypes.char16_t.ptr).readString();
|
||||
// The password is UTF-16 and null-terminated.
|
||||
currentResult.password =
|
||||
ctypes.cast(cdata.addressOfElement(headerSize + 12 + currentLoginItem.passwordOffset),
|
||||
ctypes.char16_t.ptr).readString();
|
||||
results.push(currentResult);
|
||||
// move to the next login item
|
||||
currentLoginItemPointer = currentLoginItemPointer.increment();
|
||||
}
|
||||
return results;
|
||||
},
|
||||
};
|
||||
|
||||
function Settings() {
|
||||
}
|
||||
|
||||
@ -243,6 +514,7 @@ Settings.prototype = {
|
||||
|
||||
function IEProfileMigrator()
|
||||
{
|
||||
this.wrappedJSObject = this; // export this to be able to use it in the unittest.
|
||||
}
|
||||
|
||||
IEProfileMigrator.prototype = Object.create(MigratorPrototype);
|
||||
@ -252,6 +524,7 @@ IEProfileMigrator.prototype.getResources = function IE_getResources() {
|
||||
MSMigrationUtils.getBookmarksMigrator()
|
||||
, new History()
|
||||
, MSMigrationUtils.getCookiesMigrator()
|
||||
, new IE7FormPasswords()
|
||||
, new Settings()
|
||||
];
|
||||
return [r for each (r in resources) if (r.exists)];
|
||||
|
@ -30,48 +30,45 @@ Cu.importGlobalProperties(["File"]);
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Helpers.
|
||||
|
||||
let CtypesHelpers = {
|
||||
_structs: {},
|
||||
_functions: {},
|
||||
_libs: {},
|
||||
function CtypesHelpers() {
|
||||
this._structs = {};
|
||||
this._functions = {};
|
||||
this._libs = {};
|
||||
|
||||
/**
|
||||
* Must be invoked once before first use of any of the provided helpers.
|
||||
*/
|
||||
initialize() {
|
||||
const WORD = ctypes.uint16_t;
|
||||
const DWORD = ctypes.uint32_t;
|
||||
const BOOL = ctypes.int;
|
||||
const WORD = ctypes.uint16_t;
|
||||
const DWORD = ctypes.uint32_t;
|
||||
const BOOL = ctypes.int;
|
||||
|
||||
this._structs.SYSTEMTIME = new ctypes.StructType('SYSTEMTIME', [
|
||||
{wYear: WORD},
|
||||
{wMonth: WORD},
|
||||
{wDayOfWeek: WORD},
|
||||
{wDay: WORD},
|
||||
{wHour: WORD},
|
||||
{wMinute: WORD},
|
||||
{wSecond: WORD},
|
||||
{wMilliseconds: WORD}
|
||||
]);
|
||||
this._structs.SYSTEMTIME = new ctypes.StructType('SYSTEMTIME', [
|
||||
{wYear: WORD},
|
||||
{wMonth: WORD},
|
||||
{wDayOfWeek: WORD},
|
||||
{wDay: WORD},
|
||||
{wHour: WORD},
|
||||
{wMinute: WORD},
|
||||
{wSecond: WORD},
|
||||
{wMilliseconds: WORD}
|
||||
]);
|
||||
|
||||
this._structs.FILETIME = new ctypes.StructType('FILETIME', [
|
||||
{dwLowDateTime: DWORD},
|
||||
{dwHighDateTime: DWORD}
|
||||
]);
|
||||
this._structs.FILETIME = new ctypes.StructType('FILETIME', [
|
||||
{dwLowDateTime: DWORD},
|
||||
{dwHighDateTime: DWORD}
|
||||
]);
|
||||
|
||||
try {
|
||||
this._libs.kernel32 = ctypes.open("Kernel32");
|
||||
this._functions.FileTimeToSystemTime =
|
||||
this._libs.kernel32.declare("FileTimeToSystemTime",
|
||||
ctypes.default_abi,
|
||||
BOOL,
|
||||
this._structs.FILETIME.ptr,
|
||||
this._structs.SYSTEMTIME.ptr);
|
||||
} catch (ex) {
|
||||
this.finalize();
|
||||
}
|
||||
},
|
||||
try {
|
||||
this._libs.kernel32 = ctypes.open("Kernel32");
|
||||
this._functions.FileTimeToSystemTime =
|
||||
this._libs.kernel32.declare("FileTimeToSystemTime",
|
||||
ctypes.default_abi,
|
||||
BOOL,
|
||||
this._structs.FILETIME.ptr,
|
||||
this._structs.SYSTEMTIME.ptr);
|
||||
} catch (ex) {
|
||||
this.finalize();
|
||||
}
|
||||
}
|
||||
|
||||
CtypesHelpers.prototype = {
|
||||
/**
|
||||
* Must be invoked once after last use of any of the provided helpers.
|
||||
*/
|
||||
@ -433,7 +430,7 @@ Cookies.prototype = {
|
||||
},
|
||||
|
||||
migrate(aCallback) {
|
||||
CtypesHelpers.initialize();
|
||||
this.ctypesHelpers = new CtypesHelpers();
|
||||
|
||||
let cookiesGenerator = (function genCookie() {
|
||||
let success = false;
|
||||
@ -460,7 +457,7 @@ Cookies.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
CtypesHelpers.finalize();
|
||||
this.ctypesHelpers.finalize();
|
||||
|
||||
aCallback(success);
|
||||
}).apply(this);
|
||||
@ -536,8 +533,8 @@ Cookies.prototype = {
|
||||
host = "." + host;
|
||||
}
|
||||
|
||||
let expireTime = CtypesHelpers.fileTimeToSecondsSinceEpoch(Number(expireTimeHi),
|
||||
Number(expireTimeLo));
|
||||
let expireTime = this.ctypesHelpers.fileTimeToSecondsSinceEpoch(Number(expireTimeHi),
|
||||
Number(expireTimeLo));
|
||||
Services.cookies.add(host,
|
||||
path,
|
||||
name,
|
||||
@ -554,6 +551,7 @@ Cookies.prototype = {
|
||||
let MSMigrationUtils = {
|
||||
MIGRATION_TYPE_IE: 1,
|
||||
MIGRATION_TYPE_EDGE: 2,
|
||||
CtypesHelpers: CtypesHelpers,
|
||||
getBookmarksMigrator(migrationType = this.MIGRATION_TYPE_IE) {
|
||||
return new Bookmarks(migrationType);
|
||||
},
|
||||
|
@ -84,7 +84,7 @@ function promiseSetPassword(login) {
|
||||
SET password_value = :password_value
|
||||
WHERE rowid = :rowid
|
||||
`);
|
||||
let passwordValue = crypto.encryptData(login.password);
|
||||
let passwordValue = crypto.stringToArray(crypto.encryptData(login.password));
|
||||
stmt.bindBlobByName("password_value", passwordValue, passwordValue.length);
|
||||
stmt.params.rowid = login.id;
|
||||
|
||||
|
386
browser/components/migration/tests/unit/test_IE7_passwords.js
Normal file
386
browser/components/migration/tests/unit/test_IE7_passwords.js
Normal file
@ -0,0 +1,386 @@
|
||||
Cu.import("resource://gre/modules/AppConstants.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
|
||||
"resource://gre/modules/WindowsRegistry.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "OSCrypto",
|
||||
"resource://gre/modules/OSCrypto.jsm");
|
||||
|
||||
const CRYPT_PROTECT_UI_FORBIDDEN = 1;
|
||||
const LOGINS_KEY = "Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storage2";
|
||||
const EXTENSION = "-backup";
|
||||
const TESTED_WEBSITES = {
|
||||
twitter: {
|
||||
uri: makeURI("https://twitter.com"),
|
||||
hash: "A89D42BC6406E27265B1AD0782B6F376375764A301",
|
||||
data: [12, 0, 0, 0, 56, 0, 0, 0, 38, 0, 0, 0, 87, 73, 67, 75, 24, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68, 36, 67, 124, 118, 212, 208, 1, 8, 0, 0, 0, 18, 0, 0, 0, 68, 36, 67, 124, 118, 212, 208, 1, 9, 0, 0, 0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102, 0, 103, 0, 104, 0, 0, 0, 49, 0, 50, 0, 51, 0, 52, 0, 53, 0, 54, 0, 55, 0, 56, 0, 57, 0, 0, 0],
|
||||
logins: [
|
||||
{
|
||||
username: "abcdefgh",
|
||||
password: "123456789",
|
||||
hostname: "https://twitter.com",
|
||||
formSubmitURL: "",
|
||||
httpRealm: null,
|
||||
usernameField: "",
|
||||
passwordField: "",
|
||||
timeCreated: 1439325854000,
|
||||
timeLastUsed: 1439325854000,
|
||||
timePasswordChanged: 1439325854000,
|
||||
timesUsed: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
facebook: {
|
||||
uri: makeURI("https://www.facebook.com/"),
|
||||
hash: "EF44D3E034009CB0FD1B1D81A1FF3F3335213BD796",
|
||||
data: [12, 0, 0, 0, 152, 0, 0, 0, 160, 0, 0, 0, 87, 73, 67, 75, 24, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 88, 182, 125, 18, 121, 212, 208, 1, 9, 0, 0, 0, 20, 0, 0, 0, 88, 182, 125, 18, 121, 212, 208, 1, 9, 0, 0, 0, 40, 0, 0, 0, 134, 65, 33, 37, 121, 212, 208, 1, 9, 0, 0, 0, 60, 0, 0, 0, 134, 65, 33, 37, 121, 212, 208, 1, 9, 0, 0, 0, 80, 0, 0, 0, 45, 242, 246, 62, 121, 212, 208, 1, 9, 0, 0, 0, 100, 0, 0, 0, 45, 242, 246, 62, 121, 212, 208, 1, 9, 0, 0, 0, 120, 0, 0, 0, 28, 10, 193, 80, 121, 212, 208, 1, 9, 0, 0, 0, 140, 0, 0, 0, 28, 10, 193, 80, 121, 212, 208, 1, 9, 0, 0, 0, 117, 0, 115, 0, 101, 0, 114, 0, 110, 0, 97, 0, 109, 0, 101, 0, 48, 0, 0, 0, 112, 0, 97, 0, 115, 0, 115, 0, 119, 0, 111, 0, 114, 0, 100, 0, 48, 0, 0, 0, 117, 0, 115, 0, 101, 0, 114, 0, 110, 0, 97, 0, 109, 0, 101, 0, 49, 0, 0, 0, 112, 0, 97, 0, 115, 0, 115, 0, 119, 0, 111, 0, 114, 0, 100, 0, 49, 0, 0, 0, 117, 0, 115, 0, 101, 0, 114, 0, 110, 0, 97, 0, 109, 0, 101, 0, 50, 0, 0, 0, 112, 0, 97, 0, 115, 0, 115, 0, 119, 0, 111, 0, 114, 0, 100, 0, 50, 0, 0, 0, 117, 0, 115, 0, 101, 0, 114, 0, 110, 0, 97, 0, 109, 0, 101, 0, 51, 0, 0, 0, 112, 0, 97, 0, 115, 0, 115, 0, 119, 0, 111, 0, 114, 0, 100, 0, 51, 0, 0, 0],
|
||||
logins: [
|
||||
{
|
||||
username: "username0",
|
||||
password: "password0",
|
||||
hostname: "https://www.facebook.com",
|
||||
formSubmitURL: "",
|
||||
httpRealm: null,
|
||||
usernameField: "",
|
||||
passwordField: "",
|
||||
timeCreated: 1439326966000,
|
||||
timeLastUsed: 1439326966000,
|
||||
timePasswordChanged: 1439326966000,
|
||||
timesUsed: 1,
|
||||
},
|
||||
{
|
||||
username: "username1",
|
||||
password: "password1",
|
||||
hostname: "https://www.facebook.com",
|
||||
formSubmitURL: "",
|
||||
httpRealm: null,
|
||||
usernameField: "",
|
||||
passwordField: "",
|
||||
timeCreated: 1439326997000,
|
||||
timeLastUsed: 1439326997000,
|
||||
timePasswordChanged: 1439326997000,
|
||||
timesUsed: 1,
|
||||
},
|
||||
{
|
||||
username: "username2",
|
||||
password: "password2",
|
||||
hostname: "https://www.facebook.com",
|
||||
formSubmitURL: "",
|
||||
httpRealm: null,
|
||||
usernameField: "",
|
||||
passwordField: "",
|
||||
timeCreated: 1439327040000,
|
||||
timeLastUsed: 1439327040000,
|
||||
timePasswordChanged: 1439327040000,
|
||||
timesUsed: 1,
|
||||
},
|
||||
{
|
||||
username: "username3",
|
||||
password: "password3",
|
||||
hostname: "https://www.facebook.com",
|
||||
formSubmitURL: "",
|
||||
httpRealm: null,
|
||||
usernameField: "",
|
||||
passwordField: "",
|
||||
timeCreated: 1439327070000,
|
||||
timeLastUsed: 1439327070000,
|
||||
timePasswordChanged: 1439327070000,
|
||||
timesUsed: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
live: {
|
||||
uri: makeURI("https://login.live.com/"),
|
||||
hash: "7B506F2D6B81D939A8E0456F036EE8970856FF705E",
|
||||
data: [12, 0, 0, 0, 56, 0, 0, 0, 44, 0, 0, 0, 87, 73, 67, 75, 24, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 212, 17, 219, 140, 148, 212, 208, 1, 9, 0, 0, 0, 20, 0, 0, 0, 212, 17, 219, 140, 148, 212, 208, 1, 11, 0, 0, 0, 114, 0, 105, 0, 97, 0, 100, 0, 104, 0, 49, 6, 74, 6, 39, 6, 54, 6, 0, 0, 39, 6, 66, 6, 49, 6, 35, 6, 80, 0, 192, 0, 223, 0, 119, 0, 246, 0, 114, 0, 100, 0, 0, 0],
|
||||
logins: [
|
||||
{
|
||||
username: "riadhرياض",
|
||||
password: "اقرأPÀßwörd",
|
||||
hostname: "https://login.live.com",
|
||||
formSubmitURL: "",
|
||||
httpRealm: null,
|
||||
usernameField: "",
|
||||
passwordField: "",
|
||||
timeCreated: 1439338767000,
|
||||
timeLastUsed: 1439338767000,
|
||||
timePasswordChanged: 1439338767000,
|
||||
timesUsed: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
reddit: {
|
||||
uri: makeURI("http://www.reddit.com/"),
|
||||
hash: "B644028D1C109A91EC2C4B9D1F145E55A1FAE42065",
|
||||
data: [12, 0, 0, 0, 152, 0, 0, 0, 212, 0, 0, 0, 87, 73, 67, 75, 24, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 8, 234, 114, 153, 212, 208, 1, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 97, 93, 131, 116, 153, 212, 208, 1, 3, 0, 0, 0, 14, 0, 0, 0, 97, 93, 131, 116, 153, 212, 208, 1, 16, 0, 0, 0, 48, 0, 0, 0, 88, 150, 78, 174, 153, 212, 208, 1, 4, 0, 0, 0, 58, 0, 0, 0, 88, 150, 78, 174, 153, 212, 208, 1, 29, 0, 0, 0, 118, 0, 0, 0, 79, 102, 137, 34, 154, 212, 208, 1, 15, 0, 0, 0, 150, 0, 0, 0, 79, 102, 137, 34, 154, 212, 208, 1, 30, 0, 0, 0, 97, 0, 0, 0, 0, 0, 252, 140, 173, 138, 146, 48, 0, 0, 66, 0, 105, 0, 116, 0, 116, 0, 101, 0, 32, 0, 98, 0, 101, 0, 115, 0, 116, 0, 228, 0, 116, 0, 105, 0, 103, 0, 101, 0, 110, 0, 0, 0, 205, 145, 110, 127, 198, 91, 1, 120, 0, 0, 31, 4, 48, 4, 64, 4, 62, 4, 59, 4, 76, 4, 32, 0, 67, 4, 65, 4, 63, 4, 53, 4, 72, 4, 61, 4, 62, 4, 32, 0, 65, 4, 49, 4, 64, 4, 62, 4, 72, 4, 53, 4, 61, 4, 46, 0, 32, 0, 18, 4, 62, 4, 57, 4, 66, 4, 56, 4, 0, 0, 40, 6, 51, 6, 69, 6, 32, 0, 39, 6, 68, 6, 68, 6, 71, 6, 32, 0, 39, 6, 68, 6, 49, 6, 45, 6, 69, 6, 70, 6, 0, 0, 118, 0, 101, 0, 117, 0, 105, 0, 108, 0, 108, 0, 101, 0, 122, 0, 32, 0, 108, 0, 101, 0, 32, 0, 118, 0, 233, 0, 114, 0, 105, 0, 102, 0, 105, 0, 101, 0, 114, 0, 32, 0, 224, 0, 32, 0, 110, 0, 111, 0, 117, 0, 118, 0, 101, 0, 97, 0, 117, 0, 0, 0],
|
||||
logins: [
|
||||
{
|
||||
username: "購読を",
|
||||
password: "Bitte bestätigen",
|
||||
hostname: "http://www.reddit.com",
|
||||
formSubmitURL: "",
|
||||
httpRealm: null,
|
||||
usernameField: "",
|
||||
passwordField: "",
|
||||
timeCreated: 1439340874000,
|
||||
timeLastUsed: 1439340874000,
|
||||
timePasswordChanged: 1439340874000,
|
||||
timesUsed: 1,
|
||||
},
|
||||
{
|
||||
username: "重置密码",
|
||||
password: "Пароль успешно сброшен. Войти",
|
||||
hostname: "http://www.reddit.com",
|
||||
formSubmitURL: "",
|
||||
httpRealm: null,
|
||||
usernameField: "",
|
||||
passwordField: "",
|
||||
timeCreated: 1439340971000,
|
||||
timeLastUsed: 1439340971000,
|
||||
timePasswordChanged: 1439340971000,
|
||||
timesUsed: 1,
|
||||
},
|
||||
{
|
||||
username: "بسم الله الرحمن",
|
||||
password: "veuillez le vérifier à nouveau",
|
||||
hostname: "http://www.reddit.com",
|
||||
formSubmitURL: "",
|
||||
httpRealm: null,
|
||||
usernameField: "",
|
||||
passwordField: "",
|
||||
timeCreated: 1439341166000,
|
||||
timeLastUsed: 1439341166000,
|
||||
timePasswordChanged: 1439341166000,
|
||||
timesUsed: 1,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const TESTED_URLS = [
|
||||
"http://a.foo.com",
|
||||
"http://b.foo.com",
|
||||
"http://c.foo.com",
|
||||
"http://www.test.net",
|
||||
"http://www.test.net/home",
|
||||
"http://www.test.net/index",
|
||||
"https://a.bar.com",
|
||||
"https://b.bar.com",
|
||||
"https://c.bar.com",
|
||||
];
|
||||
|
||||
let nsIWindowsRegKey = Ci.nsIWindowsRegKey;
|
||||
let Storage2Key;
|
||||
|
||||
/*
|
||||
* If the key value exists, it's going to be backed up and replaced, so the value could be restored.
|
||||
* Otherwise a new value is going to be created.
|
||||
*/
|
||||
function backupAndStore(key, name, value) {
|
||||
if (key.hasValue(name)) {
|
||||
// backup the the current value
|
||||
let type = key.getValueType(name);
|
||||
// create a new value using use the current value name followed by EXTENSION as its new name
|
||||
switch (type) {
|
||||
case nsIWindowsRegKey.TYPE_STRING:
|
||||
key.writeStringValue(name + EXTENSION, key.readStringValue(name));
|
||||
break;
|
||||
case nsIWindowsRegKey.TYPE_BINARY:
|
||||
key.writeBinaryValue(name + EXTENSION, key.readBinaryValue(name));
|
||||
break;
|
||||
case nsIWindowsRegKey.TYPE_INT:
|
||||
key.writeIntValue(name + EXTENSION, key.readIntValue(name));
|
||||
break;
|
||||
case nsIWindowsRegKey.TYPE_INT64:
|
||||
key.writeInt64Value(name + EXTENSION, key.readInt64Value(name));
|
||||
break;
|
||||
}
|
||||
}
|
||||
key.writeBinaryValue(name, value);
|
||||
}
|
||||
|
||||
// Remove all values where their names are members of the names array from the key of registry
|
||||
function removeAllValues(key, names) {
|
||||
for (let name of names) {
|
||||
key.removeValue(name);
|
||||
}
|
||||
}
|
||||
|
||||
// Restore all the backed up values
|
||||
function restore(key) {
|
||||
let count = key.valueCount;
|
||||
let names = []; // the names of the key values
|
||||
for (let i = 0; i < count; ++i) {
|
||||
names.push(key.getValueName(i));
|
||||
}
|
||||
|
||||
for (let name of names) {
|
||||
// backed up values have EXTENSION at the end of their names
|
||||
if (name.lastIndexOf(EXTENSION) == name.length - EXTENSION.length) {
|
||||
let valueName = name.substr(0, name.length - EXTENSION.length);
|
||||
let type = key.getValueType(name);
|
||||
// create a new value using the name before the backup and removed the backed up one
|
||||
switch (type) {
|
||||
case nsIWindowsRegKey.TYPE_STRING:
|
||||
key.writeStringValue(valueName, key.readStringValue(name));
|
||||
key.removeValue(name);
|
||||
break;
|
||||
case nsIWindowsRegKey.TYPE_BINARY:
|
||||
key.writeBinaryValue(valueName, key.readBinaryValue(name));
|
||||
key.removeValue(name);
|
||||
break;
|
||||
case nsIWindowsRegKey.TYPE_INT:
|
||||
key.writeIntValue(valueName, key.readIntValue(name));
|
||||
key.removeValue(name);
|
||||
break;
|
||||
case nsIWindowsRegKey.TYPE_INT64:
|
||||
key.writeInt64Value(valueName, key.readInt64Value(name));
|
||||
key.removeValue(name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkLoginsAreEqual(passwordManagerLogin, IELogin, id) {
|
||||
passwordManagerLogin.QueryInterface(Ci.nsILoginMetaInfo);
|
||||
for (let attribute in IELogin) {
|
||||
Assert.equal(passwordManagerLogin[attribute], IELogin[attribute],
|
||||
"The two logins ID " + id + " have the same " + attribute);
|
||||
}
|
||||
}
|
||||
|
||||
function createRegistryPath(path) {
|
||||
let loginPath = path.split("\\");
|
||||
let parentKey = Cc["@mozilla.org/windows-registry-key;1"].
|
||||
createInstance(nsIWindowsRegKey);
|
||||
let currentPath =[];
|
||||
for (let currentKey of loginPath) {
|
||||
parentKey.open(nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, currentPath.join("\\"),
|
||||
nsIWindowsRegKey.ACCESS_ALL);
|
||||
|
||||
if (!parentKey.hasChild(currentKey)) {
|
||||
parentKey.createChild(currentKey, 0);
|
||||
}
|
||||
currentPath.push(currentKey);
|
||||
parentKey.close();
|
||||
}
|
||||
}
|
||||
|
||||
function getFirstResourceOfType(type) {
|
||||
let migrator = Cc["@mozilla.org/profile/migrator;1?app=browser&type=ie"]
|
||||
.createInstance(Ci.nsISupports)
|
||||
.wrappedJSObject;
|
||||
let migrators = migrator.getResources();
|
||||
for (let m of migrators) {
|
||||
if (m.type == type) {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
throw new Error("failed to find the " + type + " migrator");
|
||||
}
|
||||
|
||||
function makeURI(aURL) {
|
||||
return Services.io.newURI(aURL, null, null);
|
||||
}
|
||||
|
||||
add_task(function* setup() {
|
||||
if (AppConstants.isPlatformAndVersionAtLeast("win", "6.2")) {
|
||||
Assert.throws(() => getFirstResourceOfType(MigrationUtils.resourceTypes.PASSWORDS),
|
||||
"The migrator doesn't exist for win8+");
|
||||
return;
|
||||
}
|
||||
// create the path to Storage2 in the registry if it doest exist.
|
||||
createRegistryPath(LOGINS_KEY);
|
||||
Storage2Key = Cc["@mozilla.org/windows-registry-key;1"].
|
||||
createInstance(nsIWindowsRegKey);
|
||||
Storage2Key.open(nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, LOGINS_KEY,
|
||||
nsIWindowsRegKey.ACCESS_ALL);
|
||||
|
||||
// create a dummy value otherwise the migrator doesn't exist
|
||||
if (!Storage2Key.hasValue("dummy")) {
|
||||
Storage2Key.writeBinaryValue("dummy", "dummy");
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* test_passwordsNotAvailable() {
|
||||
if (AppConstants.isPlatformAndVersionAtLeast("win", "6.2")) {
|
||||
return;
|
||||
}
|
||||
|
||||
let migrator = getFirstResourceOfType(MigrationUtils.resourceTypes.PASSWORDS);
|
||||
Assert.ok(migrator.exists, "The migrator has to exist");
|
||||
let logins = Services.logins.getAllLogins({});
|
||||
Assert.equal(logins.length, 0, "There are no logins at the beginning of the test");
|
||||
|
||||
let uris = []; // the uris of the migrated logins
|
||||
for (let url of TESTED_URLS) {
|
||||
uris.push(makeURI(url));
|
||||
// in this test, there is no IE login data in the registry, so after the migration, the number
|
||||
// of logins in the store should be 0
|
||||
migrator._migrateURIs(uris);
|
||||
logins = Services.logins.getAllLogins({});
|
||||
Assert.equal(logins.length, 0,
|
||||
"There are no logins after doing the migration without adding values to the registry");
|
||||
}
|
||||
});
|
||||
|
||||
add_task(function* test_passwordsAvailable() {
|
||||
if (AppConstants.isPlatformAndVersionAtLeast("win", "6.2")) {
|
||||
return;
|
||||
}
|
||||
|
||||
let crypto = new OSCrypto();
|
||||
let hashes = []; // the hashes of all migrator websites, this is going to be used for the clean up
|
||||
|
||||
do_register_cleanup(() => {
|
||||
Services.logins.removeAllLogins();
|
||||
logins = Services.logins.getAllLogins({});
|
||||
Assert.equal(logins.length, 0, "There are no logins after the cleanup");
|
||||
//remove all the values created in this test from the registry
|
||||
removeAllValues(Storage2Key, hashes);
|
||||
// restore all backed up values
|
||||
restore(Storage2Key);
|
||||
|
||||
// clean the dummy value
|
||||
if (Storage2Key.hasValue("dummy")) {
|
||||
Storage2Key.removeValue("dummy");
|
||||
}
|
||||
Storage2Key.close();
|
||||
crypto.finalize();
|
||||
});
|
||||
|
||||
let migrator = getFirstResourceOfType(MigrationUtils.resourceTypes.PASSWORDS);
|
||||
Assert.ok(migrator.exists, "The migrator has to exist");
|
||||
let logins = Services.logins.getAllLogins({});
|
||||
Assert.equal(logins.length, 0, "There are no logins at the beginning of the test");
|
||||
|
||||
let uris = []; // the uris of the migrated logins
|
||||
|
||||
let loginCount = 0;
|
||||
for (let current in TESTED_WEBSITES) {
|
||||
let website = TESTED_WEBSITES[current];
|
||||
// backup the current the registry value if it exists and replace the existing value/create a
|
||||
// new value with the encrypted data
|
||||
backupAndStore(Storage2Key, website.hash,
|
||||
crypto.encryptData(crypto.arrayToString(website.data),
|
||||
website.uri.spec, true));
|
||||
Assert.ok(migrator.exists, "The migrator has to exist");
|
||||
uris.push(website.uri);
|
||||
hashes.push(website.hash);
|
||||
|
||||
migrator._migrateURIs(uris);
|
||||
logins = Services.logins.getAllLogins({});
|
||||
// check that the number of logins in the password manager has increased as expected which means
|
||||
// that all the values for the current website were imported
|
||||
loginCount += website.logins.length;
|
||||
Assert.equal(logins.length, loginCount,
|
||||
"The number of logins has increased after the migration");
|
||||
let startIndex = loginCount - website.logins.length;
|
||||
// compares the imported password manager logins with their expected logins
|
||||
for (let i = 0; i < website.logins.length; i++) {
|
||||
checkLoginsAreEqual(logins[startIndex + i], website.logins[i],
|
||||
" " + current + " - " + i + " ");
|
||||
}
|
||||
}
|
||||
});
|
@ -17,5 +17,7 @@ skip-if = os != "win"
|
||||
skip-if = os != "win"
|
||||
[test_IE_cookies.js]
|
||||
skip-if = os != "win"
|
||||
[test_IE7_passwords.js]
|
||||
skip-if = os != "win"
|
||||
[test_Safari_bookmarks.js]
|
||||
skip-if = os != "mac"
|
||||
|
@ -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.
|
||||
*/
|
||||
decryptData(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 = [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;
|
||||
},
|
||||
|
@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Tests the OSCrypto object.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Globals
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "OSCrypto",
|
||||
"resource://gre/modules/OSCrypto.jsm");
|
||||
|
||||
let crypto = new OSCrypto();
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Tests
|
||||
|
||||
add_task(function test_getIELoginHash()
|
||||
{
|
||||
do_check_eq(crypto.getIELoginHash("https://bugzilla.mozilla.org/page.cgi"),
|
||||
"4A66FE96607885790F8E67B56EEE52AB539BAFB47D");
|
||||
|
||||
do_check_eq(crypto.getIELoginHash("https://github.com/login"),
|
||||
"0112F7DCE67B8579EA01367678AA44AB9868B5A143");
|
||||
|
||||
do_check_eq(crypto.getIELoginHash("https://login.live.com/login.srf"),
|
||||
"FBF92E5D804C82717A57856533B779676D92903688");
|
||||
|
||||
do_check_eq(crypto.getIELoginHash("https://preview.c9.io/riadh/w1/pass.1.html"),
|
||||
"6935CF27628830605927F86AB53831016FC8973D1A");
|
||||
|
||||
|
||||
do_check_eq(crypto.getIELoginHash("https://reviewboard.mozilla.org/account/login/"),
|
||||
"09141FD287E2E59A8B1D3BB5671537FD3D6B61337A");
|
||||
|
||||
do_check_eq(crypto.getIELoginHash("https://www.facebook.com/"),
|
||||
"EF44D3E034009CB0FD1B1D81A1FF3F3335213BD796");
|
||||
|
||||
});
|
||||
|
||||
add_task(function test_decryptData_encryptData()
|
||||
{
|
||||
function decryptEncryptTest(key) {
|
||||
do_check_eq(crypto.decryptData(crypto.encryptData("", key), key),
|
||||
"");
|
||||
|
||||
do_check_eq(crypto.decryptData(crypto.encryptData("secret", key), key),
|
||||
"secret");
|
||||
|
||||
do_check_eq(crypto.decryptData(crypto.encryptData("https://www.mozilla.org", key),
|
||||
key),
|
||||
"https://www.mozilla.org");
|
||||
|
||||
do_check_eq(crypto.decryptData(crypto.encryptData("https://reviewboard.mozilla.org", key),
|
||||
key),
|
||||
"https://reviewboard.mozilla.org");
|
||||
|
||||
do_check_eq(crypto.decryptData(crypto.encryptData("https://bugzilla.mozilla.org/page.cgi",
|
||||
key),
|
||||
key),
|
||||
"https://bugzilla.mozilla.org/page.cgi");
|
||||
}
|
||||
|
||||
let keys = [null, "a", "keys", "abcdedf", "pass", "https://bugzilla.mozilla.org/page.cgi",
|
||||
"https://login.live.com/login.srf"];
|
||||
for (let key of keys) {
|
||||
decryptEncryptTest(key);
|
||||
}
|
||||
let url = "https://twitter.com/";
|
||||
let value = [1, 0, 0, 0, 208, 140, 157, 223, 1, 21, 209, 17, 140, 122, 0, 192, 79, 194, 151, 235, 1, 0, 0, 0, 254, 58, 230, 75, 132, 228, 181, 79, 184, 160, 37, 106, 201, 29, 42, 152, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 16, 102, 0, 0, 0, 1, 0, 0, 32, 0, 0, 0, 90, 136, 17, 124, 122, 57, 178, 24, 34, 86, 209, 198, 184, 107, 58, 58, 32, 98, 61, 239, 129, 101, 56, 239, 114, 159, 139, 165, 183, 40, 183, 85, 0, 0, 0, 0, 14, 128, 0, 0, 0, 2, 0, 0, 32, 0, 0, 0, 147, 170, 34, 21, 53, 227, 191, 6, 201, 84, 106, 31, 57, 227, 46, 127, 219, 199, 80, 142, 37, 104, 112, 223, 26, 165, 223, 55, 176, 89, 55, 37, 112, 0, 0, 0, 98, 70, 221, 109, 5, 152, 46, 11, 190, 213, 226, 58, 244, 20, 180, 217, 63, 155, 227, 132, 7, 151, 235, 6, 37, 232, 176, 182, 141, 191, 251, 50, 20, 123, 53, 11, 247, 233, 112, 121, 130, 27, 168, 68, 92, 144, 192, 7, 12, 239, 53, 217, 253, 155, 54, 109, 236, 216, 225, 245, 79, 234, 165, 225, 104, 36, 77, 13, 195, 237, 143, 165, 100, 107, 230, 70, 54, 19, 179, 35, 8, 101, 93, 202, 121, 210, 222, 28, 93, 122, 36, 84, 185, 249, 238, 3, 102, 149, 248, 94, 137, 16, 192, 22, 251, 220, 22, 223, 16, 58, 104, 187, 64, 0, 0, 0, 70, 72, 15, 119, 144, 66, 117, 203, 190, 82, 131, 46, 111, 130, 238, 191, 170, 63, 186, 117, 46, 88, 171, 3, 94, 146, 75, 86, 243, 159, 63, 195, 149, 25, 105, 141, 42, 217, 108, 18, 63, 62, 98, 182, 241, 195, 12, 216, 152, 230, 176, 253, 202, 129, 41, 185, 135, 111, 226, 92, 27, 78, 27, 198];
|
||||
|
||||
let arr1 = crypto.arrayToString(value)
|
||||
let arr2 = crypto.stringToArray(crypto.decryptData(crypto.encryptData(arr1, url), url));
|
||||
for(let i = 0; i < arr1.length; i++) {
|
||||
do_check_eq(arr2[i], value[i]);
|
||||
}
|
||||
});
|
@ -30,6 +30,8 @@ skip-if = os == "android" # Bug 1171687: Needs fixing on Android
|
||||
[test_logins_metainfo.js]
|
||||
[test_logins_search.js]
|
||||
[test_notifications.js]
|
||||
[test_OSCrypto_win.js]
|
||||
skip-if = os != "win"
|
||||
[test_recipes_add.js]
|
||||
[test_recipes_content.js]
|
||||
[test_storage.js]
|
||||
|
Loading…
Reference in New Issue
Block a user