Bug 558963, part 2, UTF-8 encode passphrase and rewrap keys if needed, r=mconnor

This commit is contained in:
Philipp von Weitershausen 2010-07-03 14:13:40 -04:00
parent 138373b089
commit c4e977df64
5 changed files with 137 additions and 13 deletions

View File

@ -121,18 +121,22 @@ CryptoMeta.prototype = {
__proto__: WBORecord.prototype,
_logName: "Record.CryptoMeta",
getKey: function CryptoMeta_getKey(privkey, passphrase) {
getWrappedKey: function _getWrappedKey(privkey) {
// get the uri to our public key
let pubkeyUri = privkey.publicKeyUri.spec;
// each hash key is a relative uri, resolve those and match against ours
let wrapped_key;
for (let relUri in this.keyring) {
if (pubkeyUri == this.baseUri.resolve(relUri))
wrapped_key = this.keyring[relUri];
return this.keyring[relUri];
}
return null;
},
getKey: function CryptoMeta_getKey(privkey, passphrase) {
let wrapped_key = this.getWrappedKey(privkey);
if (!wrapped_key)
throw "keyring doesn't contain a key for " + pubkeyUri;
throw "keyring doesn't contain a key for " + privkey.publicKeyUri.spec;
// Make sure the wrapped key hasn't been tampered with
let localHMAC = Utils.sha256HMAC(wrapped_key.wrapped, this.hmacKey);
@ -144,7 +148,7 @@ CryptoMeta.prototype = {
Svc.Crypto.unwrapSymmetricKey(
wrapped_key.wrapped,
privkey.keyData,
passphrase.password,
passphrase.passwordUTF8,
privkey.salt,
privkey.iv
)
@ -170,7 +174,7 @@ CryptoMeta.prototype = {
// each hash key is a relative uri, resolve those and
// if we find the one we're about to add, remove it
for (let relUri in this.keyring) {
if (pubkeyUri == this.uri.resolve(relUri))
if (new_pubkey.uri.spec == this.uri.resolve(relUri))
delete this.keyring[relUri];
}
@ -183,7 +187,7 @@ CryptoMeta.prototype = {
},
get hmacKey() {
let passphrase = ID.get("WeaveCryptoID").password;
let passphrase = ID.get("WeaveCryptoID").passwordUTF8;
return Svc.KeyFactory.keyFromString(Ci.nsIKeyObject.HMAC, passphrase);
}
};

View File

@ -144,7 +144,8 @@ PubKeyManager.prototype = {
privkey.iv = Svc.Crypto.generateRandomIV();
let pub = {}, priv = {};
Svc.Crypto.generateKeypair(passphrase.password, privkey.salt, privkey.iv, pub, priv);
Svc.Crypto.generateKeypair(passphrase.passwordUTF8, privkey.salt,
privkey.iv, pub, priv);
[pubkey.keyData, privkey.keyData] = [pub.value, priv.value];
if (pubkeyUri) {

View File

@ -79,18 +79,31 @@ function Identity(realm, username, password) {
this.realm = realm;
this.username = username;
this._password = password;
if (password)
this._passwordUTF8 = Utils.encodeUTF8(password);
}
Identity.prototype = {
get password() {
// Look up the password then cache it
if (this._password == null)
for each (let login in this._logins)
if (login.username.toLowerCase() == this.username)
if (login.username.toLowerCase() == this.username) {
this._password = login.password;
this._passwordUTF8 = Utils.encodeUTF8(login.password);
}
return this._password;
},
set password(value) this._password = value,
set password(value) {
this._password = value;
this._passwordUTF8 = Utils.encodeUTF8(value);
},
get passwordUTF8() {
if (!this._passwordUTF8)
this.password; // invoke password getter
return this._passwordUTF8;
},
persist: function persist() {
// Clean up any existing passwords unless it's what we're persisting

View File

@ -131,6 +131,7 @@ WeaveSvc.prototype = {
get passphrase() ID.get("WeaveCryptoID").password,
set passphrase(value) ID.get("WeaveCryptoID").password = value,
get passphraseUTF8() ID.get("WeaveCryptoID").passwordUTF8,
get serverURL() Svc.Prefs.get("serverURL"),
set serverURL(value) {
@ -609,10 +610,22 @@ WeaveSvc.prototype = {
try {
let pubkey = PubKeys.getDefaultKey();
let privkey = PrivKeys.get(pubkey.privateKeyUri);
return Svc.Crypto.verifyPassphrase(
let result = Svc.Crypto.verifyPassphrase(
privkey.payload.keyData, this.passphraseUTF8,
privkey.payload.salt, privkey.payload.iv
);
if (result)
return true;
// Passphrase validation failed. Perhaps because the keys are
// based on an old low-byte only passphrase?
result = Svc.Crypto.verifyPassphrase(
privkey.payload.keyData, this.passphrase,
privkey.payload.salt, privkey.payload.iv
);
if (result)
this._needUpdatedKeys = true;
return result;
} catch (e) {
// this means no keys are present (or there's a network error)
return true;
@ -1049,6 +1062,9 @@ WeaveSvc.prototype = {
return;
}
if (this._needUpdatedKeys)
this._updateKeysToUTF8Passphrase();
// Only set the wait time to 0 if we need to sync right away
let wait;
if (this.globalScore > this.syncThreshold) {
@ -1058,6 +1074,62 @@ WeaveSvc.prototype = {
this._scheduleNextSync(wait);
},
_updateKeysToUTF8Passphrase: function _updateKeysToUTF8Passphrase() {
// Rewrap private key in UTF-8 encoded passphrase.
let pubkey = PubKeys.getDefaultKey();
let privkey = PrivKeys.get(pubkey.privateKeyUri);
this._log.debug("Rewrapping private key with UTF-8 encoded passphrase.");
let oldPrivKeyData = privkey.payload.keyData;
privkey.payload.keyData = Svc.Crypto.rewrapPrivateKey(
oldPrivKeyData, this.passphrase,
privkey.payload.salt, privkey.payload.iv, this.passphraseUTF8
);
let response = new Resource(privkey.uri).put(privkey);
if (!response.success) {
this._log("Uploading rewrapped private key failed!");
this._needUpdatedKeys = false;
return;
}
// Recompute HMAC for symmetric bulk keys based on UTF-8 encoded passphrase.
let oldHmacKey = Svc.KeyFactory.keyFromString(Ci.nsIKeyObject.HMAC,
this.passphrase);
let enginesToWipe = [];
for each (let engine in Engines.getAll()) {
let meta = CryptoMetas.get(engine.cryptoMetaURL);
if (!meta)
continue;
this._log.debug("Recomputing HMAC for key at " + engine.cryptoMetaURL
+ " with UTF-8 encoded passphrase.");
for each (key in meta.keyring) {
if (key.hmac != Utils.sha256HMAC(key.wrapped, oldHmacKey)) {
this._log.debug("Key SHA256 HMAC mismatch! Wiping server.");
enginesToWipe.push(engine.name);
meta = null;
break;
}
key.hmac = Utils.sha256HMAC(key.wrapped, meta.hmacKey);
}
if (!meta)
continue;
response = new Resource(meta.uri).put(meta);
if (!response.success) {
this._log.debug("Key upload failed: " + response);
}
}
if (enginesToWipe.length) {
this._log.debug("Wiping engines " + enginesToWipe.join(", "));
this.wipeRemote(enginesToWipe);
}
this._needUpdatedKeys = false;
},
/**
* Call sync() on an idle timer
*

View File

@ -4,7 +4,9 @@ try {
Cu.import("resource://services-sync/log4moz.js");
Cu.import("resource://services-sync/identity.js");
Cu.import("resource://services-sync/util.js");
} catch (e) { do_throw(e); }
} catch (e) {
do_throw(e);
}
function pubkey_handler(metadata, response) {
let obj = {id: "asdf-1234-asdf-1234",
@ -24,7 +26,7 @@ function privkey_handler(metadata, response) {
return httpd_basic_auth_handler(JSON.stringify(obj), metadata, response);
}
function run_test() {
function test_get() {
let server;
try {
@ -56,3 +58,35 @@ function run_test() {
catch (e) { do_throw(e); }
finally { server.stop(function() {}); }
}
function test_createKeypair() {
let passphrase = "moneyislike$\u20ac\u00a5\u5143";
let id = ID.set('foo', new Identity('foo', 'luser'));
id.password = passphrase;
_("Generate a key pair.");
let result = PubKeys.createKeypair(id, "http://pub/key", "http://priv/key");
_("Check that salt and IV are of correct length.");
// 16 bytes = 24 base64 encoded characters
do_check_eq(result.privkey.salt.length, 24);
do_check_eq(result.privkey.iv.length, 24);
_("URIs are set.");
do_check_eq(result.pubkey.uri.spec, "http://pub/key");
do_check_eq(result.pubkey.privateKeyUri.spec, "http://priv/key");
do_check_eq(result.privkey.uri.spec, "http://priv/key");
do_check_eq(result.privkey.publicKeyUri.spec, "http://pub/key");
_("UTF8 encoded passphrase was used.");
do_check_true(Svc.Crypto.verifyPassphrase(result.privkey.keyData,
Utils.encodeUTF8(passphrase),
result.privkey.salt,
result.privkey.payload.iv));
}
function run_test() {
test_get();
test_createKeypair();
}