mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-25 20:01:50 +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");
|
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) {
|
function do_check_empty(obj) {
|
||||||
do_check_attribute_count(obj, 0);
|
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_load_modules.js]
|
||||||
|
|
||||||
[test_utils_atob.js]
|
[test_utils_atob.js]
|
||||||
|
[test_utils_encodeBase32.js]
|
||||||
[test_utils_makeURI.js]
|
[test_utils_makeURI.js]
|
||||||
[test_utils_namedTimer.js]
|
[test_utils_namedTimer.js]
|
||||||
[test_utils_stackTrace.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().
|
* 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
|
// Test vectors from RFC 5869
|
||||||
|
|
||||||
@ -60,8 +64,8 @@ let tc3 = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function sha256HMAC(message, key) {
|
function sha256HMAC(message, key) {
|
||||||
let h = Utils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256, key);
|
let h = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256, key);
|
||||||
return Utils.digestBytes(message, h);
|
return CryptoUtils.digestBytes(message, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _hexToString(hex) {
|
function _hexToString(hex) {
|
||||||
@ -80,13 +84,13 @@ function _hexToString(hex) {
|
|||||||
function extract_hex(salt, ikm) {
|
function extract_hex(salt, ikm) {
|
||||||
salt = _hexToString(salt);
|
salt = _hexToString(salt);
|
||||||
ikm = _hexToString(ikm);
|
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) {
|
function expand_hex(prk, info, len) {
|
||||||
prk = _hexToString(prk);
|
prk = _hexToString(prk);
|
||||||
info = _hexToString(info);
|
info = _hexToString(info);
|
||||||
return Utils.bytesAsHex(Utils.hkdfExpand(prk, info, len));
|
return CommonUtils.bytesAsHex(CryptoUtils.hkdfExpand(prk, info, len));
|
||||||
}
|
}
|
||||||
|
|
||||||
function run_test() {
|
function run_test() {
|
@ -2,7 +2,8 @@
|
|||||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
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() {
|
function run_test() {
|
||||||
initTestLogging();
|
initTestLogging();
|
||||||
@ -18,10 +19,10 @@ add_test(function test_sha1() {
|
|||||||
let ts = 1329181221;
|
let ts = 1329181221;
|
||||||
let method = "GET";
|
let method = "GET";
|
||||||
let nonce = "wGX71";
|
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,
|
let result = CryptoUtils.computeHTTPMACSHA1(id, key, method, uri,
|
||||||
nonce: nonce});
|
{ts: ts, nonce: nonce});
|
||||||
|
|
||||||
do_check_eq(btoa(result.mac), "jzh5chjQc2zFEvLbyHnPdX11Yck=");
|
do_check_eq(btoa(result.mac), "jzh5chjQc2zFEvLbyHnPdX11Yck=");
|
||||||
|
|
||||||
@ -31,9 +32,8 @@ add_test(function test_sha1() {
|
|||||||
|
|
||||||
let ext = "EXTRA DATA; foo,bar=1";
|
let ext = "EXTRA DATA; foo,bar=1";
|
||||||
|
|
||||||
let result = Utils.computeHTTPMACSHA1(id, key, method, uri, {ts: ts,
|
let result = CryptoUtils.computeHTTPMACSHA1(id, key, method, uri,
|
||||||
nonce: nonce,
|
{ts: ts, nonce: nonce, ext: ext});
|
||||||
ext: ext});
|
|
||||||
do_check_eq(btoa(result.mac), "bNf4Fnt5k6DnhmyipLPkuZroH68=");
|
do_check_eq(btoa(result.mac), "bNf4Fnt5k6DnhmyipLPkuZroH68=");
|
||||||
do_check_eq(result.getHeader(),
|
do_check_eq(result.getHeader(),
|
||||||
'MAC id="vmo1txkttblmn51u2p3zk2xiy16hgvm5ok8qiv1yyi86ffjzy9zj0ez9x6wnvbx7", ' +
|
'MAC id="vmo1txkttblmn51u2p3zk2xiy16hgvm5ok8qiv1yyi86ffjzy9zj0ez9x6wnvbx7", ' +
|
||||||
@ -47,8 +47,8 @@ add_test(function test_nonce_length() {
|
|||||||
_("Ensure custom nonce lengths are honoured.");
|
_("Ensure custom nonce lengths are honoured.");
|
||||||
|
|
||||||
function get_mac(length) {
|
function get_mac(length) {
|
||||||
let uri = Utils.makeURI("http://example.com/");
|
let uri = CommonUtils.makeURI("http://example.com/");
|
||||||
return Utils.computeHTTPMACSHA1("foo", "bar", "GET", uri, {
|
return CryptoUtils.computeHTTPMACSHA1("foo", "bar", "GET", uri, {
|
||||||
nonce_bytes: length
|
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]
|
[DEFAULT]
|
||||||
head = head_helpers.js
|
head = head_helpers.js ../../../common/tests/unit/head_helpers.js
|
||||||
tail =
|
tail =
|
||||||
|
|
||||||
|
[test_load_modules.js]
|
||||||
|
|
||||||
[test_crypto_crypt.js]
|
[test_crypto_crypt.js]
|
||||||
[test_crypto_deriveKey.js]
|
[test_crypto_deriveKey.js]
|
||||||
[test_crypto_random.js]
|
[test_crypto_random.js]
|
||||||
# Bug 676977: test hangs consistently on Android
|
# Bug 676977: test hangs consistently on Android
|
||||||
skip-if = os == "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;
|
const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
|
||||||
|
|
||||||
Cu.import("resource://services-common/log4moz.js");
|
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/preferences.js");
|
||||||
Cu.import("resource://services-common/stringbundle.js");
|
Cu.import("resource://services-common/stringbundle.js");
|
||||||
Cu.import("resource://services-common/utils.js");
|
Cu.import("resource://services-common/utils.js");
|
||||||
Cu.import("resource://services-common/async.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-sync/constants.js");
|
||||||
Cu.import("resource://services-common/observers.js");
|
|
||||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
Cu.import("resource://gre/modules/Services.jsm");
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
Cu.import("resource://gre/modules/PlacesUtils.jsm");
|
Cu.import("resource://gre/modules/PlacesUtils.jsm");
|
||||||
@ -68,6 +69,24 @@ let Utils = {
|
|||||||
encodeUTF8: CommonUtils.encodeUTF8,
|
encodeUTF8: CommonUtils.encodeUTF8,
|
||||||
decodeUTF8: CommonUtils.decodeUTF8,
|
decodeUTF8: CommonUtils.decodeUTF8,
|
||||||
safeAtoB: CommonUtils.safeAtoB,
|
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
|
* 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).
|
* Encode byte string as base64url (RFC 4648).
|
||||||
*/
|
*/
|
||||||
@ -290,310 +295,6 @@ let Utils = {
|
|||||||
return ex && ex.indexOf && (ex.indexOf(hmacFail) == 0);
|
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.
|
* Turn RFC 4648 base32 into our own user-friendly version.
|
||||||
* ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
|
* ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
|
||||||
@ -612,7 +313,6 @@ let Utils = {
|
|||||||
.replace("9", 'O', "g");
|
.replace("9", 'O', "g");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Key manipulation.
|
* Key manipulation.
|
||||||
*/
|
*/
|
||||||
@ -635,23 +335,13 @@ let Utils = {
|
|||||||
return btoa(keyData);
|
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
|
* N.B., salt should be base64 encoded, even though we have to decode
|
||||||
* it later!
|
* it later!
|
||||||
*/
|
*/
|
||||||
derivePresentableKeyFromPassphrase : function derivePresentableKeyFromPassphrase(passphrase, salt, keyLength, forceJS) {
|
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);
|
return Utils.encodeKeyBase32(k);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -660,7 +350,8 @@ let Utils = {
|
|||||||
* it later!
|
* it later!
|
||||||
*/
|
*/
|
||||||
deriveEncodedKeyFromPassphrase : function deriveEncodedKeyFromPassphrase(passphrase, salt, keyLength, forceJS) {
|
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);
|
return Utils.base64Key(k);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -673,130 +364,6 @@ let Utils = {
|
|||||||
return Utils.encodeKeyBase32(atob(encodedKey));
|
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
|
* 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
|
// 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
|
// other tasks. It's lowercase, uses different letters, and needs to be
|
||||||
// decoded with decodeKeyBase32, not just decodeBase32.
|
// 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"],
|
[["Form", "@mozilla.org/satchel/form-history;1", "nsIFormHistory2"],
|
||||||
["Idle", "@mozilla.org/widget/idleservice;1", "nsIIdleService"],
|
["Idle", "@mozilla.org/widget/idleservice;1", "nsIIdleService"],
|
||||||
["KeyFactory", "@mozilla.org/security/keyobjectfactory;1", "nsIKeyObjectFactory"],
|
|
||||||
["Session", _sessionCID, "nsISessionStore"]
|
["Session", _sessionCID, "nsISessionStore"]
|
||||||
].forEach(function([name, contract, iface]) {
|
].forEach(function([name, contract, iface]) {
|
||||||
XPCOMUtils.defineLazyServiceGetter(Svc, 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_deepEquals.js]
|
||||||
[test_utils_deferGetSet.js]
|
[test_utils_deferGetSet.js]
|
||||||
[test_utils_deriveKey.js]
|
[test_utils_deriveKey.js]
|
||||||
[test_utils_encodeBase32.js]
|
[test_utils_keyEncoding.js]
|
||||||
[test_utils_getErrorString.js]
|
[test_utils_getErrorString.js]
|
||||||
[test_utils_getIcon.js]
|
[test_utils_getIcon.js]
|
||||||
[test_utils_hkdfExpand.js]
|
|
||||||
[test_utils_httpmac.js]
|
|
||||||
[test_utils_json.js]
|
[test_utils_json.js]
|
||||||
[test_utils_lazyStrings.js]
|
[test_utils_lazyStrings.js]
|
||||||
[test_utils_lock.js]
|
[test_utils_lock.js]
|
||||||
[test_utils_makeGUID.js]
|
[test_utils_makeGUID.js]
|
||||||
[test_utils_notify.js]
|
[test_utils_notify.js]
|
||||||
[test_utils_passphrase.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.
|
# We have a number of other libraries that are pretty much standalone.
|
||||||
[test_httpd_sync_server.js]
|
[test_httpd_sync_server.js]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user