mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-25 11:58:55 +00:00
Bug 745396 - Refactor Sync utility functions into services-{common,crypto}; r=rnewman
This commit is contained in:
parent
52ab59508d
commit
9139619075
@ -4,6 +4,9 @@
|
||||
|
||||
Cu.import("resource://services-common/log4moz.js");
|
||||
|
||||
let btoa = Cu.import("resource://services-common/log4moz.js").btoa;
|
||||
let atob = Cu.import("resource://services-common/log4moz.js").atob;
|
||||
|
||||
function do_check_empty(obj) {
|
||||
do_check_attribute_count(obj, 0);
|
||||
}
|
||||
|
51
services/common/tests/unit/test_utils_encodeBase32.js
Normal file
51
services/common/tests/unit/test_utils_encodeBase32.js
Normal file
@ -0,0 +1,51 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
|
||||
function run_test() {
|
||||
// Testing byte array manipulation.
|
||||
do_check_eq("FOOBAR", CommonUtils.byteArrayToString([70, 79, 79, 66, 65, 82]));
|
||||
do_check_eq("", CommonUtils.byteArrayToString([]));
|
||||
|
||||
_("Testing encoding...");
|
||||
// Test vectors from RFC 4648
|
||||
do_check_eq(CommonUtils.encodeBase32(""), "");
|
||||
do_check_eq(CommonUtils.encodeBase32("f"), "MY======");
|
||||
do_check_eq(CommonUtils.encodeBase32("fo"), "MZXQ====");
|
||||
do_check_eq(CommonUtils.encodeBase32("foo"), "MZXW6===");
|
||||
do_check_eq(CommonUtils.encodeBase32("foob"), "MZXW6YQ=");
|
||||
do_check_eq(CommonUtils.encodeBase32("fooba"), "MZXW6YTB");
|
||||
do_check_eq(CommonUtils.encodeBase32("foobar"), "MZXW6YTBOI======");
|
||||
|
||||
do_check_eq(CommonUtils.encodeBase32("Bacon is a vegetable."),
|
||||
"IJQWG33OEBUXGIDBEB3GKZ3FORQWE3DFFY======");
|
||||
|
||||
_("Checking assumptions...");
|
||||
for (let i = 0; i <= 255; ++i)
|
||||
do_check_eq(undefined | i, i);
|
||||
|
||||
_("Testing decoding...");
|
||||
do_check_eq(CommonUtils.decodeBase32(""), "");
|
||||
do_check_eq(CommonUtils.decodeBase32("MY======"), "f");
|
||||
do_check_eq(CommonUtils.decodeBase32("MZXQ===="), "fo");
|
||||
do_check_eq(CommonUtils.decodeBase32("MZXW6YTB"), "fooba");
|
||||
do_check_eq(CommonUtils.decodeBase32("MZXW6YTBOI======"), "foobar");
|
||||
|
||||
// Same with incorrect or missing padding.
|
||||
do_check_eq(CommonUtils.decodeBase32("MZXW6YTBOI=="), "foobar");
|
||||
do_check_eq(CommonUtils.decodeBase32("MZXW6YTBOI"), "foobar");
|
||||
|
||||
let encoded = CommonUtils.encodeBase32("Bacon is a vegetable.");
|
||||
_("Encoded to " + JSON.stringify(encoded));
|
||||
do_check_eq(CommonUtils.decodeBase32(encoded), "Bacon is a vegetable.");
|
||||
|
||||
// Test failure.
|
||||
let err;
|
||||
try {
|
||||
CommonUtils.decodeBase32("000");
|
||||
} catch (ex) {
|
||||
err = ex;
|
||||
}
|
||||
do_check_eq(err, "Unknown character in base32: 0");
|
||||
}
|
@ -6,6 +6,7 @@ tail =
|
||||
[test_load_modules.js]
|
||||
|
||||
[test_utils_atob.js]
|
||||
[test_utils_encodeBase32.js]
|
||||
[test_utils_makeURI.js]
|
||||
[test_utils_namedTimer.js]
|
||||
[test_utils_stackTrace.js]
|
||||
|
@ -141,6 +141,148 @@ let CommonUtils = {
|
||||
}
|
||||
},
|
||||
|
||||
byteArrayToString: function byteArrayToString(bytes) {
|
||||
return [String.fromCharCode(byte) for each (byte in bytes)].join("");
|
||||
},
|
||||
|
||||
bytesAsHex: function bytesAsHex(bytes) {
|
||||
let hex = "";
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
hex += ("0" + bytes[i].charCodeAt().toString(16)).slice(-2);
|
||||
}
|
||||
return hex;
|
||||
},
|
||||
|
||||
/**
|
||||
* Base32 encode (RFC 4648) a string
|
||||
*/
|
||||
encodeBase32: function encodeBase32(bytes) {
|
||||
const key = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||
let quanta = Math.floor(bytes.length / 5);
|
||||
let leftover = bytes.length % 5;
|
||||
|
||||
// Pad the last quantum with zeros so the length is a multiple of 5.
|
||||
if (leftover) {
|
||||
quanta += 1;
|
||||
for (let i = leftover; i < 5; i++)
|
||||
bytes += "\0";
|
||||
}
|
||||
|
||||
// Chop the string into quanta of 5 bytes (40 bits). Each quantum
|
||||
// is turned into 8 characters from the 32 character base.
|
||||
let ret = "";
|
||||
for (let i = 0; i < bytes.length; i += 5) {
|
||||
let c = [byte.charCodeAt() for each (byte in bytes.slice(i, i + 5))];
|
||||
ret += key[c[0] >> 3]
|
||||
+ key[((c[0] << 2) & 0x1f) | (c[1] >> 6)]
|
||||
+ key[(c[1] >> 1) & 0x1f]
|
||||
+ key[((c[1] << 4) & 0x1f) | (c[2] >> 4)]
|
||||
+ key[((c[2] << 1) & 0x1f) | (c[3] >> 7)]
|
||||
+ key[(c[3] >> 2) & 0x1f]
|
||||
+ key[((c[3] << 3) & 0x1f) | (c[4] >> 5)]
|
||||
+ key[c[4] & 0x1f];
|
||||
}
|
||||
|
||||
switch (leftover) {
|
||||
case 1:
|
||||
return ret.slice(0, -6) + "======";
|
||||
case 2:
|
||||
return ret.slice(0, -4) + "====";
|
||||
case 3:
|
||||
return ret.slice(0, -3) + "===";
|
||||
case 4:
|
||||
return ret.slice(0, -1) + "=";
|
||||
default:
|
||||
return ret;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Base32 decode (RFC 4648) a string.
|
||||
*/
|
||||
decodeBase32: function decodeBase32(str) {
|
||||
const key = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||
|
||||
let padChar = str.indexOf("=");
|
||||
let chars = (padChar == -1) ? str.length : padChar;
|
||||
let bytes = Math.floor(chars * 5 / 8);
|
||||
let blocks = Math.ceil(chars / 8);
|
||||
|
||||
// Process a chunk of 5 bytes / 8 characters.
|
||||
// The processing of this is known in advance,
|
||||
// so avoid arithmetic!
|
||||
function processBlock(ret, cOffset, rOffset) {
|
||||
let c, val;
|
||||
|
||||
// N.B., this relies on
|
||||
// undefined | foo == foo.
|
||||
function accumulate(val) {
|
||||
ret[rOffset] |= val;
|
||||
}
|
||||
|
||||
function advance() {
|
||||
c = str[cOffset++];
|
||||
if (!c || c == "" || c == "=") // Easier than range checking.
|
||||
throw "Done"; // Will be caught far away.
|
||||
val = key.indexOf(c);
|
||||
if (val == -1)
|
||||
throw "Unknown character in base32: " + c;
|
||||
}
|
||||
|
||||
// Handle a left shift, restricted to bytes.
|
||||
function left(octet, shift)
|
||||
(octet << shift) & 0xff;
|
||||
|
||||
advance();
|
||||
accumulate(left(val, 3));
|
||||
advance();
|
||||
accumulate(val >> 2);
|
||||
++rOffset;
|
||||
accumulate(left(val, 6));
|
||||
advance();
|
||||
accumulate(left(val, 1));
|
||||
advance();
|
||||
accumulate(val >> 4);
|
||||
++rOffset;
|
||||
accumulate(left(val, 4));
|
||||
advance();
|
||||
accumulate(val >> 1);
|
||||
++rOffset;
|
||||
accumulate(left(val, 7));
|
||||
advance();
|
||||
accumulate(left(val, 2));
|
||||
advance();
|
||||
accumulate(val >> 3);
|
||||
++rOffset;
|
||||
accumulate(left(val, 5));
|
||||
advance();
|
||||
accumulate(val);
|
||||
++rOffset;
|
||||
}
|
||||
|
||||
// Our output. Define to be explicit (and maybe the compiler will be smart).
|
||||
let ret = new Array(bytes);
|
||||
let i = 0;
|
||||
let cOff = 0;
|
||||
let rOff = 0;
|
||||
|
||||
for (; i < blocks; ++i) {
|
||||
try {
|
||||
processBlock(ret, cOff, rOff);
|
||||
} catch (ex) {
|
||||
// Handle the detection of padding.
|
||||
if (ex == "Done")
|
||||
break;
|
||||
throw ex;
|
||||
}
|
||||
cOff += 8;
|
||||
rOff += 5;
|
||||
}
|
||||
|
||||
// Slice in case our shift overflowed to the right.
|
||||
return CommonUtils.byteArrayToString(ret.slice(0, bytes));
|
||||
},
|
||||
|
||||
/**
|
||||
* Trim excess padding from a Base64 string and atob().
|
||||
*
|
||||
|
362
services/crypto/modules/utils.js
Normal file
362
services/crypto/modules/utils.js
Normal file
@ -0,0 +1,362 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
|
||||
|
||||
const EXPORTED_SYMBOLS = ["CryptoUtils"];
|
||||
|
||||
Cu.import("resource://services-common/observers.js");
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
let CryptoUtils = {
|
||||
/**
|
||||
* Generate a string of random bytes.
|
||||
*/
|
||||
generateRandomBytes: function generateRandomBytes(length) {
|
||||
let rng = Cc["@mozilla.org/security/random-generator;1"]
|
||||
.createInstance(Ci.nsIRandomGenerator);
|
||||
let bytes = rng.generateRandomBytes(length);
|
||||
return CommonUtils.byteArrayToString(bytes);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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);
|
||||
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;
|
||||
},
|
||||
|
||||
_sha1: function _sha1(message) {
|
||||
let hasher = Cc["@mozilla.org/security/hash;1"]
|
||||
.createInstance(Ci.nsICryptoHash);
|
||||
hasher.init(hasher.SHA1);
|
||||
return CryptoUtils.digestUTF8(message, hasher);
|
||||
},
|
||||
|
||||
sha1: function sha1(message) {
|
||||
return CommonUtils.bytesAsHex(CryptoUtils._sha1(message));
|
||||
},
|
||||
|
||||
sha1Base32: function sha1Base32(message) {
|
||||
return CommonUtils.encodeBase32(CryptoUtils._sha1(message));
|
||||
},
|
||||
|
||||
/**
|
||||
* Produce an HMAC key object from a key string.
|
||||
*/
|
||||
makeHMACKey: function makeHMACKey(str) {
|
||||
return Svc.KeyFactory.keyFromString(Ci.nsIKeyObject.HMAC, str);
|
||||
},
|
||||
|
||||
/**
|
||||
* Produce an HMAC hasher and initialize it with the given HMAC key.
|
||||
*/
|
||||
makeHMACHasher: function makeHMACHasher(type, key) {
|
||||
let hasher = Cc["@mozilla.org/security/hmac;1"]
|
||||
.createInstance(Ci.nsICryptoHMAC);
|
||||
hasher.init(type, key);
|
||||
return hasher;
|
||||
},
|
||||
|
||||
/**
|
||||
* HMAC-based Key Derivation Step 2 according to RFC 5869.
|
||||
*/
|
||||
hkdfExpand: function hkdfExpand(prk, info, len) {
|
||||
const BLOCKSIZE = 256 / 8;
|
||||
let h = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256,
|
||||
CryptoUtils.makeHMACKey(prk));
|
||||
let T = "";
|
||||
let Tn = "";
|
||||
let iterations = Math.ceil(len/BLOCKSIZE);
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
Tn = CryptoUtils.digestBytes(Tn + info + String.fromCharCode(i + 1), h);
|
||||
T += Tn;
|
||||
}
|
||||
return T.slice(0, len);
|
||||
},
|
||||
|
||||
/**
|
||||
* PBKDF2 implementation in Javascript.
|
||||
*
|
||||
* The arguments to this function correspond to items in
|
||||
* PKCS #5, v2.0 pp. 9-10
|
||||
*
|
||||
* P: the passphrase, an octet string: e.g., "secret phrase"
|
||||
* S: the salt, an octet string: e.g., "DNXPzPpiwn"
|
||||
* c: the number of iterations, a positive integer: e.g., 4096
|
||||
* dkLen: the length in octets of the destination
|
||||
* key, a positive integer: e.g., 16
|
||||
*
|
||||
* The output is an octet string of length dkLen, which you
|
||||
* 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) {
|
||||
dkLen = SYNC_KEY_DECODED_LENGTH;
|
||||
}
|
||||
|
||||
/* For HMAC-SHA-1 */
|
||||
const HLEN = 20;
|
||||
|
||||
function F(S, c, i, h) {
|
||||
|
||||
function XOR(a, b, isA) {
|
||||
if (a.length != b.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let val = [];
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (isA) {
|
||||
val[i] = a[i] ^ b[i];
|
||||
} else {
|
||||
val[i] = a.charCodeAt(i) ^ b.charCodeAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
let ret;
|
||||
let U = [];
|
||||
|
||||
/* Encode i into 4 octets: _INT */
|
||||
let I = [];
|
||||
I[0] = String.fromCharCode((i >> 24) & 0xff);
|
||||
I[1] = String.fromCharCode((i >> 16) & 0xff);
|
||||
I[2] = String.fromCharCode((i >> 8) & 0xff);
|
||||
I[3] = String.fromCharCode(i & 0xff);
|
||||
|
||||
U[0] = CryptoUtils.digestBytes(S + I.join(''), h);
|
||||
for (let j = 1; j < c; j++) {
|
||||
U[j] = CryptoUtils.digestBytes(U[j - 1], h);
|
||||
}
|
||||
|
||||
ret = U[0];
|
||||
for (j = 1; j < c; j++) {
|
||||
ret = CommonUtils.byteArrayToString(XOR(ret, U[j]));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
let l = Math.ceil(dkLen / HLEN);
|
||||
let r = dkLen - ((l - 1) * HLEN);
|
||||
|
||||
// Reuse the key and the hasher. Remaking them 4096 times is 'spensive.
|
||||
let h = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA1,
|
||||
CryptoUtils.makeHMACKey(P));
|
||||
|
||||
T = [];
|
||||
for (let i = 0; i < l;) {
|
||||
T[i] = F(S, c, ++i, h);
|
||||
}
|
||||
|
||||
let ret = "";
|
||||
for (i = 0; i < l-1;) {
|
||||
ret += T[i++];
|
||||
}
|
||||
ret += T[l - 1].substr(0, r);
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
deriveKeyFromPassphrase: function deriveKeyFromPassphrase(passphrase,
|
||||
salt,
|
||||
keyLength,
|
||||
forceJS) {
|
||||
if (Svc.Crypto.deriveKeyFromPassphrase && !forceJS) {
|
||||
return Svc.Crypto.deriveKeyFromPassphrase(passphrase, salt, keyLength);
|
||||
}
|
||||
else {
|
||||
// Fall back to JS implementation.
|
||||
// 4096 is hardcoded in WeaveCrypto, so do so here.
|
||||
return CryptoUtils.pbkdf2Generate(passphrase, atob(salt), 4096,
|
||||
keyLength);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Compute the HTTP MAC SHA-1 for an HTTP request.
|
||||
*
|
||||
* @param identifier
|
||||
* (string) MAC Key Identifier.
|
||||
* @param key
|
||||
* (string) MAC Key.
|
||||
* @param method
|
||||
* (string) HTTP request method.
|
||||
* @param URI
|
||||
* (nsIURI) HTTP request URI.
|
||||
* @param extra
|
||||
* (object) Optional extra parameters. Valid keys are:
|
||||
* nonce_bytes - How many bytes the nonce should be. This defaults
|
||||
* to 8. Note that this many bytes are Base64 encoded, so the
|
||||
* string length of the nonce will be longer than this value.
|
||||
* ts - Timestamp to use. Should only be defined for testing.
|
||||
* nonce - String nonce. Should only be defined for testing as this
|
||||
* function will generate a cryptographically secure random one
|
||||
* if not defined.
|
||||
* ext - Extra string to be included in MAC. Per the HTTP MAC spec,
|
||||
* the format is undefined and thus application specific.
|
||||
* @returns
|
||||
* (object) Contains results of operation and input arguments (for
|
||||
* symmetry). The object has the following keys:
|
||||
*
|
||||
* identifier - (string) MAC Key Identifier (from arguments).
|
||||
* key - (string) MAC Key (from arguments).
|
||||
* method - (string) HTTP request method (from arguments).
|
||||
* hostname - (string) HTTP hostname used (derived from arguments).
|
||||
* port - (string) HTTP port number used (derived from arguments).
|
||||
* mac - (string) Raw HMAC digest bytes.
|
||||
* getHeader - (function) Call to obtain the string Authorization
|
||||
* header value for this invocation.
|
||||
* nonce - (string) Nonce value used.
|
||||
* ts - (number) Integer seconds since Unix epoch that was used.
|
||||
*/
|
||||
computeHTTPMACSHA1: function computeHTTPMACSHA1(identifier, key, method,
|
||||
uri, extra) {
|
||||
let ts = (extra && extra.ts) ? extra.ts : Math.floor(Date.now() / 1000);
|
||||
let nonce_bytes = (extra && extra.nonce_bytes > 0) ? extra.nonce_bytes : 8;
|
||||
|
||||
// We are allowed to use more than the Base64 alphabet if we want.
|
||||
let nonce = (extra && extra.nonce)
|
||||
? extra.nonce
|
||||
: btoa(CryptoUtils.generateRandomBytes(nonce_bytes));
|
||||
|
||||
let host = uri.asciiHost;
|
||||
let port;
|
||||
let usedMethod = method.toUpperCase();
|
||||
|
||||
if (uri.port != -1) {
|
||||
port = uri.port;
|
||||
} else if (uri.scheme == "http") {
|
||||
port = "80";
|
||||
} else if (uri.scheme == "https") {
|
||||
port = "443";
|
||||
} else {
|
||||
throw new Error("Unsupported URI scheme: " + uri.scheme);
|
||||
}
|
||||
|
||||
let ext = (extra && extra.ext) ? extra.ext : "";
|
||||
|
||||
let requestString = ts.toString(10) + "\n" +
|
||||
nonce + "\n" +
|
||||
usedMethod + "\n" +
|
||||
uri.path + "\n" +
|
||||
host + "\n" +
|
||||
port + "\n" +
|
||||
ext + "\n";
|
||||
|
||||
let hasher = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA1,
|
||||
CryptoUtils.makeHMACKey(key));
|
||||
let mac = CryptoUtils.digestBytes(requestString, hasher);
|
||||
|
||||
function getHeader() {
|
||||
return CryptoUtils.getHTTPMACSHA1Header(this.identifier, this.ts,
|
||||
this.nonce, this.mac, this.ext);
|
||||
}
|
||||
|
||||
return {
|
||||
identifier: identifier,
|
||||
key: key,
|
||||
method: usedMethod,
|
||||
hostname: host,
|
||||
port: port,
|
||||
mac: mac,
|
||||
nonce: nonce,
|
||||
ts: ts,
|
||||
ext: ext,
|
||||
getHeader: getHeader
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Obtain the HTTP MAC Authorization header value from fields.
|
||||
*
|
||||
* @param identifier
|
||||
* (string) MAC key identifier.
|
||||
* @param ts
|
||||
* (number) Integer seconds since Unix epoch.
|
||||
* @param nonce
|
||||
* (string) Nonce value.
|
||||
* @param mac
|
||||
* (string) Computed HMAC digest (raw bytes).
|
||||
* @param ext
|
||||
* (optional) (string) Extra string content.
|
||||
* @returns
|
||||
* (string) Value to put in Authorization header.
|
||||
*/
|
||||
getHTTPMACSHA1Header: function getHTTPMACSHA1Header(identifier, ts, nonce,
|
||||
mac, ext) {
|
||||
let header ='MAC id="' + identifier + '", ' +
|
||||
'ts="' + ts + '", ' +
|
||||
'nonce="' + nonce + '", ' +
|
||||
'mac="' + btoa(mac) + '"';
|
||||
|
||||
if (!ext) {
|
||||
return header;
|
||||
}
|
||||
|
||||
return header += ', ext="' + ext +'"';
|
||||
},
|
||||
};
|
||||
|
||||
XPCOMUtils.defineLazyGetter(CryptoUtils, "_utf8Converter", function() {
|
||||
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
|
||||
return converter;
|
||||
});
|
||||
|
||||
let Svc = {};
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(Svc,
|
||||
"KeyFactory",
|
||||
"@mozilla.org/security/keyobjectfactory;1",
|
||||
"nsIKeyObjectFactory");
|
||||
|
||||
Svc.__defineGetter__("Crypto", function() {
|
||||
let ns = {};
|
||||
Cu.import("resource://services-crypto/WeaveCrypto.js", ns);
|
||||
|
||||
let wc = new ns.WeaveCrypto();
|
||||
delete Svc.Crypto;
|
||||
return Svc.Crypto = wc;
|
||||
});
|
||||
|
||||
Observers.add("xpcom-shutdown", function unloadServices() {
|
||||
Observers.remove("xpcom-shutdown", unloadServices);
|
||||
|
||||
for (let k in Svc) {
|
||||
delete Svc[k];
|
||||
}
|
||||
});
|
16
services/crypto/tests/unit/test_load_modules.js
Normal file
16
services/crypto/tests/unit/test_load_modules.js
Normal file
@ -0,0 +1,16 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const modules = [
|
||||
"utils.js",
|
||||
"WeaveCrypto.js",
|
||||
];
|
||||
|
||||
function run_test() {
|
||||
for each (let m in modules) {
|
||||
let resource = "resource://services-crypto/" + m;
|
||||
_("Attempting to import: " + resource);
|
||||
Components.utils.import(resource, {});
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,8 @@
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
Cu.import("resource://services-crypto/utils.js");
|
||||
|
||||
// Test vectors from RFC 5869
|
||||
|
||||
@ -60,8 +64,8 @@ let tc3 = {
|
||||
};
|
||||
|
||||
function sha256HMAC(message, key) {
|
||||
let h = Utils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256, key);
|
||||
return Utils.digestBytes(message, h);
|
||||
let h = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256, key);
|
||||
return CryptoUtils.digestBytes(message, h);
|
||||
}
|
||||
|
||||
function _hexToString(hex) {
|
||||
@ -80,13 +84,13 @@ function _hexToString(hex) {
|
||||
function extract_hex(salt, ikm) {
|
||||
salt = _hexToString(salt);
|
||||
ikm = _hexToString(ikm);
|
||||
return Utils.bytesAsHex(sha256HMAC(ikm, Utils.makeHMACKey(salt)));
|
||||
return CommonUtils.bytesAsHex(sha256HMAC(ikm, CryptoUtils.makeHMACKey(salt)));
|
||||
}
|
||||
|
||||
function expand_hex(prk, info, len) {
|
||||
prk = _hexToString(prk);
|
||||
info = _hexToString(info);
|
||||
return Utils.bytesAsHex(Utils.hkdfExpand(prk, info, len));
|
||||
return CommonUtils.bytesAsHex(CryptoUtils.hkdfExpand(prk, info, len));
|
||||
}
|
||||
|
||||
function run_test() {
|
@ -2,7 +2,8 @@
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
Cu.import("resource://services-crypto/utils.js");
|
||||
|
||||
function run_test() {
|
||||
initTestLogging();
|
||||
@ -18,10 +19,10 @@ add_test(function test_sha1() {
|
||||
let ts = 1329181221;
|
||||
let method = "GET";
|
||||
let nonce = "wGX71";
|
||||
let uri = Utils.makeURI("http://10.250.2.176/alias/");
|
||||
let uri = CommonUtils.makeURI("http://10.250.2.176/alias/");
|
||||
|
||||
let result = Utils.computeHTTPMACSHA1(id, key, method, uri, {ts: ts,
|
||||
nonce: nonce});
|
||||
let result = CryptoUtils.computeHTTPMACSHA1(id, key, method, uri,
|
||||
{ts: ts, nonce: nonce});
|
||||
|
||||
do_check_eq(btoa(result.mac), "jzh5chjQc2zFEvLbyHnPdX11Yck=");
|
||||
|
||||
@ -31,9 +32,8 @@ add_test(function test_sha1() {
|
||||
|
||||
let ext = "EXTRA DATA; foo,bar=1";
|
||||
|
||||
let result = Utils.computeHTTPMACSHA1(id, key, method, uri, {ts: ts,
|
||||
nonce: nonce,
|
||||
ext: ext});
|
||||
let result = CryptoUtils.computeHTTPMACSHA1(id, key, method, uri,
|
||||
{ts: ts, nonce: nonce, ext: ext});
|
||||
do_check_eq(btoa(result.mac), "bNf4Fnt5k6DnhmyipLPkuZroH68=");
|
||||
do_check_eq(result.getHeader(),
|
||||
'MAC id="vmo1txkttblmn51u2p3zk2xiy16hgvm5ok8qiv1yyi86ffjzy9zj0ez9x6wnvbx7", ' +
|
||||
@ -47,8 +47,8 @@ add_test(function test_nonce_length() {
|
||||
_("Ensure custom nonce lengths are honoured.");
|
||||
|
||||
function get_mac(length) {
|
||||
let uri = Utils.makeURI("http://example.com/");
|
||||
return Utils.computeHTTPMACSHA1("foo", "bar", "GET", uri, {
|
||||
let uri = CommonUtils.makeURI("http://example.com/");
|
||||
return CryptoUtils.computeHTTPMACSHA1("foo", "bar", "GET", uri, {
|
||||
nonce_bytes: length
|
||||
});
|
||||
}
|
15
services/crypto/tests/unit/test_utils_pbkdf2.js
Normal file
15
services/crypto/tests/unit/test_utils_pbkdf2.js
Normal file
@ -0,0 +1,15 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Evil.
|
||||
let btoa = Cu.import("resource://services-common/utils.js").btoa;
|
||||
Cu.import("resource://services-crypto/utils.js");
|
||||
|
||||
function run_test() {
|
||||
let symmKey16 = CryptoUtils.pbkdf2Generate("secret phrase", "DNXPzPpiwn", 4096, 16);
|
||||
do_check_eq(symmKey16.length, 16);
|
||||
do_check_eq(btoa(symmKey16), "d2zG0d2cBfXnRwMUGyMwyg==");
|
||||
do_check_eq(CommonUtils.encodeBase32(symmKey16), "O5WMNUO5TQC7LZ2HAMKBWIZQZI======");
|
||||
let symmKey32 = CryptoUtils.pbkdf2Generate("passphrase", "salt", 4096, 32);
|
||||
do_check_eq(symmKey32.length, 32);
|
||||
}
|
33
services/crypto/tests/unit/test_utils_sha1.js
Normal file
33
services/crypto/tests/unit/test_utils_sha1.js
Normal file
@ -0,0 +1,33 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
_("Make sure sha1 digests works with various messages");
|
||||
|
||||
Cu.import("resource://services-crypto/utils.js");
|
||||
|
||||
function run_test() {
|
||||
let mes1 = "hello";
|
||||
let mes2 = "world";
|
||||
|
||||
_("Make sure right sha1 digests are generated");
|
||||
let dig1 = CryptoUtils.sha1(mes1);
|
||||
do_check_eq(dig1, "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d");
|
||||
let dig2 = CryptoUtils.sha1(mes2);
|
||||
do_check_eq(dig2, "7c211433f02071597741e6ff5a8ea34789abbf43");
|
||||
let dig12 = CryptoUtils.sha1(mes1 + mes2);
|
||||
do_check_eq(dig12, "6adfb183a4a2c94a2f92dab5ade762a47889a5a1");
|
||||
let dig21 = CryptoUtils.sha1(mes2 + mes1);
|
||||
do_check_eq(dig21, "5715790a892990382d98858c4aa38d0617151575");
|
||||
|
||||
_("Repeated sha1s shouldn't change the digest");
|
||||
do_check_eq(CryptoUtils.sha1(mes1), dig1);
|
||||
do_check_eq(CryptoUtils.sha1(mes2), dig2);
|
||||
do_check_eq(CryptoUtils.sha1(mes1 + mes2), dig12);
|
||||
do_check_eq(CryptoUtils.sha1(mes2 + mes1), dig21);
|
||||
|
||||
_("Nested sha1 should work just fine");
|
||||
let nest1 = CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(mes1)))));
|
||||
do_check_eq(nest1, "23f340d0cff31e299158b3181b6bcc7e8c7f985a");
|
||||
let nest2 = CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(CryptoUtils.sha1(mes2)))));
|
||||
do_check_eq(nest2, "1f6453867e3fb9876ae429918a64cdb8dc5ff2d0");
|
||||
}
|
@ -1,9 +1,16 @@
|
||||
[DEFAULT]
|
||||
head = head_helpers.js
|
||||
tail =
|
||||
head = head_helpers.js ../../../common/tests/unit/head_helpers.js
|
||||
tail =
|
||||
|
||||
[test_load_modules.js]
|
||||
|
||||
[test_crypto_crypt.js]
|
||||
[test_crypto_deriveKey.js]
|
||||
[test_crypto_random.js]
|
||||
# Bug 676977: test hangs consistently on Android
|
||||
skip-if = os == "android"
|
||||
|
||||
[test_utils_hkdfExpand.js]
|
||||
[test_utils_httpmac.js]
|
||||
[test_utils_pbkdf2.js]
|
||||
[test_utils_sha1.js]
|
||||
|
@ -41,12 +41,13 @@ const EXPORTED_SYMBOLS = ["XPCOMUtils", "Services", "NetUtil", "PlacesUtils",
|
||||
const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://services-common/log4moz.js");
|
||||
Cu.import("resource://services-common/observers.js");
|
||||
Cu.import("resource://services-common/preferences.js");
|
||||
Cu.import("resource://services-common/stringbundle.js");
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
Cu.import("resource://services-common/async.js");
|
||||
Cu.import("resource://services-crypto/utils.js");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-common/observers.js");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/PlacesUtils.jsm");
|
||||
@ -68,6 +69,24 @@ let Utils = {
|
||||
encodeUTF8: CommonUtils.encodeUTF8,
|
||||
decodeUTF8: CommonUtils.decodeUTF8,
|
||||
safeAtoB: CommonUtils.safeAtoB,
|
||||
byteArrayToString: CommonUtils.byteArrayToString,
|
||||
bytesAsHex: CommonUtils.bytesAsHex,
|
||||
encodeBase32: CommonUtils.encodeBase32,
|
||||
decodeBase32: CommonUtils.decodeBase32,
|
||||
|
||||
// Aliases from CryptoUtils.
|
||||
generateRandomBytes: CryptoUtils.generateRandomBytes,
|
||||
computeHTTPMACSHA1: CryptoUtils.computeHTTPMACSHA1,
|
||||
digestUTF8: CryptoUtils.digestUTF8,
|
||||
digestBytes: CryptoUtils.digestBytes,
|
||||
sha1: CryptoUtils.sha1,
|
||||
sha1Base32: CryptoUtils.sha1Base32,
|
||||
makeHMACKey: CryptoUtils.makeHMACKey,
|
||||
makeHMACHasher: CryptoUtils.makeHMACHasher,
|
||||
hkdfExpand: CryptoUtils.hkdfExpand,
|
||||
pbkdf2Generate: CryptoUtils.pbkdf2Generate,
|
||||
deriveKeyFromPassphrase: CryptoUtils.deriveKeyFromPassphrase,
|
||||
getHTTPMACSHA1Header: CryptoUtils.getHTTPMACSHA1Header,
|
||||
|
||||
/**
|
||||
* Wrap a function to catch all exceptions and log them
|
||||
@ -181,20 +200,6 @@ let Utils = {
|
||||
}
|
||||
},
|
||||
|
||||
byteArrayToString: function byteArrayToString(bytes) {
|
||||
return [String.fromCharCode(byte) for each (byte in bytes)].join("");
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate a string of random bytes.
|
||||
*/
|
||||
generateRandomBytes: function generateRandomBytes(length) {
|
||||
let rng = Cc["@mozilla.org/security/random-generator;1"]
|
||||
.createInstance(Ci.nsIRandomGenerator);
|
||||
let bytes = rng.generateRandomBytes(length);
|
||||
return Utils.byteArrayToString(bytes);
|
||||
},
|
||||
|
||||
/**
|
||||
* Encode byte string as base64url (RFC 4648).
|
||||
*/
|
||||
@ -290,310 +295,6 @@ let Utils = {
|
||||
return ex && ex.indexOf && (ex.indexOf(hmacFail) == 0);
|
||||
},
|
||||
|
||||
/**
|
||||
* 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);
|
||||
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) {
|
||||
let hex = "";
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
hex += ("0" + bytes[i].charCodeAt().toString(16)).slice(-2);
|
||||
}
|
||||
return hex;
|
||||
},
|
||||
|
||||
_sha1: function _sha1(message) {
|
||||
let hasher = Cc["@mozilla.org/security/hash;1"].
|
||||
createInstance(Ci.nsICryptoHash);
|
||||
hasher.init(hasher.SHA1);
|
||||
return Utils.digestUTF8(message, hasher);
|
||||
},
|
||||
|
||||
sha1: function sha1(message) {
|
||||
return Utils.bytesAsHex(Utils._sha1(message));
|
||||
},
|
||||
|
||||
sha1Base32: function sha1Base32(message) {
|
||||
return Utils.encodeBase32(Utils._sha1(message));
|
||||
},
|
||||
|
||||
/**
|
||||
* Produce an HMAC key object from a key string.
|
||||
*/
|
||||
makeHMACKey: function makeHMACKey(str) {
|
||||
return Svc.KeyFactory.keyFromString(Ci.nsIKeyObject.HMAC, str);
|
||||
},
|
||||
|
||||
/**
|
||||
* Produce an HMAC hasher and initialize it with the given HMAC key.
|
||||
*/
|
||||
makeHMACHasher: function makeHMACHasher(type, key) {
|
||||
let hasher = Cc["@mozilla.org/security/hmac;1"]
|
||||
.createInstance(Ci.nsICryptoHMAC);
|
||||
hasher.init(type, key);
|
||||
return hasher;
|
||||
},
|
||||
|
||||
/**
|
||||
* HMAC-based Key Derivation Step 2 according to RFC 5869.
|
||||
*/
|
||||
hkdfExpand: function hkdfExpand(prk, info, len) {
|
||||
const BLOCKSIZE = 256 / 8;
|
||||
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.digestBytes(Tn + info + String.fromCharCode(i + 1), h);
|
||||
T += Tn;
|
||||
}
|
||||
return T.slice(0, len);
|
||||
},
|
||||
|
||||
/**
|
||||
* PBKDF2 implementation in Javascript.
|
||||
*
|
||||
* The arguments to this function correspond to items in
|
||||
* PKCS #5, v2.0 pp. 9-10
|
||||
*
|
||||
* P: the passphrase, an octet string: e.g., "secret phrase"
|
||||
* S: the salt, an octet string: e.g., "DNXPzPpiwn"
|
||||
* c: the number of iterations, a positive integer: e.g., 4096
|
||||
* dkLen: the length in octets of the destination
|
||||
* key, a positive integer: e.g., 16
|
||||
*
|
||||
* The output is an octet string of length dkLen, which you
|
||||
* 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)
|
||||
dkLen = SYNC_KEY_DECODED_LENGTH;
|
||||
|
||||
/* For HMAC-SHA-1 */
|
||||
const HLEN = 20;
|
||||
|
||||
function F(S, c, i, h) {
|
||||
|
||||
function XOR(a, b, isA) {
|
||||
if (a.length != b.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let val = [];
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (isA) {
|
||||
val[i] = a[i] ^ b[i];
|
||||
} else {
|
||||
val[i] = a.charCodeAt(i) ^ b.charCodeAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
let ret;
|
||||
let U = [];
|
||||
|
||||
/* Encode i into 4 octets: _INT */
|
||||
let I = [];
|
||||
I[0] = String.fromCharCode((i >> 24) & 0xff);
|
||||
I[1] = String.fromCharCode((i >> 16) & 0xff);
|
||||
I[2] = String.fromCharCode((i >> 8) & 0xff);
|
||||
I[3] = String.fromCharCode(i & 0xff);
|
||||
|
||||
U[0] = Utils.digestBytes(S + I.join(''), h);
|
||||
for (let j = 1; j < c; j++) {
|
||||
U[j] = Utils.digestBytes(U[j - 1], h);
|
||||
}
|
||||
|
||||
ret = U[0];
|
||||
for (j = 1; j < c; j++) {
|
||||
ret = Utils.byteArrayToString(XOR(ret, U[j]));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
let l = Math.ceil(dkLen / HLEN);
|
||||
let r = dkLen - ((l - 1) * HLEN);
|
||||
|
||||
// Reuse the key and the hasher. Remaking them 4096 times is 'spensive.
|
||||
let h = Utils.makeHMACHasher(Ci.nsICryptoHMAC.SHA1, Utils.makeHMACKey(P));
|
||||
|
||||
T = [];
|
||||
for (let i = 0; i < l;) {
|
||||
T[i] = F(S, c, ++i, h);
|
||||
}
|
||||
|
||||
let ret = '';
|
||||
for (i = 0; i < l-1;) {
|
||||
ret += T[i++];
|
||||
}
|
||||
ret += T[l - 1].substr(0, r);
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Base32 decode (RFC 4648) a string.
|
||||
*/
|
||||
decodeBase32: function decodeBase32(str) {
|
||||
const key = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||
|
||||
let padChar = str.indexOf("=");
|
||||
let chars = (padChar == -1) ? str.length : padChar;
|
||||
let bytes = Math.floor(chars * 5 / 8);
|
||||
let blocks = Math.ceil(chars / 8);
|
||||
|
||||
// Process a chunk of 5 bytes / 8 characters.
|
||||
// The processing of this is known in advance,
|
||||
// so avoid arithmetic!
|
||||
function processBlock(ret, cOffset, rOffset) {
|
||||
let c, val;
|
||||
|
||||
// N.B., this relies on
|
||||
// undefined | foo == foo.
|
||||
function accumulate(val) {
|
||||
ret[rOffset] |= val;
|
||||
}
|
||||
|
||||
function advance() {
|
||||
c = str[cOffset++];
|
||||
if (!c || c == "" || c == "=") // Easier than range checking.
|
||||
throw "Done"; // Will be caught far away.
|
||||
val = key.indexOf(c);
|
||||
if (val == -1)
|
||||
throw "Unknown character in base32: " + c;
|
||||
}
|
||||
|
||||
// Handle a left shift, restricted to bytes.
|
||||
function left(octet, shift)
|
||||
(octet << shift) & 0xff;
|
||||
|
||||
advance();
|
||||
accumulate(left(val, 3));
|
||||
advance();
|
||||
accumulate(val >> 2);
|
||||
++rOffset;
|
||||
accumulate(left(val, 6));
|
||||
advance();
|
||||
accumulate(left(val, 1));
|
||||
advance();
|
||||
accumulate(val >> 4);
|
||||
++rOffset;
|
||||
accumulate(left(val, 4));
|
||||
advance();
|
||||
accumulate(val >> 1);
|
||||
++rOffset;
|
||||
accumulate(left(val, 7));
|
||||
advance();
|
||||
accumulate(left(val, 2));
|
||||
advance();
|
||||
accumulate(val >> 3);
|
||||
++rOffset;
|
||||
accumulate(left(val, 5));
|
||||
advance();
|
||||
accumulate(val);
|
||||
++rOffset;
|
||||
}
|
||||
|
||||
// Our output. Define to be explicit (and maybe the compiler will be smart).
|
||||
let ret = new Array(bytes);
|
||||
let i = 0;
|
||||
let cOff = 0;
|
||||
let rOff = 0;
|
||||
|
||||
for (; i < blocks; ++i) {
|
||||
try {
|
||||
processBlock(ret, cOff, rOff);
|
||||
} catch (ex) {
|
||||
// Handle the detection of padding.
|
||||
if (ex == "Done")
|
||||
break;
|
||||
throw ex;
|
||||
}
|
||||
cOff += 8;
|
||||
rOff += 5;
|
||||
}
|
||||
|
||||
// Slice in case our shift overflowed to the right.
|
||||
return Utils.byteArrayToString(ret.slice(0, bytes));
|
||||
},
|
||||
|
||||
/**
|
||||
* Base32 encode (RFC 4648) a string
|
||||
*/
|
||||
encodeBase32: function encodeBase32(bytes) {
|
||||
const key = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||
let quanta = Math.floor(bytes.length / 5);
|
||||
let leftover = bytes.length % 5;
|
||||
|
||||
// Pad the last quantum with zeros so the length is a multiple of 5.
|
||||
if (leftover) {
|
||||
quanta += 1;
|
||||
for (let i = leftover; i < 5; i++)
|
||||
bytes += "\0";
|
||||
}
|
||||
|
||||
// Chop the string into quanta of 5 bytes (40 bits). Each quantum
|
||||
// is turned into 8 characters from the 32 character base.
|
||||
let ret = "";
|
||||
for (let i = 0; i < bytes.length; i += 5) {
|
||||
let c = [byte.charCodeAt() for each (byte in bytes.slice(i, i + 5))];
|
||||
ret += key[c[0] >> 3]
|
||||
+ key[((c[0] << 2) & 0x1f) | (c[1] >> 6)]
|
||||
+ key[(c[1] >> 1) & 0x1f]
|
||||
+ key[((c[1] << 4) & 0x1f) | (c[2] >> 4)]
|
||||
+ key[((c[2] << 1) & 0x1f) | (c[3] >> 7)]
|
||||
+ key[(c[3] >> 2) & 0x1f]
|
||||
+ key[((c[3] << 3) & 0x1f) | (c[4] >> 5)]
|
||||
+ key[c[4] & 0x1f];
|
||||
}
|
||||
|
||||
switch (leftover) {
|
||||
case 1:
|
||||
return ret.slice(0, -6) + "======";
|
||||
case 2:
|
||||
return ret.slice(0, -4) + "====";
|
||||
case 3:
|
||||
return ret.slice(0, -3) + "===";
|
||||
case 4:
|
||||
return ret.slice(0, -1) + "=";
|
||||
default:
|
||||
return ret;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Turn RFC 4648 base32 into our own user-friendly version.
|
||||
* ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
|
||||
@ -612,7 +313,6 @@ let Utils = {
|
||||
.replace("9", 'O', "g");
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Key manipulation.
|
||||
*/
|
||||
@ -635,23 +335,13 @@ let Utils = {
|
||||
return btoa(keyData);
|
||||
},
|
||||
|
||||
deriveKeyFromPassphrase: function deriveKeyFromPassphrase(passphrase, salt, keyLength, forceJS) {
|
||||
if (Svc.Crypto.deriveKeyFromPassphrase && !forceJS) {
|
||||
return Svc.Crypto.deriveKeyFromPassphrase(passphrase, salt, keyLength);
|
||||
}
|
||||
else {
|
||||
// Fall back to JS implementation.
|
||||
// 4096 is hardcoded in WeaveCrypto, so do so here.
|
||||
return Utils.pbkdf2Generate(passphrase, atob(salt), 4096, keyLength);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* N.B., salt should be base64 encoded, even though we have to decode
|
||||
* it later!
|
||||
*/
|
||||
derivePresentableKeyFromPassphrase : function derivePresentableKeyFromPassphrase(passphrase, salt, keyLength, forceJS) {
|
||||
let k = Utils.deriveKeyFromPassphrase(passphrase, salt, keyLength, forceJS);
|
||||
let k = CryptoUtils.deriveKeyFromPassphrase(passphrase, salt, keyLength,
|
||||
forceJS);
|
||||
return Utils.encodeKeyBase32(k);
|
||||
},
|
||||
|
||||
@ -660,7 +350,8 @@ let Utils = {
|
||||
* it later!
|
||||
*/
|
||||
deriveEncodedKeyFromPassphrase : function deriveEncodedKeyFromPassphrase(passphrase, salt, keyLength, forceJS) {
|
||||
let k = Utils.deriveKeyFromPassphrase(passphrase, salt, keyLength, forceJS);
|
||||
let k = CryptoUtils.deriveKeyFromPassphrase(passphrase, salt, keyLength,
|
||||
forceJS);
|
||||
return Utils.base64Key(k);
|
||||
},
|
||||
|
||||
@ -673,130 +364,6 @@ let Utils = {
|
||||
return Utils.encodeKeyBase32(atob(encodedKey));
|
||||
},
|
||||
|
||||
/**
|
||||
* Compute the HTTP MAC SHA-1 for an HTTP request.
|
||||
*
|
||||
* @param identifier
|
||||
* (string) MAC Key Identifier.
|
||||
* @param key
|
||||
* (string) MAC Key.
|
||||
* @param method
|
||||
* (string) HTTP request method.
|
||||
* @param URI
|
||||
* (nsIURI) HTTP request URI.
|
||||
* @param extra
|
||||
* (object) Optional extra parameters. Valid keys are:
|
||||
* nonce_bytes - How many bytes the nonce should be. This defaults
|
||||
* to 8. Note that this many bytes are Base64 encoded, so the
|
||||
* string length of the nonce will be longer than this value.
|
||||
* ts - Timestamp to use. Should only be defined for testing.
|
||||
* nonce - String nonce. Should only be defined for testing as this
|
||||
* function will generate a cryptographically secure random one
|
||||
* if not defined.
|
||||
* ext - Extra string to be included in MAC. Per the HTTP MAC spec,
|
||||
* the format is undefined and thus application specific.
|
||||
* @returns
|
||||
* (object) Contains results of operation and input arguments (for
|
||||
* symmetry). The object has the following keys:
|
||||
*
|
||||
* identifier - (string) MAC Key Identifier (from arguments).
|
||||
* key - (string) MAC Key (from arguments).
|
||||
* method - (string) HTTP request method (from arguments).
|
||||
* hostname - (string) HTTP hostname used (derived from arguments).
|
||||
* port - (string) HTTP port number used (derived from arguments).
|
||||
* mac - (string) Raw HMAC digest bytes.
|
||||
* getHeader - (function) Call to obtain the string Authorization
|
||||
* header value for this invocation.
|
||||
* nonce - (string) Nonce value used.
|
||||
* ts - (number) Integer seconds since Unix epoch that was used.
|
||||
*/
|
||||
computeHTTPMACSHA1: function computeHTTPMACSHA1(identifier, key, method,
|
||||
uri, extra) {
|
||||
let ts = (extra && extra.ts) ? extra.ts : Math.floor(Date.now() / 1000);
|
||||
let nonce_bytes = (extra && extra.nonce_bytes > 0) ? extra.nonce_bytes : 8;
|
||||
|
||||
// We are allowed to use more than the Base64 alphabet if we want.
|
||||
let nonce = (extra && extra.nonce)
|
||||
? extra.nonce
|
||||
: btoa(Utils.generateRandomBytes(nonce_bytes));
|
||||
|
||||
let host = uri.asciiHost;
|
||||
let port;
|
||||
let usedMethod = method.toUpperCase();
|
||||
|
||||
if (uri.port != -1) {
|
||||
port = uri.port;
|
||||
} else if (uri.scheme == "http") {
|
||||
port = "80";
|
||||
} else if (uri.scheme == "https") {
|
||||
port = "443";
|
||||
} else {
|
||||
throw new Error("Unsupported URI scheme: " + uri.scheme);
|
||||
}
|
||||
|
||||
let ext = (extra && extra.ext) ? extra.ext : "";
|
||||
|
||||
let requestString = ts.toString(10) + "\n" +
|
||||
nonce + "\n" +
|
||||
usedMethod + "\n" +
|
||||
uri.path + "\n" +
|
||||
host + "\n" +
|
||||
port + "\n" +
|
||||
ext + "\n";
|
||||
|
||||
let hasher = Utils.makeHMACHasher(Ci.nsICryptoHMAC.SHA1,
|
||||
Utils.makeHMACKey(key));
|
||||
let mac = Utils.digestBytes(requestString, hasher);
|
||||
|
||||
function getHeader() {
|
||||
return Utils.getHTTPMACSHA1Header(this.identifier, this.ts, this.nonce,
|
||||
this.mac, this.ext);
|
||||
}
|
||||
|
||||
return {
|
||||
identifier: identifier,
|
||||
key: key,
|
||||
method: usedMethod,
|
||||
hostname: host,
|
||||
port: port,
|
||||
mac: mac,
|
||||
nonce: nonce,
|
||||
ts: ts,
|
||||
ext: ext,
|
||||
getHeader: getHeader
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Obtain the HTTP MAC Authorization header value from fields.
|
||||
*
|
||||
* @param identifier
|
||||
* (string) MAC key identifier.
|
||||
* @param ts
|
||||
* (number) Integer seconds since Unix epoch.
|
||||
* @param nonce
|
||||
* (string) Nonce value.
|
||||
* @param mac
|
||||
* (string) Computed HMAC digest (raw bytes).
|
||||
* @param ext
|
||||
* (optional) (string) Extra string content.
|
||||
* @returns
|
||||
* (string) Value to put in Authorization header.
|
||||
*/
|
||||
getHTTPMACSHA1Header: function getHTTPMACSHA1Header(identifier, ts, nonce,
|
||||
mac, ext) {
|
||||
let header ='MAC id="' + identifier + '", ' +
|
||||
'ts="' + ts + '", ' +
|
||||
'nonce="' + nonce + '", ' +
|
||||
'mac="' + btoa(mac) + '"';
|
||||
|
||||
if (!ext) {
|
||||
return header;
|
||||
}
|
||||
|
||||
return header += ', ext="' + ext +'"';
|
||||
},
|
||||
|
||||
/**
|
||||
* Load a json object from disk
|
||||
*
|
||||
@ -897,7 +464,7 @@ let Utils = {
|
||||
// Note that this is a different base32 alphabet to the one we use for
|
||||
// other tasks. It's lowercase, uses different letters, and needs to be
|
||||
// decoded with decodeKeyBase32, not just decodeBase32.
|
||||
return Utils.encodeKeyBase32(Utils.generateRandomBytes(16));
|
||||
return Utils.encodeKeyBase32(CryptoUtils.generateRandomBytes(16));
|
||||
},
|
||||
|
||||
/**
|
||||
@ -1084,7 +651,6 @@ let _sessionCID = Services.appinfo.ID == SEAMONKEY_ID ?
|
||||
|
||||
[["Form", "@mozilla.org/satchel/form-history;1", "nsIFormHistory2"],
|
||||
["Idle", "@mozilla.org/widget/idleservice;1", "nsIIdleService"],
|
||||
["KeyFactory", "@mozilla.org/security/keyobjectfactory;1", "nsIKeyObjectFactory"],
|
||||
["Session", _sessionCID, "nsISessionStore"]
|
||||
].forEach(function([name, contract, iface]) {
|
||||
XPCOMUtils.defineLazyServiceGetter(Svc, name, contract, iface);
|
||||
|
@ -1,58 +0,0 @@
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
// Testing byte array manipulation.
|
||||
do_check_eq("FOOBAR", Utils.byteArrayToString([70, 79, 79, 66, 65, 82]));
|
||||
do_check_eq("", Utils.byteArrayToString([]));
|
||||
|
||||
_("Testing encoding...");
|
||||
// Test vectors from RFC 4648
|
||||
do_check_eq(Utils.encodeBase32(""), "");
|
||||
do_check_eq(Utils.encodeBase32("f"), "MY======");
|
||||
do_check_eq(Utils.encodeBase32("fo"), "MZXQ====");
|
||||
do_check_eq(Utils.encodeBase32("foo"), "MZXW6===");
|
||||
do_check_eq(Utils.encodeBase32("foob"), "MZXW6YQ=");
|
||||
do_check_eq(Utils.encodeBase32("fooba"), "MZXW6YTB");
|
||||
do_check_eq(Utils.encodeBase32("foobar"), "MZXW6YTBOI======");
|
||||
|
||||
do_check_eq(Utils.encodeBase32("Bacon is a vegetable."),
|
||||
"IJQWG33OEBUXGIDBEB3GKZ3FORQWE3DFFY======");
|
||||
|
||||
_("Checking assumptions...");
|
||||
for (let i = 0; i <= 255; ++i)
|
||||
do_check_eq(undefined | i, i);
|
||||
|
||||
_("Testing decoding...");
|
||||
do_check_eq(Utils.decodeBase32(""), "");
|
||||
do_check_eq(Utils.decodeBase32("MY======"), "f");
|
||||
do_check_eq(Utils.decodeBase32("MZXQ===="), "fo");
|
||||
do_check_eq(Utils.decodeBase32("MZXW6YTB"), "fooba");
|
||||
do_check_eq(Utils.decodeBase32("MZXW6YTBOI======"), "foobar");
|
||||
|
||||
// Same with incorrect or missing padding.
|
||||
do_check_eq(Utils.decodeBase32("MZXW6YTBOI=="), "foobar");
|
||||
do_check_eq(Utils.decodeBase32("MZXW6YTBOI"), "foobar");
|
||||
|
||||
let encoded = Utils.encodeBase32("Bacon is a vegetable.");
|
||||
_("Encoded to " + JSON.stringify(encoded));
|
||||
do_check_eq(Utils.decodeBase32(encoded), "Bacon is a vegetable.");
|
||||
|
||||
// Test failure.
|
||||
let err;
|
||||
try {
|
||||
Utils.decodeBase32("000");
|
||||
} catch (ex) {
|
||||
err = ex;
|
||||
}
|
||||
do_check_eq(err, "Unknown character in base32: 0");
|
||||
|
||||
// Testing our own variant.
|
||||
do_check_eq(Utils.encodeKeyBase32("foobarbafoobarba"), "mzxw6ytb9jrgcztpn5rgc4tcme");
|
||||
do_check_eq(Utils.decodeKeyBase32("mzxw6ytb9jrgcztpn5rgc4tcme"), "foobarbafoobarba");
|
||||
do_check_eq(
|
||||
Utils.encodeKeyBase32("\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"),
|
||||
"aeaqcaibaeaqcaibaeaqcaibae");
|
||||
do_check_eq(
|
||||
Utils.decodeKeyBase32("aeaqcaibaeaqcaibaeaqcaibae"),
|
||||
"\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01");
|
||||
}
|
15
services/sync/tests/unit/test_utils_keyEncoding.js
Normal file
15
services/sync/tests/unit/test_utils_keyEncoding.js
Normal file
@ -0,0 +1,15 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
do_check_eq(Utils.encodeKeyBase32("foobarbafoobarba"), "mzxw6ytb9jrgcztpn5rgc4tcme");
|
||||
do_check_eq(Utils.decodeKeyBase32("mzxw6ytb9jrgcztpn5rgc4tcme"), "foobarbafoobarba");
|
||||
do_check_eq(
|
||||
Utils.encodeKeyBase32("\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"),
|
||||
"aeaqcaibaeaqcaibaeaqcaibae");
|
||||
do_check_eq(
|
||||
Utils.decodeKeyBase32("aeaqcaibaeaqcaibaeaqcaibae"),
|
||||
"\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01");
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
// Evil.
|
||||
let btoa = Cu.import("resource://services-sync/util.js").btoa;
|
||||
|
||||
function run_test() {
|
||||
let symmKey16 = Utils.pbkdf2Generate("secret phrase", "DNXPzPpiwn", 4096, 16);
|
||||
do_check_eq(symmKey16.length, 16);
|
||||
do_check_eq(btoa(symmKey16), "d2zG0d2cBfXnRwMUGyMwyg==");
|
||||
do_check_eq(Utils.encodeBase32(symmKey16), "O5WMNUO5TQC7LZ2HAMKBWIZQZI======");
|
||||
let symmKey32 = Utils.pbkdf2Generate("passphrase", "salt", 4096, 32);
|
||||
do_check_eq(symmKey32.length, 32);
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
_("Make sure sha1 digests works with various messages");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
let mes1 = "hello";
|
||||
let mes2 = "world";
|
||||
|
||||
_("Make sure right sha1 digests are generated");
|
||||
let dig1 = Utils.sha1(mes1);
|
||||
do_check_eq(dig1, "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d");
|
||||
let dig2 = Utils.sha1(mes2);
|
||||
do_check_eq(dig2, "7c211433f02071597741e6ff5a8ea34789abbf43");
|
||||
let dig12 = Utils.sha1(mes1 + mes2);
|
||||
do_check_eq(dig12, "6adfb183a4a2c94a2f92dab5ade762a47889a5a1");
|
||||
let dig21 = Utils.sha1(mes2 + mes1);
|
||||
do_check_eq(dig21, "5715790a892990382d98858c4aa38d0617151575");
|
||||
|
||||
_("Repeated sha1s shouldn't change the digest");
|
||||
do_check_eq(Utils.sha1(mes1), dig1);
|
||||
do_check_eq(Utils.sha1(mes2), dig2);
|
||||
do_check_eq(Utils.sha1(mes1 + mes2), dig12);
|
||||
do_check_eq(Utils.sha1(mes2 + mes1), dig21);
|
||||
|
||||
_("Nested sha1 should work just fine");
|
||||
let nest1 = Utils.sha1(Utils.sha1(Utils.sha1(Utils.sha1(Utils.sha1(mes1)))));
|
||||
do_check_eq(nest1, "23f340d0cff31e299158b3181b6bcc7e8c7f985a");
|
||||
let nest2 = Utils.sha1(Utils.sha1(Utils.sha1(Utils.sha1(Utils.sha1(mes2)))));
|
||||
do_check_eq(nest2, "1f6453867e3fb9876ae429918a64cdb8dc5ff2d0");
|
||||
}
|
@ -15,19 +15,15 @@ tail =
|
||||
[test_utils_deepEquals.js]
|
||||
[test_utils_deferGetSet.js]
|
||||
[test_utils_deriveKey.js]
|
||||
[test_utils_encodeBase32.js]
|
||||
[test_utils_keyEncoding.js]
|
||||
[test_utils_getErrorString.js]
|
||||
[test_utils_getIcon.js]
|
||||
[test_utils_hkdfExpand.js]
|
||||
[test_utils_httpmac.js]
|
||||
[test_utils_json.js]
|
||||
[test_utils_lazyStrings.js]
|
||||
[test_utils_lock.js]
|
||||
[test_utils_makeGUID.js]
|
||||
[test_utils_notify.js]
|
||||
[test_utils_passphrase.js]
|
||||
[test_utils_pbkdf2.js]
|
||||
[test_utils_sha1.js]
|
||||
|
||||
# We have a number of other libraries that are pretty much standalone.
|
||||
[test_httpd_sync_server.js]
|
||||
|
Loading…
x
Reference in New Issue
Block a user