зеркало из https://github.com/mozilla/gecko-dev.git
Bug 316084 - Migrated base64 suite passwords not encrypted when master PW added in Firefox; r=(dolske + gavin.sharp)
This commit is contained in:
Родитель
c6ae4ba1ac
Коммит
5a54b08217
|
@ -41,7 +41,10 @@
|
|||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
const DB_VERSION = 2; // The database schema version
|
||||
const DB_VERSION = 3; // The database schema version
|
||||
|
||||
const ENCTYPE_BASE64 = 0;
|
||||
const ENCTYPE_SDR = 1;
|
||||
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
|
@ -118,7 +121,7 @@ LoginManagerStorage_mozStorage.prototype = {
|
|||
},
|
||||
|
||||
|
||||
// The current database schema
|
||||
// The current database schema.
|
||||
_dbSchema: {
|
||||
tables: {
|
||||
moz_logins: "id INTEGER PRIMARY KEY," +
|
||||
|
@ -129,8 +132,9 @@ LoginManagerStorage_mozStorage.prototype = {
|
|||
"passwordField TEXT NOT NULL," +
|
||||
"encryptedUsername TEXT NOT NULL," +
|
||||
"encryptedPassword TEXT NOT NULL," +
|
||||
"guid TEXT",
|
||||
|
||||
"guid TEXT," +
|
||||
"encType INTEGER",
|
||||
// Changes must be reflected in this._dbAreExpectedColumnsPresent
|
||||
moz_disabledHosts: "id INTEGER PRIMARY KEY," +
|
||||
"hostname TEXT UNIQUE ON CONFLICT REPLACE",
|
||||
},
|
||||
|
@ -150,6 +154,10 @@ LoginManagerStorage_mozStorage.prototype = {
|
|||
moz_logins_guid_index: {
|
||||
table: "moz_logins",
|
||||
columns: ["guid"]
|
||||
},
|
||||
moz_logins_encType_index: {
|
||||
table: "moz_logins",
|
||||
columns: ["encType"]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -160,6 +168,7 @@ LoginManagerStorage_mozStorage.prototype = {
|
|||
_signonsFile : null, // nsIFile for "signons.sqlite"
|
||||
_importFile : null, // nsIFile for import from legacy
|
||||
_debug : false, // mirrors signon.debug
|
||||
_base64checked : false,
|
||||
|
||||
|
||||
/*
|
||||
|
@ -292,14 +301,20 @@ LoginManagerStorage_mozStorage.prototype = {
|
|||
loginClone.guid = this._uuidService.generateUUID().toString();
|
||||
}
|
||||
|
||||
// Determine encryption type
|
||||
let encType = ENCTYPE_SDR;
|
||||
if (isEncrypted &&
|
||||
(encUsername.charAt(0) == '~' || encPassword.charAt(0) == '~'))
|
||||
encType = ENCTYPE_BASE64;
|
||||
|
||||
let query =
|
||||
"INSERT INTO moz_logins " +
|
||||
"(hostname, httpRealm, formSubmitURL, usernameField, " +
|
||||
"passwordField, encryptedUsername, encryptedPassword, " +
|
||||
"guid) " +
|
||||
"guid, encType) " +
|
||||
"VALUES (:hostname, :httpRealm, :formSubmitURL, :usernameField, " +
|
||||
":passwordField, :encryptedUsername, :encryptedPassword, " +
|
||||
":guid)";
|
||||
":guid, :encType)";
|
||||
|
||||
let params = {
|
||||
hostname: loginClone.hostname,
|
||||
|
@ -309,7 +324,8 @@ LoginManagerStorage_mozStorage.prototype = {
|
|||
passwordField: loginClone.passwordField,
|
||||
encryptedUsername: encUsername,
|
||||
encryptedPassword: encPassword,
|
||||
guid: loginClone.guid
|
||||
guid: loginClone.guid,
|
||||
encType: encType
|
||||
};
|
||||
|
||||
let stmt;
|
||||
|
@ -429,7 +445,8 @@ LoginManagerStorage_mozStorage.prototype = {
|
|||
"passwordField = :passwordField, " +
|
||||
"encryptedUsername = :encryptedUsername, " +
|
||||
"encryptedPassword = :encryptedPassword, " +
|
||||
"guid = :guid " +
|
||||
"guid = :guid, " +
|
||||
"encType = :encType " +
|
||||
"WHERE id = :id";
|
||||
|
||||
let params = {
|
||||
|
@ -441,7 +458,8 @@ LoginManagerStorage_mozStorage.prototype = {
|
|||
passwordField: newLogin.passwordField,
|
||||
encryptedUsername: encUsername,
|
||||
encryptedPassword: encPassword,
|
||||
guid: newLogin.guid
|
||||
guid: newLogin.guid,
|
||||
encType: ENCTYPE_SDR
|
||||
};
|
||||
|
||||
let stmt;
|
||||
|
@ -697,13 +715,18 @@ LoginManagerStorage_mozStorage.prototype = {
|
|||
* is an array of encrypted nsLoginInfo and ids is an array of associated
|
||||
* ids in the database.
|
||||
*/
|
||||
_queryLogins : function (hostname, formSubmitURL, httpRealm) {
|
||||
_queryLogins : function (hostname, formSubmitURL, httpRealm, encType) {
|
||||
let logins = [], ids = [];
|
||||
|
||||
let query = "SELECT * FROM moz_logins";
|
||||
let [conditions, params] =
|
||||
this._buildConditionsAndParams(hostname, formSubmitURL, httpRealm);
|
||||
|
||||
if (typeof encType != "undefined") {
|
||||
conditions.push("encType = :encType");
|
||||
params.encType = encType;
|
||||
}
|
||||
|
||||
if (conditions.length) {
|
||||
conditions = conditions.map(function(c) "(" + c + ")");
|
||||
query += " WHERE " + conditions.join(" AND ");
|
||||
|
@ -975,6 +998,9 @@ LoginManagerStorage_mozStorage.prototype = {
|
|||
if (userCanceled)
|
||||
return [null, null, true];
|
||||
|
||||
if (!this._base64checked)
|
||||
this._reencryptBase64Logins();
|
||||
|
||||
return [encUsername, encPassword, false];
|
||||
},
|
||||
|
||||
|
@ -1020,10 +1046,46 @@ LoginManagerStorage_mozStorage.prototype = {
|
|||
result.push(login);
|
||||
}
|
||||
|
||||
if (!this._base64checked && !userCanceled)
|
||||
this._reencryptBase64Logins();
|
||||
|
||||
return [result, userCanceled];
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* _reencryptBase64Logins
|
||||
*
|
||||
* Checks the signons DB for any logins using the old wallet-style base64
|
||||
* obscuring of the username/password, instead of proper encryption. We're
|
||||
* called once per session, after the user has successfully encrypted or
|
||||
* decrypted some login (this helps ensure the user doesn't get mysterious
|
||||
* prompts for a master password, when set).
|
||||
*/
|
||||
_reencryptBase64Logins : function () {
|
||||
this._base64checked = true;
|
||||
// Ignore failures, will try again next session...
|
||||
|
||||
try {
|
||||
let [logins, ids] =
|
||||
this._queryLogins("", "", "", 0);
|
||||
|
||||
if (!logins.length)
|
||||
return;
|
||||
|
||||
let userCancelled;
|
||||
[logins, userCanceled] = this._decryptLogins(logins);
|
||||
if (userCanceled)
|
||||
return;
|
||||
|
||||
for each (let login in logins)
|
||||
this.modifyLogin(login, login);
|
||||
} catch (e) {
|
||||
this.log("_reencryptBase64Logins caught error: " + e);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* _encrypt
|
||||
*
|
||||
|
@ -1303,6 +1365,73 @@ LoginManagerStorage_mozStorage.prototype = {
|
|||
},
|
||||
|
||||
|
||||
/*
|
||||
* _dbMigrateToVersion3
|
||||
*
|
||||
* Version 3 adds a encType column.
|
||||
*/
|
||||
_dbMigrateToVersion3 : function () {
|
||||
// Check to see if encType column already exists.
|
||||
let exists = true;
|
||||
let query = "SELECT encType FROM moz_logins";
|
||||
let stmt;
|
||||
try {
|
||||
stmt = this._dbConnection.createStatement(query);
|
||||
// (no need to execute statement, if it compiled we're good)
|
||||
stmt.finalize();
|
||||
} catch (e) {
|
||||
exists = false;
|
||||
}
|
||||
|
||||
// Add the new column and index only if needed.
|
||||
if (!exists) {
|
||||
query = "ALTER TABLE moz_logins ADD COLUMN encType INTEGER";
|
||||
this._dbConnection.executeSimpleSQL(query);
|
||||
|
||||
query = "CREATE INDEX IF NOT EXISTS " +
|
||||
"moz_logins_encType_index ON moz_logins (encType)";
|
||||
this._dbConnection.executeSimpleSQL(query);
|
||||
}
|
||||
|
||||
// Get a list of existing logins
|
||||
let logins = [];
|
||||
query = "SELECT id, encryptedUsername, encryptedPassword " +
|
||||
"FROM moz_logins WHERE encType isnull";
|
||||
try {
|
||||
stmt = this._dbCreateStatement(query);
|
||||
while (stmt.step()) {
|
||||
let params = { id: stmt.row.id };
|
||||
if (stmt.row.encryptedUsername.charAt(0) == '~' ||
|
||||
stmt.row.encryptedPassword.charAt(0) == '~')
|
||||
params.encType = ENCTYPE_BASE64;
|
||||
else
|
||||
params.encType = ENCTYPE_SDR;
|
||||
logins.push(params);
|
||||
}
|
||||
} catch (e) {
|
||||
this.log("Failed getting logins: " + e);
|
||||
throw e;
|
||||
} finally {
|
||||
stmt.reset();
|
||||
}
|
||||
|
||||
// Determine encryption type for each login and update the DB.
|
||||
query = "UPDATE moz_logins SET encType = :encType WHERE id = :id";
|
||||
for each (params in logins) {
|
||||
try {
|
||||
stmt = this._dbCreateStatement(query, params);
|
||||
stmt.execute();
|
||||
} catch (e) {
|
||||
this.log("Failed setting encType: " + e);
|
||||
throw e;
|
||||
} finally {
|
||||
stmt.reset();
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* _dbAreExpectedColumnsPresent
|
||||
*
|
||||
|
@ -1319,7 +1448,8 @@ LoginManagerStorage_mozStorage.prototype = {
|
|||
"passwordField, " +
|
||||
"encryptedUsername, " +
|
||||
"encryptedPassword, " +
|
||||
"guid " +
|
||||
"guid, " +
|
||||
"encType " +
|
||||
"FROM moz_logins";
|
||||
try {
|
||||
let stmt = this._dbConnection.createStatement(query);
|
||||
|
|
Двоичный файл не отображается.
Двоичный файл не отображается.
Двоичный файл не отображается.
|
@ -8,6 +8,8 @@
|
|||
|
||||
|
||||
const STORAGE_TYPE = "mozStorage";
|
||||
const ENCTYPE_BASE64 = 0;
|
||||
const ENCTYPE_SDR = 1;
|
||||
|
||||
function run_test() {
|
||||
|
||||
|
@ -15,6 +17,15 @@ try {
|
|||
|
||||
var storage, testnum = 0;
|
||||
|
||||
function countBase64Logins(conn) {
|
||||
let stmt = conn.createStatement("SELECT COUNT(1) as numBase64 FROM moz_logins " +
|
||||
"WHERE encType = " + ENCTYPE_BASE64);
|
||||
do_check_true(stmt.step());
|
||||
let numBase64 = stmt.row.numBase64;
|
||||
stmt.finalize();
|
||||
return numBase64;
|
||||
}
|
||||
|
||||
|
||||
/* ========== 1 ========== */
|
||||
testnum++;
|
||||
|
@ -26,6 +37,8 @@ var dummyuser2 = Cc["@mozilla.org/login-manager/loginInfo;1"].
|
|||
createInstance(Ci.nsILoginInfo);
|
||||
var dummyuser3 = Cc["@mozilla.org/login-manager/loginInfo;1"].
|
||||
createInstance(Ci.nsILoginInfo);
|
||||
var dummyuser4 = Cc["@mozilla.org/login-manager/loginInfo;1"].
|
||||
createInstance(Ci.nsILoginInfo);
|
||||
|
||||
dummyuser1.init("http://dummyhost.mozilla.org", "", null,
|
||||
"testuser1", "testpass1", "put_user_here", "put_pw_here");
|
||||
|
@ -36,6 +49,10 @@ dummyuser2.init("http://dummyhost2.mozilla.org", "", null,
|
|||
dummyuser3.init("http://dummyhost2.mozilla.org", "", null,
|
||||
"testuser3", "testpass3", "put_user3_here", "put_pw3_here");
|
||||
|
||||
dummyuser4.init("http://dummyhost4.mozilla.org", "", null,
|
||||
"testuser4", "testpass4", "put_user4_here", "put_pw4_here");
|
||||
|
||||
|
||||
LoginTest.deleteFile(OUTDIR, "signons.sqlite");
|
||||
|
||||
|
||||
|
@ -560,6 +577,77 @@ testdesc = "[flush and reload for verification]"
|
|||
storage = LoginTest.reloadStorage(OUTDIR, "output-451155.sqlite");
|
||||
LoginTest.checkStorageData(storage, [utfHost], [utfUser1, utfUser2]);
|
||||
|
||||
LoginTest.deleteFile(OUTDIR, "output-451155.sqlite");
|
||||
|
||||
|
||||
/*
|
||||
* ---------------------- Bug 316984 ----------------------
|
||||
* Ensure that base64 logins are reencrypted upon call to
|
||||
* getAllLogins
|
||||
*/
|
||||
|
||||
/* ========== 15 ========== */
|
||||
testnum++;
|
||||
testdesc = "ensure base64 logins are reencrypted on first call to getAllLogins"
|
||||
|
||||
// signons-380961-3.txt contains 2 base64 logins & 1 normal
|
||||
storage = LoginTest.initStorage(INDIR, "signons-380961-3.txt",
|
||||
OUTDIR, "output-316984-1.sqlite");
|
||||
|
||||
// Check that we do have 2 base64 logins here.
|
||||
let dbConnection = LoginTest.openDB("output-316984-1.sqlite");
|
||||
do_check_eq(countBase64Logins(dbConnection), 2);
|
||||
|
||||
// This makes a call to getAllLogins which should reencrypt
|
||||
LoginTest.checkStorageData(storage, [], [dummyuser1, dummyuser2, dummyuser3]);
|
||||
|
||||
// Check that there are 0 base64 logins remaining
|
||||
do_check_eq(countBase64Logins(dbConnection), 0);
|
||||
|
||||
LoginTest.deleteFile(OUTDIR, "output-316984-1.sqlite");
|
||||
|
||||
|
||||
/* ========== 16 ========== */
|
||||
testnum++;
|
||||
testdesc = "ensure base64 logins are reencrypted when first new login is added"
|
||||
|
||||
// signons-380961-3.txt contains 2 base64 logins & 1 normal
|
||||
storage = LoginTest.initStorage(INDIR, "signons-380961-3.txt",
|
||||
OUTDIR, "output-316984-2.sqlite");
|
||||
|
||||
// Check that we do have 2 base64 logins here.
|
||||
dbConnection = LoginTest.openDB("output-316984-2.sqlite");
|
||||
do_check_eq(countBase64Logins(dbConnection), 2);
|
||||
|
||||
// Adding a new user should reencrypt the first time
|
||||
storage.addLogin(dummyuser4)
|
||||
|
||||
// Check that there are 0 base64 logins remaining
|
||||
do_check_eq(countBase64Logins(dbConnection), 0);
|
||||
|
||||
LoginTest.deleteFile(OUTDIR, "output-316984-2.sqlite");
|
||||
|
||||
|
||||
/* ========== 17 ========== */
|
||||
testnum++;
|
||||
testdesc = "ensure base64 logins are NOT reencrypted on call to countLogins"
|
||||
|
||||
// signons-380961-3.txt contains 2 base64 logins & 1 normal
|
||||
storage = LoginTest.initStorage(INDIR, "signons-380961-3.txt",
|
||||
OUTDIR, "output-316984-3.sqlite");
|
||||
|
||||
// Check that we do have 2 base64 logins here.
|
||||
dbConnection = LoginTest.openDB("output-316984-3.sqlite");
|
||||
do_check_eq(countBase64Logins(dbConnection), 2);
|
||||
|
||||
// countLogins should NOT reencrypt logins
|
||||
storage.countLogins("", "", "")
|
||||
|
||||
// Check that there are still 2 base64 logins here
|
||||
do_check_eq(countBase64Logins(dbConnection), 2);
|
||||
|
||||
LoginTest.deleteFile(OUTDIR, "output-316984-3.sqlite");
|
||||
|
||||
|
||||
|
||||
/* ========== end ========== */
|
||||
|
|
|
@ -9,10 +9,12 @@
|
|||
|
||||
|
||||
const STORAGE_TYPE = "mozStorage";
|
||||
const ENCTYPE_BASE64 = 0;
|
||||
const ENCTYPE_SDR = 1;
|
||||
|
||||
// Current schema version used by storage-mozStorage.js. This will need to be
|
||||
// kept in sync with the version there (or else the tests fail).
|
||||
const CURRENT_SCHEMA = 2;
|
||||
const CURRENT_SCHEMA = 3;
|
||||
|
||||
function run_test() {
|
||||
|
||||
|
@ -27,6 +29,14 @@ function getGUIDforID(conn, id) {
|
|||
return guid;
|
||||
}
|
||||
|
||||
function getEncTypeForID(conn, id) {
|
||||
var stmt = conn.createStatement("SELECT encType from moz_logins WHERE id = " + id);
|
||||
stmt.executeStep();
|
||||
var encType = stmt.row.encType;
|
||||
stmt.finalize();
|
||||
return encType;
|
||||
}
|
||||
|
||||
var storage;
|
||||
var dbConnection;
|
||||
var testnum = 0;
|
||||
|
@ -48,6 +58,13 @@ testuser2.init("http://test.org", "http://test.org", null,
|
|||
var testuser3 = new nsLoginInfo;
|
||||
testuser3.init("http://test.gov", "http://test.gov", null,
|
||||
"testuser3", "testpass3", "u3", "p3");
|
||||
var testuser4 = new nsLoginInfo;
|
||||
testuser4.init("http://test.gov", "http://test.gov", null,
|
||||
"testuser1", "testpass2", "u4", "p4");
|
||||
var testuser5 = new nsLoginInfo;
|
||||
testuser5.init("http://test.gov", "http://test.gov", null,
|
||||
"testuser2", "testpass1", "u5", "p5");
|
||||
|
||||
|
||||
/* ========== 1 ========== */
|
||||
testnum++;
|
||||
|
@ -99,7 +116,7 @@ LoginTest.deleteFile(OUTDIR, "signons-v999-2.sqlite.corrupt");
|
|||
|
||||
/* ========== 3 ========== */
|
||||
testnum++;
|
||||
testdesc = "Test upgrade from v1 storage"
|
||||
testdesc = "Test upgrade from v1->v2 storage"
|
||||
|
||||
LoginTest.copyFile("signons-v1.sqlite");
|
||||
// Sanity check the test file.
|
||||
|
@ -156,6 +173,63 @@ dbConnection.close();
|
|||
|
||||
LoginTest.deleteFile(OUTDIR, "signons-v1v2.sqlite");
|
||||
|
||||
/* ========== 5 ========== */
|
||||
testnum++;
|
||||
testdesc = "Test upgrade from v2->v3 storage"
|
||||
|
||||
LoginTest.copyFile("signons-v2.sqlite");
|
||||
// Sanity check the test file.
|
||||
dbConnection = LoginTest.openDB("signons-v2.sqlite");
|
||||
do_check_eq(2, dbConnection.schemaVersion);
|
||||
|
||||
storage = LoginTest.reloadStorage(OUTDIR, "signons-v2.sqlite");
|
||||
|
||||
// Check to see that we added the correct encType to the logins.
|
||||
do_check_eq(CURRENT_SCHEMA, dbConnection.schemaVersion);
|
||||
let encTypes = [ENCTYPE_BASE64, ENCTYPE_SDR, ENCTYPE_BASE64, ENCTYPE_BASE64];
|
||||
for (let i = 0; i < encTypes.length; i++)
|
||||
do_check_eq(encTypes[i], getEncTypeForID(dbConnection, i + 1));
|
||||
dbConnection.close();
|
||||
|
||||
// Ensure that call to getAllLogins will reencrypt
|
||||
LoginTest.checkStorageData(storage, ["https://disabled.net"],
|
||||
[testuser1, testuser2, testuser4, testuser5]);
|
||||
|
||||
LoginTest.deleteFile(OUTDIR, "signons-v2.sqlite");
|
||||
|
||||
/* ========== 6 ========== */
|
||||
testnum++;
|
||||
testdesc = "Test upgrade v3->v2 storage";
|
||||
// This is the case where a v3 DB has been accessed with v2 code, and now we
|
||||
// are upgrading it again. Any logins added by the v2 code must be properly
|
||||
// upgraded.
|
||||
|
||||
LoginTest.copyFile("signons-v2v3.sqlite");
|
||||
// Sanity check the test file.
|
||||
dbConnection = LoginTest.openDB("signons-v2v3.sqlite");
|
||||
do_check_eq(2, dbConnection.schemaVersion);
|
||||
encTypes = [ENCTYPE_BASE64, ENCTYPE_SDR, ENCTYPE_BASE64, ENCTYPE_BASE64, null];
|
||||
for (let i = 0; i < encTypes.length; i++)
|
||||
do_check_eq(encTypes[i], getEncTypeForID(dbConnection, i + 1));
|
||||
|
||||
// Reload storage, check that the new login now has encType=1, others untouched
|
||||
storage = LoginTest.reloadStorage(OUTDIR, "signons-v2v3.sqlite");
|
||||
do_check_eq(CURRENT_SCHEMA, dbConnection.schemaVersion);
|
||||
|
||||
encTypes = [ENCTYPE_BASE64, ENCTYPE_SDR, ENCTYPE_BASE64, ENCTYPE_BASE64, ENCTYPE_SDR];
|
||||
for (let i = 0; i < encTypes.length; i++)
|
||||
do_check_eq(encTypes[i], getEncTypeForID(dbConnection, i + 1));
|
||||
|
||||
// Sanity check that the data gets migrated
|
||||
LoginTest.checkStorageData(storage, ["https://disabled.net"],
|
||||
[testuser1, testuser2, testuser4, testuser5, testuser3]);
|
||||
encTypes = [ENCTYPE_SDR, ENCTYPE_SDR, ENCTYPE_SDR, ENCTYPE_SDR, ENCTYPE_SDR];
|
||||
for (let i = 0; i < encTypes.length; i++)
|
||||
do_check_eq(encTypes[i], getEncTypeForID(dbConnection, i + 1));
|
||||
dbConnection.close();
|
||||
|
||||
LoginTest.deleteFile(OUTDIR, "signons-v2v3.sqlite");
|
||||
|
||||
|
||||
} catch (e) {
|
||||
throw "FAILED in test #" + testnum + " -- " + testdesc + ": " + e;
|
||||
|
|
Загрузка…
Ссылка в новой задаче