Merge services-central into mozilla-central

This commit is contained in:
Gregory Szorc 2012-05-08 09:52:27 -07:00
commit 14164cb13d
28 changed files with 888 additions and 634 deletions

View File

@ -2,7 +2,7 @@
* 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 Cu = Components.utils;
const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;
@ -176,27 +176,25 @@ let DOMApplicationRegistry = {
dir.remove(true);
} catch(e) {
}
} else {
id = this.makeAppId();
}
else {
let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
id = uuidGenerator.generateUUID().toString();
}
let appObject = this._cloneAppObject(app);
appObject.installTime = (new Date()).getTime();
let appNote = JSON.stringify(appObject);
appNote.id = id;
let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", id], true, true);
let manFile = dir.clone();
manFile.append("manifest.json");
this._writeFile(manFile, JSON.stringify(app.manifest));
this.webapps[id] = appObject;
this.webapps[id] = this._cloneAppObject(app);
delete this.webapps[id].manifest;
this.webapps[id].installTime = (new Date()).getTime()
if (!aFromSync)
this._saveApps((function() {
ppmm.sendAsyncMessage("Webapps:Install:Return:OK", aData);
Services.obs.notifyObservers(this, "webapps-sync-install", id);
Services.obs.notifyObservers(this, "webapps-sync-install", appNote);
}).bind(this));
},
@ -208,6 +206,11 @@ let DOMApplicationRegistry = {
return null;
},
makeAppId: function() {
let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
return uuidGenerator.generateUUID().toString();
},
_saveApps: function(aCallback) {
this._writeFile(this.appsFile, JSON.stringify(this.webapps), function() {
if (aCallback)
@ -233,7 +236,7 @@ let DOMApplicationRegistry = {
aFinalCallback(aData);
else
this._readManifests(aData, aFinalCallback, index + 1);
}).bind(this));
}).bind(this));
},
uninstall: function(aData) {
@ -242,15 +245,19 @@ let DOMApplicationRegistry = {
let app = this.webapps[id];
if (app.origin == aData.origin) {
found = true;
let appNote = JSON.stringify(this._cloneAppObject(app));
appNote.id = id;
delete this.webapps[id];
let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", id], true, true);
try {
dir.remove(true);
} catch (e) {
}
this._saveApps((function() {
ppmm.sendAsyncMessage("Webapps:Uninstall:Return:OK", aData);
Services.obs.notifyObservers(this, "webapps-sync-uninstall", id);
Services.obs.notifyObservers(this, "webapps-sync-uninstall", appNote);
}).bind(this));
}
}
@ -299,7 +306,7 @@ let DOMApplicationRegistry = {
aData.apps = [];
let tmp = [];
for (id in this.webapps) {
for (let id in this.webapps) {
let app = this._cloneAppObject(this.webapps[id]);
aData.apps.push(app);
tmp.push({ id: id });
@ -327,18 +334,26 @@ let DOMApplicationRegistry = {
});
},
/** added to support the sync engine */
/** Added to support AITC and classic sync */
itemExists: function(aId) {
return !!this.webapps[aId];
},
getAppById: function(aId) {
if (!this.webapps[aId])
return null;
let app = this._cloneAppObject(this.webapps[aId]);
return app;
},
itemExists: function(aId) {
return !!this.webapps[aId];
getAllWithoutManifests: function(aCallback) {
let result = {};
for (let id in this.webapps) {
let app = this._cloneAppObject(this.webapps[id]);
result[id] = app;
}
aCallback(result);
},
updateApps: function(aRecords, aCallback) {
@ -356,11 +371,10 @@ let DOMApplicationRegistry = {
}
ppmm.sendAsyncMessage("Webapps:Uninstall:Return:OK", { origin: origin });
} else {
if (!!this.webapps[record.id]) {
if (this.webapps[record.id]) {
this.webapps[record.id] = record.value;
delete this.webapps[record.id].manifest;
}
else {
} else {
let data = { app: record.value };
this.confirmInstall(data, true);
ppmm.sendAsyncMessage("Webapps:Install:Return:OK", data);
@ -370,9 +384,6 @@ let DOMApplicationRegistry = {
this._saveApps(aCallback);
},
/*
* May be removed once sync API change
*/
getAllIDs: function() {
let apps = {};
for (let id in this.webapps) {
@ -394,7 +405,7 @@ let DOMApplicationRegistry = {
}
}
this._saveApps(aCallback);
}
}
};
/**
@ -407,7 +418,7 @@ DOMApplicationManifest = function(aManifest, aOrigin) {
.QueryInterface(Ci.nsIToolkitChromeRegistry);
let locale = chrome.getSelectedLocale("browser").toLowerCase();
this._localeRoot = this._manifest;
if (this._manifest.locales && this._manifest.locales[locale]) {
this._localeRoot = this._manifest.locales[locale];
}
@ -417,7 +428,7 @@ DOMApplicationManifest = function(aManifest, aOrigin) {
if (lang != locale && this._manifest.locales[lang])
this._localeRoot = this._manifest.locales[lang];
}
}
};
DOMApplicationManifest.prototype = {
_localeProp: function(aProp) {
@ -429,27 +440,27 @@ DOMApplicationManifest.prototype = {
get name() {
return this._localeProp("name");
},
get description() {
return this._localeProp("description");
},
get version() {
return this._localeProp("version");
},
get launch_path() {
return this._localeProp("launch_path");
},
get developer() {
return this._localeProp("developer");
},
get icons() {
return this._localeProp("icons");
},
iconURLForSize: function(aSize) {
let icons = this._localeProp("icons");
if (!icons)
@ -465,11 +476,11 @@ DOMApplicationManifest.prototype = {
}
return icon;
},
fullLaunchPath: function(aStartPoint) {
let launchPath = this._localeProp("launch_path") || "";
return this._origin.resolve(launchPath + aStartPoint);
}
}
};
DOMApplicationRegistry.init();

View File

@ -27,7 +27,7 @@ module_dir = $(FINAL_TARGET)/modules/services-common
libs::
$(NSINSTALL) -D $(module_dir)
$(NSINSTALL) -R $(source_modules) $(module_dir)
$(NSINSTALL) $(source_modules) $(module_dir)
TEST_DIRS += tests

View File

@ -4,10 +4,15 @@
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
const EXPORTED_SYMBOLS = ["RESTRequest", "RESTResponse"];
const EXPORTED_SYMBOLS = [
"RESTRequest",
"RESTResponse",
"TokenAuthenticatedRESTRequest"
];
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://services-crypto/utils.js");
Cu.import("resource://services-common/log4moz.js");
Cu.import("resource://services-common/preferences.js");
Cu.import("resource://services-common/utils.js");
@ -576,3 +581,39 @@ RESTResponse.prototype = {
body: null
};
/**
* Single use MAC authenticated HTTP requests to RESTish resources.
*
* @param uri
* URI going to the RESTRequest constructor.
* @param authToken
* (Object) An auth token of the form {id: (string), key: (string)}
* from which the MAC Authentication header for this request will be
* derived. A token as obtained from
* TokenServerClient.getTokenFromBrowserIDAssertion is accepted.
* @param extra
* (Object) Optional extra parameters. Valid keys are: nonce_bytes, ts,
* nonce, and ext. See CrytoUtils.computeHTTPMACSHA1 for information on
* the purpose of these values.
*/
function TokenAuthenticatedRESTRequest(uri, authToken, extra) {
RESTRequest.call(this, uri);
this.authToken = authToken;
this.extra = extra || {};
}
TokenAuthenticatedRESTRequest.prototype = {
__proto__: RESTRequest.prototype,
dispatch: function dispatch(method, data, onComplete, onProgress) {
let sig = CryptoUtils.computeHTTPMACSHA1(
this.authToken.id, this.authToken.key, method, this.uri, this.extra
);
this.setHeader("Authorization", sig.getHeader());
return RESTRequest.prototype.dispatch.call(
this, method, data, onComplete, onProgress
);
},
};

View File

@ -47,8 +47,11 @@ function addResourceAlias() {
const handler = Services.io.getProtocolHandler("resource")
.QueryInterface(Ci.nsIResProtocolHandler);
let uri = Services.io.newURI("resource:///modules/services-common/", null,
null);
handler.setSubstitution("services-common", uri);
let modules = ["common", "crypto"];
for each (let module in modules) {
let uri = Services.io.newURI("resource:///modules/services-" + module + "/",
null, null);
handler.setSubstitution("services-" + module, uri);
}
}
addResourceAlias();
addResourceAlias();

View File

@ -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);
}

View File

@ -0,0 +1,51 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://services-crypto/utils.js");
Cu.import("resource://services-common/async.js");
Cu.import("resource://services-common/rest.js");
Cu.import("resource://services-common/utils.js");
function run_test() {
initTestLogging("Trace");
run_next_test();
}
add_test(function test_authenticated_request() {
_("Ensure that sending a MAC authenticated GET request works as expected.");
let message = "Great Success!";
// TODO: We use a preset key here, but use getTokenFromBrowserIDAssertion()
// from TokenServerClient to get a real one when possible. (Bug 745800)
let id = "eyJleHBpcmVzIjogMTM2NTAxMDg5OC4x";
let key = "qTZf4ZFpAMpMoeSsX3zVRjiqmNs=";
let method = "GET";
let uri = CommonUtils.makeURI(TEST_SERVER_URL + "foo");
let nonce = btoa(CryptoUtils.generateRandomBytes(16));
let ts = Math.floor(Date.now() / 1000);
let extra = {ts: ts, nonce: nonce};
let sig = CryptoUtils.computeHTTPMACSHA1(id, key, method, uri, extra);
let auth = sig.getHeader();
let server = httpd_setup({"/foo": function(request, response) {
do_check_true(request.hasHeader("Authorization"));
do_check_eq(auth, request.getHeader("Authorization"));
response.setStatusLine(request.httpVersion, 200, "OK");
response.bodyOutputStream.write(message, message.length);
}
});
let req = new TokenAuthenticatedRESTRequest(uri, {id: id, key: key}, extra);
let cb = Async.makeSpinningCallback();
req.get(cb);
let result = cb.wait();
do_check_eq(null, result);
do_check_eq(message, req.response.body);
server.stop(run_next_test);
});

View 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");
}

View File

@ -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]
@ -17,4 +18,5 @@ tail =
[test_observers.js]
[test_preferences.js]
[test_restrequest.js]
[test_tokenauthenticatedrequest.js]
[test_tokenserverclient.js]

View File

@ -55,6 +55,13 @@ let CommonUtils = {
return "No traceback available";
},
/**
* Encode byte string as base64URL (RFC 4648).
*/
encodeBase64URL: function encodeBase64URL(bytes) {
return btoa(bytes).replace("+", "-", "g").replace("/", "_", "g");
},
/**
* Create a nsIURI instance from a string.
*/
@ -141,6 +148,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().
*

View File

@ -0,0 +1,377 @@
/* 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;
},
/**
* UTF-8 encode a message and perform a SHA-1 over it.
*
* @param message
* (string) Buffer to perform operation on. Should be a JS string.
* It is possible to pass in a string representing an array
* of bytes. But, you probably don't want to UTF-8 encode
* such data and thus should not be using this function.
*
* @return string
* Raw bytes constituting SHA-1 hash. Value is a JS string. Each
* character is the byte value for that offset. Returned string
* always has .length == 20.
*/
UTF8AndSHA1: function UTF8AndSHA1(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.UTF8AndSHA1(message));
},
sha1Base32: function sha1Base32(message) {
return CommonUtils.encodeBase32(CryptoUtils.UTF8AndSHA1(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];
}
});

View 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, {});
}
}

View File

@ -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() {

View File

@ -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
});
}

View 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);
}

View File

@ -0,0 +1,37 @@
/* 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";
let dig0 = CryptoUtils.UTF8AndSHA1(mes1);
do_check_eq(dig0,
"\xaa\xf4\xc6\x1d\xdc\xc5\xe8\xa2\xda\xbe\xde\x0f\x3b\x48\x2c\xd9\xae\xa9\x43\x4d");
_("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");
}

View File

@ -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]

View File

@ -131,8 +131,15 @@ AppTracker.prototype = {
case "webapps-sync-uninstall":
// ask for immediate sync. not sure if we really need this or
// if a lower score increment would be enough
let app;
this.score += SCORE_INCREMENT_XLARGE;
this.addChangedID(aData);
try {
app = JSON.parse(aData);
} catch (e) {
this._log.error("JSON.parse failed in observer " + e);
return;
}
this.addChangedID(app.id);
break;
case "weave:engine:start-tracking":
this._enabled = true;

View File

@ -47,10 +47,11 @@ Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/record.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-common/utils.js");
Cu.import("resource://services-common/preferences.js");
Cu.import("resource://gre/modules/LightweightThemeManager.jsm");
const PREFS_GUID = Utils.encodeBase64url(Services.appinfo.ID);
const PREFS_GUID = CommonUtils.encodeBase64URL(Services.appinfo.ID);
function PrefRec(collection, id) {
CryptoWrapper.call(this, collection, id);

View File

@ -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,33 +200,12 @@ 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).
*/
encodeBase64url: function encodeBase64url(bytes) {
return btoa(bytes).replace('+', '-', 'g').replace('/', '_', 'g');
},
/**
* GUIDs are 9 random bytes encoded with base64url (RFC 4648).
* That makes them 12 characters long with 72 bits of entropy.
*/
makeGUID: function makeGUID() {
return Utils.encodeBase64url(Utils.generateRandomBytes(9));
return CommonUtils.encodeBase64URL(Utils.generateRandomBytes(9));
},
_base64url_regex: /^[-abcdefghijklmnopqrstuvwxyz0123456789_]{12}$/i,
@ -290,310 +288,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 +306,6 @@ let Utils = {
.replace("9", 'O', "g");
},
/**
* Key manipulation.
*/
@ -635,23 +328,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 +343,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 +357,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 +457,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 +644,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);

View File

@ -1,10 +1,11 @@
Cu.import("resource://services-sync/engines/prefs.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://services-common/utils.js");
Cu.import("resource://services-common/preferences.js");
Cu.import("resource://gre/modules/LightweightThemeManager.jsm");
Cu.import("resource://gre/modules/Services.jsm");
const PREFS_GUID = Utils.encodeBase64url(Services.appinfo.ID);
const PREFS_GUID = CommonUtils.encodeBase64URL(Services.appinfo.ID);
function makePersona(id) {
return {

View File

@ -1,6 +1,7 @@
Cu.import("resource://services-sync/engines/prefs.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://services-common/utils.js");
Cu.import("resource://services-common/preferences.js");
function run_test() {
@ -22,7 +23,7 @@ function run_test() {
let changedIDs = engine.getChangedIDs();
let ids = Object.keys(changedIDs);
do_check_eq(ids.length, 1);
do_check_eq(ids[0], Utils.encodeBase64url(Services.appinfo.ID));
do_check_eq(ids[0], CommonUtils.encodeBase64URL(Services.appinfo.ID));
Svc.Prefs.set("engine.prefs.modified", false);
do_check_false(tracker.modified);

View File

@ -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");
}

View 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");
}

View File

@ -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);
}

View File

@ -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");
}

View File

@ -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]

View File

@ -1 +1 @@
1.16.0
1.17.0

View File

@ -524,7 +524,12 @@ class TPSTestRunner(object):
text = test['message'],
logfile = errorlog_filename
)
group.submit()
try:
group.submit()
except:
self.sendEmail('<pre>%s</pre>' % traceback.format_exc(),
sendTo='crossweave@mozilla.com')
return
# Iterate through all testfailure objects, and update the postdata
# dict with the testfailure logurl's, if any.