зеркало из https://github.com/mozilla/pjs.git
Bug 636677 - Don't create a new nsICryptoHMAC object for each HMAC verification. r=rnewman a=blocking-fennec
This commit is contained in:
Родитель
b929fb4fe9
Коммит
fbb1ae2b7e
|
@ -475,7 +475,8 @@ JPAKEClient.prototype = {
|
|||
}
|
||||
|
||||
this._crypto_key = aes256Key.value;
|
||||
this._hmac_key = Utils.makeHMACKey(Utils.safeAtoB(hmac256Key.value));
|
||||
let hmac_key = Utils.makeHMACKey(Utils.safeAtoB(hmac256Key.value));
|
||||
this._hmac_hasher = Utils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256, hmac_key);
|
||||
|
||||
callback();
|
||||
},
|
||||
|
@ -523,7 +524,7 @@ JPAKEClient.prototype = {
|
|||
try {
|
||||
iv = Svc.Crypto.generateRandomIV();
|
||||
ciphertext = Svc.Crypto.encrypt(this._data, this._crypto_key, iv);
|
||||
hmac = Utils.sha256HMAC(ciphertext, this._hmac_key);
|
||||
hmac = Utils.bytesAsHex(Utils.digestUTF8(ciphertext, this._hmac_hasher));
|
||||
} catch (ex) {
|
||||
this._log.error("Failed to encrypt data.");
|
||||
this.abort(JPAKE_ERROR_INTERNAL);
|
||||
|
@ -545,7 +546,8 @@ JPAKEClient.prototype = {
|
|||
}
|
||||
let step3 = this._incoming.payload;
|
||||
try {
|
||||
let hmac = Utils.sha256HMAC(step3.ciphertext, this._hmac_key);
|
||||
let hmac = Utils.bytesAsHex(
|
||||
Utils.digestUTF8(step3.ciphertext, this._hmac_hasher));
|
||||
if (hmac != step3.hmac)
|
||||
throw "HMAC validation failed!";
|
||||
} catch (ex) {
|
||||
|
|
|
@ -190,11 +190,11 @@ CryptoWrapper.prototype = {
|
|||
_logName: "Record.CryptoWrapper",
|
||||
|
||||
ciphertextHMAC: function ciphertextHMAC(keyBundle) {
|
||||
let hmacKey = keyBundle.hmacKeyObject;
|
||||
if (!hmacKey)
|
||||
throw "Cannot compute HMAC with null key.";
|
||||
|
||||
return Utils.sha256HMAC(this.ciphertext, hmacKey);
|
||||
let hasher = keyBundle.sha256HMACHasher;
|
||||
if (!hasher)
|
||||
throw "Cannot compute HMAC without an HMAC key.";
|
||||
|
||||
return Utils.bytesAsHex(Utils.digestUTF8(this.ciphertext, hasher));
|
||||
},
|
||||
|
||||
/*
|
||||
|
@ -207,7 +207,6 @@ CryptoWrapper.prototype = {
|
|||
* Optional key bundle overrides the collection key lookup.
|
||||
*/
|
||||
encrypt: function encrypt(keyBundle) {
|
||||
|
||||
keyBundle = keyBundle || CollectionKeys.keyForCollection(this.collection);
|
||||
if (!keyBundle)
|
||||
throw new Error("Key bundle is null for " + this.uri.spec);
|
||||
|
@ -221,7 +220,6 @@ CryptoWrapper.prototype = {
|
|||
|
||||
// Optional key bundle.
|
||||
decrypt: function decrypt(keyBundle) {
|
||||
|
||||
if (!this.ciphertext) {
|
||||
throw "No ciphertext: nothing to decrypt?";
|
||||
}
|
||||
|
@ -238,14 +236,14 @@ CryptoWrapper.prototype = {
|
|||
}
|
||||
|
||||
// Handle invalid data here. Elsewhere we assume that cleartext is an object.
|
||||
let json_result = JSON.parse(Svc.Crypto.decrypt(this.ciphertext,
|
||||
keyBundle.encryptionKey, this.IV));
|
||||
let cleartext = Svc.Crypto.decrypt(this.ciphertext,
|
||||
keyBundle.encryptionKey, this.IV);
|
||||
let json_result = JSON.parse(cleartext);
|
||||
|
||||
if (json_result && (json_result instanceof Object)) {
|
||||
this.cleartext = json_result;
|
||||
this.ciphertext = null;
|
||||
}
|
||||
else {
|
||||
this.ciphertext = null;
|
||||
} else {
|
||||
throw "Decryption failed: result is <" + json_result + ">, not an object.";
|
||||
}
|
||||
|
||||
|
@ -536,15 +534,14 @@ function KeyBundle(realm, collectionName, keyStr) {
|
|||
throw "KeyBundle given non-string key.";
|
||||
|
||||
Identity.call(this, realm, collectionName, keyStr);
|
||||
this._hmac = null;
|
||||
this._encrypt = null;
|
||||
|
||||
// Cache the key object.
|
||||
this._hmacObj = null;
|
||||
}
|
||||
|
||||
KeyBundle.prototype = {
|
||||
__proto__: Identity.prototype,
|
||||
|
||||
_encrypt: null,
|
||||
_hmac: null,
|
||||
_hmacObj: null,
|
||||
_sha256HMACHasher: null,
|
||||
|
||||
equals: function equals(bundle) {
|
||||
return bundle &&
|
||||
|
@ -570,12 +567,18 @@ KeyBundle.prototype = {
|
|||
set hmacKey(value) {
|
||||
this._hmac = value;
|
||||
this._hmacObj = value ? Utils.makeHMACKey(value) : null;
|
||||
this._sha256HMACHasher = value ? Utils.makeHMACHasher(
|
||||
Ci.nsICryptoHMAC.SHA256, this._hmacObj) : null;
|
||||
},
|
||||
|
||||
get hmacKeyObject() {
|
||||
return this._hmacObj;
|
||||
},
|
||||
}
|
||||
|
||||
get sha256HMACHasher() {
|
||||
return this._sha256HMACHasher;
|
||||
}
|
||||
};
|
||||
|
||||
function BulkKeyBundle(realm, collectionName) {
|
||||
let log = Log4Moz.repository.getLogger("BulkKeyBundle");
|
||||
|
@ -612,8 +615,8 @@ BulkKeyBundle.prototype = {
|
|||
}
|
||||
else {
|
||||
throw "Invalid keypair";
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
function SyncKeyBundle(realm, collectionName, syncKey) {
|
||||
|
@ -640,6 +643,7 @@ SyncKeyBundle.prototype = {
|
|||
this._hmac = null;
|
||||
this._hmacObj = null;
|
||||
this._encrypt = null;
|
||||
this._sha256HMACHasher = null;
|
||||
},
|
||||
|
||||
/*
|
||||
|
@ -666,7 +670,13 @@ SyncKeyBundle.prototype = {
|
|||
this.generateEntry();
|
||||
return this._hmacObj;
|
||||
},
|
||||
|
||||
|
||||
get sha256HMACHasher() {
|
||||
if (!this._sha256HMACHasher)
|
||||
this.generateEntry();
|
||||
return this._sha256HMACHasher;
|
||||
},
|
||||
|
||||
/*
|
||||
* If we've got a string, hash it into keys and store them.
|
||||
*/
|
||||
|
@ -687,6 +697,8 @@ SyncKeyBundle.prototype = {
|
|||
// Individual sets: cheaper than calling parent setter.
|
||||
this._hmac = hmac;
|
||||
this._hmacObj = Utils.makeHMACKey(hmac);
|
||||
this._sha256HMACHasher = Utils.makeHMACHasher(
|
||||
Ci.nsICryptoHMAC.SHA256, this._hmacObj);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -588,27 +588,49 @@ let Utils = {
|
|||
throw 'checkStatus failed';
|
||||
},
|
||||
|
||||
digest: function digest(message, hasher) {
|
||||
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
|
||||
createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
|
||||
let data = converter.convertToByteArray(message, {});
|
||||
/**
|
||||
* UTF8-encode a message and hash it with the given hasher. Returns a
|
||||
* string containing bytes. The hasher is reset if it's an HMAC hasher.
|
||||
*/
|
||||
digestUTF8: function digestUTF8(message, hasher) {
|
||||
let data = this._utf8Converter.convertToByteArray(message, {});
|
||||
hasher.update(data, data.length);
|
||||
return hasher.finish(false);
|
||||
let result = hasher.finish(false);
|
||||
if (hasher instanceof Ci.nsICryptoHMAC) {
|
||||
hasher.reset();
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Treat the given message as a bytes string and hash it with the given
|
||||
* hasher. Returns a string containing bytes. The hasher is reset if it's
|
||||
* an HMAC hasher.
|
||||
*/
|
||||
digestBytes: function digestBytes(message, hasher) {
|
||||
// No UTF-8 encoding for you, sunshine.
|
||||
let bytes = [b.charCodeAt() for each (b in message)];
|
||||
hasher.update(bytes, bytes.length);
|
||||
let result = hasher.finish(false);
|
||||
if (hasher instanceof Ci.nsICryptoHMAC) {
|
||||
hasher.reset();
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
bytesAsHex: function bytesAsHex(bytes) {
|
||||
// Convert each hashed byte into 2-hex strings then combine them
|
||||
return [("0" + byte.charCodeAt().toString(16)).slice(-2)
|
||||
for each (byte in bytes)].join("");
|
||||
let hex = "";
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
hex += ("0" + bytes[i].charCodeAt().toString(16)).slice(-2);
|
||||
}
|
||||
return hex;
|
||||
},
|
||||
|
||||
_sha256: function _sha256(message) {
|
||||
let hasher = Cc["@mozilla.org/security/hash;1"].
|
||||
createInstance(Ci.nsICryptoHash);
|
||||
hasher.init(hasher.SHA256);
|
||||
return Utils.digest(message, hasher);
|
||||
return Utils.digestUTF8(message, hasher);
|
||||
},
|
||||
|
||||
sha256: function sha256(message) {
|
||||
|
@ -623,7 +645,7 @@ let Utils = {
|
|||
let hasher = Cc["@mozilla.org/security/hash;1"].
|
||||
createInstance(Ci.nsICryptoHash);
|
||||
hasher.init(hasher.SHA1);
|
||||
return Utils.digest(message, hasher);
|
||||
return Utils.digestUTF8(message, hasher);
|
||||
},
|
||||
|
||||
sha1: function sha1(message) {
|
||||
|
@ -634,6 +656,10 @@ let Utils = {
|
|||
return Utils.encodeBase32(Utils._sha1(message));
|
||||
},
|
||||
|
||||
sha1Base64: function (message) {
|
||||
return btoa(Utils._sha1(message));
|
||||
},
|
||||
|
||||
/**
|
||||
* Produce an HMAC key object from a key string.
|
||||
*/
|
||||
|
@ -642,61 +668,33 @@ let Utils = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Produce an HMAC hasher.
|
||||
* Produce an HMAC hasher and initialize it with the given HMAC key.
|
||||
*/
|
||||
makeHMACHasher: function makeHMACHasher() {
|
||||
return Cc["@mozilla.org/security/hmac;1"]
|
||||
.createInstance(Ci.nsICryptoHMAC);
|
||||
},
|
||||
|
||||
sha1Base64: function (message) {
|
||||
return btoa(Utils._sha1(message));
|
||||
makeHMACHasher: function makeHMACHasher(type, key) {
|
||||
let hasher = Cc["@mozilla.org/security/hmac;1"]
|
||||
.createInstance(Ci.nsICryptoHMAC);
|
||||
hasher.init(type, key);
|
||||
return hasher;
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate a sha1 HMAC for a message, not UTF-8 encoded,
|
||||
* and a given nsIKeyObject.
|
||||
* Optionally provide an existing hasher, which will be
|
||||
* initialized and reused.
|
||||
* Some HMAC convenience functions for tests and backwards compatibility:
|
||||
*
|
||||
* sha1HMACBytes: hashes byte string, returns bytes string
|
||||
* sha256HMAC: hashes UTF-8 encoded string, returns hex string
|
||||
* sha256HMACBytes: hashes byte string, returns bytes string
|
||||
*/
|
||||
sha1HMACBytes: function sha1HMACBytes(message, key, hasher) {
|
||||
let h = hasher || this.makeHMACHasher();
|
||||
h.init(h.SHA1, key);
|
||||
|
||||
// No UTF-8 encoding for you, sunshine.
|
||||
let bytes = [b.charCodeAt() for each (b in message)];
|
||||
h.update(bytes, bytes.length);
|
||||
return h.finish(false);
|
||||
sha1HMACBytes: function sha1HMACBytes(message, key) {
|
||||
let h = Utils.makeHMACHasher(Ci.nsICryptoHMAC.SHA1, key);
|
||||
return Utils.digestBytes(message, h);
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate a sha256 HMAC for a string message and a given nsIKeyObject.
|
||||
* Optionally provide an existing hasher, which will be
|
||||
* initialized and reused.
|
||||
*
|
||||
* Returns hex output.
|
||||
*/
|
||||
sha256HMAC: function sha256HMAC(message, key, hasher) {
|
||||
let h = hasher || this.makeHMACHasher();
|
||||
h.init(h.SHA256, key);
|
||||
return Utils.bytesAsHex(Utils.digest(message, h));
|
||||
sha256HMAC: function sha256HMAC(message, key) {
|
||||
let h = Utils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256, key);
|
||||
return Utils.bytesAsHex(Utils.digestUTF8(message, h));
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Generate a sha256 HMAC for a string message, not UTF-8 encoded,
|
||||
* and a given nsIKeyObject.
|
||||
* Optionally provide an existing hasher, which will be
|
||||
* initialized and reused.
|
||||
*/
|
||||
sha256HMACBytes: function sha256HMACBytes(message, key, hasher) {
|
||||
let h = hasher || this.makeHMACHasher();
|
||||
h.init(h.SHA256, key);
|
||||
|
||||
// No UTF-8 encoding for you, sunshine.
|
||||
let bytes = [b.charCodeAt() for each (b in message)];
|
||||
h.update(bytes, bytes.length);
|
||||
return h.finish(false);
|
||||
sha256HMACBytes: function sha256HMACBytes(message, key) {
|
||||
let h = Utils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256, key);
|
||||
return Utils.digestBytes(message, h);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -704,13 +702,13 @@ let Utils = {
|
|||
*/
|
||||
hkdfExpand: function hkdfExpand(prk, info, len) {
|
||||
const BLOCKSIZE = 256 / 8;
|
||||
let h = Utils.makeHMACHasher();
|
||||
let h = Utils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256,
|
||||
Utils.makeHMACKey(prk));
|
||||
let T = "";
|
||||
let Tn = "";
|
||||
let iterations = Math.ceil(len/BLOCKSIZE);
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
Tn = Utils.sha256HMACBytes(Tn + info + String.fromCharCode(i + 1),
|
||||
Utils.makeHMACKey(prk), h);
|
||||
Tn = Utils.digestBytes(Tn + info + String.fromCharCode(i + 1), h);
|
||||
T += Tn;
|
||||
}
|
||||
return T.slice(0, len);
|
||||
|
@ -736,7 +734,6 @@ let Utils = {
|
|||
* can encode as you wish.
|
||||
*/
|
||||
pbkdf2Generate : function pbkdf2Generate(P, S, c, dkLen) {
|
||||
|
||||
// We don't have a default in the algo itself, as NSS does.
|
||||
// Use the constant.
|
||||
if (!dkLen)
|
||||
|
@ -745,7 +742,7 @@ let Utils = {
|
|||
/* For HMAC-SHA-1 */
|
||||
const HLEN = 20;
|
||||
|
||||
function F(PK, S, c, i, h) {
|
||||
function F(S, c, i, h) {
|
||||
|
||||
function XOR(a, b, isA) {
|
||||
if (a.length != b.length) {
|
||||
|
@ -774,9 +771,9 @@ let Utils = {
|
|||
I[2] = String.fromCharCode((i >> 8) & 0xff);
|
||||
I[3] = String.fromCharCode(i & 0xff);
|
||||
|
||||
U[0] = Utils.sha1HMACBytes(S + I.join(''), PK, h);
|
||||
U[0] = Utils.digestBytes(S + I.join(''), h);
|
||||
for (let j = 1; j < c; j++) {
|
||||
U[j] = Utils.sha1HMACBytes(U[j - 1], PK, h);
|
||||
U[j] = Utils.digestBytes(U[j - 1], h);
|
||||
}
|
||||
|
||||
ret = U[0];
|
||||
|
@ -791,12 +788,11 @@ let Utils = {
|
|||
let r = dkLen - ((l - 1) * HLEN);
|
||||
|
||||
// Reuse the key and the hasher. Remaking them 4096 times is 'spensive.
|
||||
let PK = Utils.makeHMACKey(P);
|
||||
let h = Utils.makeHMACHasher();
|
||||
let h = Utils.makeHMACHasher(Ci.nsICryptoHMAC.SHA1, Utils.makeHMACKey(P));
|
||||
|
||||
T = [];
|
||||
for (let i = 0; i < l;) {
|
||||
T[i] = F(PK, S, c, ++i, h);
|
||||
T[i] = F(S, c, ++i, h);
|
||||
}
|
||||
|
||||
let ret = '';
|
||||
|
@ -1153,10 +1149,7 @@ let Utils = {
|
|||
let fos = Cc["@mozilla.org/network/safe-file-output-stream;1"]
|
||||
.createInstance(Ci.nsIFileOutputStream);
|
||||
fos.init(file, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE, PERMS_FILE, 0);
|
||||
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
let is = converter.convertToInputStream(out);
|
||||
let is = this._utf8Converter.convertToInputStream(out);
|
||||
NetUtil.asyncCopy(is, fos, function (result) {
|
||||
if (typeof callback == "function") {
|
||||
callback.call(that);
|
||||
|
@ -1289,11 +1282,8 @@ let Utils = {
|
|||
|
||||
encodeUTF8: function(str) {
|
||||
try {
|
||||
var unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
unicodeConverter.charset = "UTF-8";
|
||||
str = unicodeConverter.ConvertFromUnicode(str);
|
||||
return str + unicodeConverter.Finish();
|
||||
str = this._utf8Converter.ConvertFromUnicode(str);
|
||||
return str + this._utf8Converter.Finish();
|
||||
} catch(ex) {
|
||||
return null;
|
||||
}
|
||||
|
@ -1301,11 +1291,8 @@ let Utils = {
|
|||
|
||||
decodeUTF8: function(str) {
|
||||
try {
|
||||
var unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
unicodeConverter.charset = "UTF-8";
|
||||
str = unicodeConverter.ConvertToUnicode(str);
|
||||
return str + unicodeConverter.Finish();
|
||||
str = this._utf8Converter.ConvertToUnicode(str);
|
||||
return str + this._utf8Converter.Finish();
|
||||
} catch(ex) {
|
||||
return null;
|
||||
}
|
||||
|
@ -1645,6 +1632,12 @@ let FakeSvc = {
|
|||
isFake: true
|
||||
}
|
||||
};
|
||||
Utils.lazy2(Utils, "_utf8Converter", function() {
|
||||
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
return converter;
|
||||
});
|
||||
|
||||
/*
|
||||
* Commonly-used services
|
||||
|
|
|
@ -187,10 +187,13 @@ function FakeCryptoService() {
|
|||
delete Svc.Crypto; // get rid of the getter first
|
||||
Svc.Crypto = this;
|
||||
Utils.sha256HMAC = this.sha256HMAC;
|
||||
|
||||
Cu.import("resource://services-sync/record.js");
|
||||
CryptoWrapper.prototype.ciphertextHMAC = this.ciphertextHMAC;
|
||||
}
|
||||
FakeCryptoService.prototype = {
|
||||
|
||||
sha256HMAC: function(message, key) {
|
||||
sha256HMAC: function Utils_sha256HMAC(message, hasher) {
|
||||
message = message.substr(0, 64);
|
||||
while (message.length < 64) {
|
||||
message += " ";
|
||||
|
@ -198,6 +201,10 @@ FakeCryptoService.prototype = {
|
|||
return message;
|
||||
},
|
||||
|
||||
ciphertextHMAC: function CryptoWrapper_ciphertextHMAC(keyBundle) {
|
||||
return Utils.sha256HMAC(this.ciphertext);
|
||||
},
|
||||
|
||||
encrypt: function(aClearText, aSymmetricKey, aIV) {
|
||||
return aClearText;
|
||||
},
|
||||
|
|
|
@ -2,8 +2,8 @@ _("Make sure sha256 hmac works with various messages and keys");
|
|||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
let key1 = Svc.KeyFactory.keyFromString(Ci.nsIKeyObject.HMAC, "key1");
|
||||
let key2 = Svc.KeyFactory.keyFromString(Ci.nsIKeyObject.HMAC, "key2");
|
||||
let key1 = Utils.makeHMACKey("key1");
|
||||
let key2 = Utils.makeHMACKey("key2");
|
||||
|
||||
let mes1 = "message 1";
|
||||
let mes2 = "message 2";
|
||||
|
|
Загрузка…
Ссылка в новой задаче