mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-09 19:35:51 +00:00
Merge services-central into mozilla-central
This commit is contained in:
commit
14164cb13d
@ -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();
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
);
|
||||
},
|
||||
};
|
@ -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();
|
@ -4,6 +4,9 @@
|
||||
|
||||
Cu.import("resource://services-common/log4moz.js");
|
||||
|
||||
let btoa = Cu.import("resource://services-common/log4moz.js").btoa;
|
||||
let atob = Cu.import("resource://services-common/log4moz.js").atob;
|
||||
|
||||
function do_check_empty(obj) {
|
||||
do_check_attribute_count(obj, 0);
|
||||
}
|
||||
|
51
services/common/tests/unit/test_tokenauthenticatedrequest.js
Normal file
51
services/common/tests/unit/test_tokenauthenticatedrequest.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-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);
|
||||
});
|
51
services/common/tests/unit/test_utils_encodeBase32.js
Normal file
51
services/common/tests/unit/test_utils_encodeBase32.js
Normal file
@ -0,0 +1,51 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
|
||||
function run_test() {
|
||||
// Testing byte array manipulation.
|
||||
do_check_eq("FOOBAR", CommonUtils.byteArrayToString([70, 79, 79, 66, 65, 82]));
|
||||
do_check_eq("", CommonUtils.byteArrayToString([]));
|
||||
|
||||
_("Testing encoding...");
|
||||
// Test vectors from RFC 4648
|
||||
do_check_eq(CommonUtils.encodeBase32(""), "");
|
||||
do_check_eq(CommonUtils.encodeBase32("f"), "MY======");
|
||||
do_check_eq(CommonUtils.encodeBase32("fo"), "MZXQ====");
|
||||
do_check_eq(CommonUtils.encodeBase32("foo"), "MZXW6===");
|
||||
do_check_eq(CommonUtils.encodeBase32("foob"), "MZXW6YQ=");
|
||||
do_check_eq(CommonUtils.encodeBase32("fooba"), "MZXW6YTB");
|
||||
do_check_eq(CommonUtils.encodeBase32("foobar"), "MZXW6YTBOI======");
|
||||
|
||||
do_check_eq(CommonUtils.encodeBase32("Bacon is a vegetable."),
|
||||
"IJQWG33OEBUXGIDBEB3GKZ3FORQWE3DFFY======");
|
||||
|
||||
_("Checking assumptions...");
|
||||
for (let i = 0; i <= 255; ++i)
|
||||
do_check_eq(undefined | i, i);
|
||||
|
||||
_("Testing decoding...");
|
||||
do_check_eq(CommonUtils.decodeBase32(""), "");
|
||||
do_check_eq(CommonUtils.decodeBase32("MY======"), "f");
|
||||
do_check_eq(CommonUtils.decodeBase32("MZXQ===="), "fo");
|
||||
do_check_eq(CommonUtils.decodeBase32("MZXW6YTB"), "fooba");
|
||||
do_check_eq(CommonUtils.decodeBase32("MZXW6YTBOI======"), "foobar");
|
||||
|
||||
// Same with incorrect or missing padding.
|
||||
do_check_eq(CommonUtils.decodeBase32("MZXW6YTBOI=="), "foobar");
|
||||
do_check_eq(CommonUtils.decodeBase32("MZXW6YTBOI"), "foobar");
|
||||
|
||||
let encoded = CommonUtils.encodeBase32("Bacon is a vegetable.");
|
||||
_("Encoded to " + JSON.stringify(encoded));
|
||||
do_check_eq(CommonUtils.decodeBase32(encoded), "Bacon is a vegetable.");
|
||||
|
||||
// Test failure.
|
||||
let err;
|
||||
try {
|
||||
CommonUtils.decodeBase32("000");
|
||||
} catch (ex) {
|
||||
err = ex;
|
||||
}
|
||||
do_check_eq(err, "Unknown character in base32: 0");
|
||||
}
|
@ -6,6 +6,7 @@ tail =
|
||||
[test_load_modules.js]
|
||||
|
||||
[test_utils_atob.js]
|
||||
[test_utils_encodeBase32.js]
|
||||
[test_utils_makeURI.js]
|
||||
[test_utils_namedTimer.js]
|
||||
[test_utils_stackTrace.js]
|
||||
@ -17,4 +18,5 @@ tail =
|
||||
[test_observers.js]
|
||||
[test_preferences.js]
|
||||
[test_restrequest.js]
|
||||
[test_tokenauthenticatedrequest.js]
|
||||
[test_tokenserverclient.js]
|
||||
|
@ -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().
|
||||
*
|
||||
|
377
services/crypto/modules/utils.js
Normal file
377
services/crypto/modules/utils.js
Normal 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];
|
||||
}
|
||||
});
|
16
services/crypto/tests/unit/test_load_modules.js
Normal file
16
services/crypto/tests/unit/test_load_modules.js
Normal file
@ -0,0 +1,16 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const modules = [
|
||||
"utils.js",
|
||||
"WeaveCrypto.js",
|
||||
];
|
||||
|
||||
function run_test() {
|
||||
for each (let m in modules) {
|
||||
let resource = "resource://services-crypto/" + m;
|
||||
_("Attempting to import: " + resource);
|
||||
Components.utils.import(resource, {});
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,8 @@
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
Cu.import("resource://services-crypto/utils.js");
|
||||
|
||||
// Test vectors from RFC 5869
|
||||
|
||||
@ -60,8 +64,8 @@ let tc3 = {
|
||||
};
|
||||
|
||||
function sha256HMAC(message, key) {
|
||||
let h = Utils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256, key);
|
||||
return Utils.digestBytes(message, h);
|
||||
let h = CryptoUtils.makeHMACHasher(Ci.nsICryptoHMAC.SHA256, key);
|
||||
return CryptoUtils.digestBytes(message, h);
|
||||
}
|
||||
|
||||
function _hexToString(hex) {
|
||||
@ -80,13 +84,13 @@ function _hexToString(hex) {
|
||||
function extract_hex(salt, ikm) {
|
||||
salt = _hexToString(salt);
|
||||
ikm = _hexToString(ikm);
|
||||
return Utils.bytesAsHex(sha256HMAC(ikm, Utils.makeHMACKey(salt)));
|
||||
return CommonUtils.bytesAsHex(sha256HMAC(ikm, CryptoUtils.makeHMACKey(salt)));
|
||||
}
|
||||
|
||||
function expand_hex(prk, info, len) {
|
||||
prk = _hexToString(prk);
|
||||
info = _hexToString(info);
|
||||
return Utils.bytesAsHex(Utils.hkdfExpand(prk, info, len));
|
||||
return CommonUtils.bytesAsHex(CryptoUtils.hkdfExpand(prk, info, len));
|
||||
}
|
||||
|
||||
function run_test() {
|
@ -2,7 +2,8 @@
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
Cu.import("resource://services-crypto/utils.js");
|
||||
|
||||
function run_test() {
|
||||
initTestLogging();
|
||||
@ -18,10 +19,10 @@ add_test(function test_sha1() {
|
||||
let ts = 1329181221;
|
||||
let method = "GET";
|
||||
let nonce = "wGX71";
|
||||
let uri = Utils.makeURI("http://10.250.2.176/alias/");
|
||||
let uri = CommonUtils.makeURI("http://10.250.2.176/alias/");
|
||||
|
||||
let result = Utils.computeHTTPMACSHA1(id, key, method, uri, {ts: ts,
|
||||
nonce: nonce});
|
||||
let result = CryptoUtils.computeHTTPMACSHA1(id, key, method, uri,
|
||||
{ts: ts, nonce: nonce});
|
||||
|
||||
do_check_eq(btoa(result.mac), "jzh5chjQc2zFEvLbyHnPdX11Yck=");
|
||||
|
||||
@ -31,9 +32,8 @@ add_test(function test_sha1() {
|
||||
|
||||
let ext = "EXTRA DATA; foo,bar=1";
|
||||
|
||||
let result = Utils.computeHTTPMACSHA1(id, key, method, uri, {ts: ts,
|
||||
nonce: nonce,
|
||||
ext: ext});
|
||||
let result = CryptoUtils.computeHTTPMACSHA1(id, key, method, uri,
|
||||
{ts: ts, nonce: nonce, ext: ext});
|
||||
do_check_eq(btoa(result.mac), "bNf4Fnt5k6DnhmyipLPkuZroH68=");
|
||||
do_check_eq(result.getHeader(),
|
||||
'MAC id="vmo1txkttblmn51u2p3zk2xiy16hgvm5ok8qiv1yyi86ffjzy9zj0ez9x6wnvbx7", ' +
|
||||
@ -47,8 +47,8 @@ add_test(function test_nonce_length() {
|
||||
_("Ensure custom nonce lengths are honoured.");
|
||||
|
||||
function get_mac(length) {
|
||||
let uri = Utils.makeURI("http://example.com/");
|
||||
return Utils.computeHTTPMACSHA1("foo", "bar", "GET", uri, {
|
||||
let uri = CommonUtils.makeURI("http://example.com/");
|
||||
return CryptoUtils.computeHTTPMACSHA1("foo", "bar", "GET", uri, {
|
||||
nonce_bytes: length
|
||||
});
|
||||
}
|
15
services/crypto/tests/unit/test_utils_pbkdf2.js
Normal file
15
services/crypto/tests/unit/test_utils_pbkdf2.js
Normal file
@ -0,0 +1,15 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
// Evil.
|
||||
let btoa = Cu.import("resource://services-common/utils.js").btoa;
|
||||
Cu.import("resource://services-crypto/utils.js");
|
||||
|
||||
function run_test() {
|
||||
let symmKey16 = CryptoUtils.pbkdf2Generate("secret phrase", "DNXPzPpiwn", 4096, 16);
|
||||
do_check_eq(symmKey16.length, 16);
|
||||
do_check_eq(btoa(symmKey16), "d2zG0d2cBfXnRwMUGyMwyg==");
|
||||
do_check_eq(CommonUtils.encodeBase32(symmKey16), "O5WMNUO5TQC7LZ2HAMKBWIZQZI======");
|
||||
let symmKey32 = CryptoUtils.pbkdf2Generate("passphrase", "salt", 4096, 32);
|
||||
do_check_eq(symmKey32.length, 32);
|
||||
}
|
37
services/crypto/tests/unit/test_utils_sha1.js
Normal file
37
services/crypto/tests/unit/test_utils_sha1.js
Normal 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");
|
||||
}
|
@ -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]
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
|
@ -1,58 +0,0 @@
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
// Testing byte array manipulation.
|
||||
do_check_eq("FOOBAR", Utils.byteArrayToString([70, 79, 79, 66, 65, 82]));
|
||||
do_check_eq("", Utils.byteArrayToString([]));
|
||||
|
||||
_("Testing encoding...");
|
||||
// Test vectors from RFC 4648
|
||||
do_check_eq(Utils.encodeBase32(""), "");
|
||||
do_check_eq(Utils.encodeBase32("f"), "MY======");
|
||||
do_check_eq(Utils.encodeBase32("fo"), "MZXQ====");
|
||||
do_check_eq(Utils.encodeBase32("foo"), "MZXW6===");
|
||||
do_check_eq(Utils.encodeBase32("foob"), "MZXW6YQ=");
|
||||
do_check_eq(Utils.encodeBase32("fooba"), "MZXW6YTB");
|
||||
do_check_eq(Utils.encodeBase32("foobar"), "MZXW6YTBOI======");
|
||||
|
||||
do_check_eq(Utils.encodeBase32("Bacon is a vegetable."),
|
||||
"IJQWG33OEBUXGIDBEB3GKZ3FORQWE3DFFY======");
|
||||
|
||||
_("Checking assumptions...");
|
||||
for (let i = 0; i <= 255; ++i)
|
||||
do_check_eq(undefined | i, i);
|
||||
|
||||
_("Testing decoding...");
|
||||
do_check_eq(Utils.decodeBase32(""), "");
|
||||
do_check_eq(Utils.decodeBase32("MY======"), "f");
|
||||
do_check_eq(Utils.decodeBase32("MZXQ===="), "fo");
|
||||
do_check_eq(Utils.decodeBase32("MZXW6YTB"), "fooba");
|
||||
do_check_eq(Utils.decodeBase32("MZXW6YTBOI======"), "foobar");
|
||||
|
||||
// Same with incorrect or missing padding.
|
||||
do_check_eq(Utils.decodeBase32("MZXW6YTBOI=="), "foobar");
|
||||
do_check_eq(Utils.decodeBase32("MZXW6YTBOI"), "foobar");
|
||||
|
||||
let encoded = Utils.encodeBase32("Bacon is a vegetable.");
|
||||
_("Encoded to " + JSON.stringify(encoded));
|
||||
do_check_eq(Utils.decodeBase32(encoded), "Bacon is a vegetable.");
|
||||
|
||||
// Test failure.
|
||||
let err;
|
||||
try {
|
||||
Utils.decodeBase32("000");
|
||||
} catch (ex) {
|
||||
err = ex;
|
||||
}
|
||||
do_check_eq(err, "Unknown character in base32: 0");
|
||||
|
||||
// Testing our own variant.
|
||||
do_check_eq(Utils.encodeKeyBase32("foobarbafoobarba"), "mzxw6ytb9jrgcztpn5rgc4tcme");
|
||||
do_check_eq(Utils.decodeKeyBase32("mzxw6ytb9jrgcztpn5rgc4tcme"), "foobarbafoobarba");
|
||||
do_check_eq(
|
||||
Utils.encodeKeyBase32("\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"),
|
||||
"aeaqcaibaeaqcaibaeaqcaibae");
|
||||
do_check_eq(
|
||||
Utils.decodeKeyBase32("aeaqcaibaeaqcaibaeaqcaibae"),
|
||||
"\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01");
|
||||
}
|
15
services/sync/tests/unit/test_utils_keyEncoding.js
Normal file
15
services/sync/tests/unit/test_utils_keyEncoding.js
Normal file
@ -0,0 +1,15 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
do_check_eq(Utils.encodeKeyBase32("foobarbafoobarba"), "mzxw6ytb9jrgcztpn5rgc4tcme");
|
||||
do_check_eq(Utils.decodeKeyBase32("mzxw6ytb9jrgcztpn5rgc4tcme"), "foobarbafoobarba");
|
||||
do_check_eq(
|
||||
Utils.encodeKeyBase32("\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01"),
|
||||
"aeaqcaibaeaqcaibaeaqcaibae");
|
||||
do_check_eq(
|
||||
Utils.decodeKeyBase32("aeaqcaibaeaqcaibaeaqcaibae"),
|
||||
"\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01");
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
// Evil.
|
||||
let btoa = Cu.import("resource://services-sync/util.js").btoa;
|
||||
|
||||
function run_test() {
|
||||
let symmKey16 = Utils.pbkdf2Generate("secret phrase", "DNXPzPpiwn", 4096, 16);
|
||||
do_check_eq(symmKey16.length, 16);
|
||||
do_check_eq(btoa(symmKey16), "d2zG0d2cBfXnRwMUGyMwyg==");
|
||||
do_check_eq(Utils.encodeBase32(symmKey16), "O5WMNUO5TQC7LZ2HAMKBWIZQZI======");
|
||||
let symmKey32 = Utils.pbkdf2Generate("passphrase", "salt", 4096, 32);
|
||||
do_check_eq(symmKey32.length, 32);
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
_("Make sure sha1 digests works with various messages");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
function run_test() {
|
||||
let mes1 = "hello";
|
||||
let mes2 = "world";
|
||||
|
||||
_("Make sure right sha1 digests are generated");
|
||||
let dig1 = Utils.sha1(mes1);
|
||||
do_check_eq(dig1, "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d");
|
||||
let dig2 = Utils.sha1(mes2);
|
||||
do_check_eq(dig2, "7c211433f02071597741e6ff5a8ea34789abbf43");
|
||||
let dig12 = Utils.sha1(mes1 + mes2);
|
||||
do_check_eq(dig12, "6adfb183a4a2c94a2f92dab5ade762a47889a5a1");
|
||||
let dig21 = Utils.sha1(mes2 + mes1);
|
||||
do_check_eq(dig21, "5715790a892990382d98858c4aa38d0617151575");
|
||||
|
||||
_("Repeated sha1s shouldn't change the digest");
|
||||
do_check_eq(Utils.sha1(mes1), dig1);
|
||||
do_check_eq(Utils.sha1(mes2), dig2);
|
||||
do_check_eq(Utils.sha1(mes1 + mes2), dig12);
|
||||
do_check_eq(Utils.sha1(mes2 + mes1), dig21);
|
||||
|
||||
_("Nested sha1 should work just fine");
|
||||
let nest1 = Utils.sha1(Utils.sha1(Utils.sha1(Utils.sha1(Utils.sha1(mes1)))));
|
||||
do_check_eq(nest1, "23f340d0cff31e299158b3181b6bcc7e8c7f985a");
|
||||
let nest2 = Utils.sha1(Utils.sha1(Utils.sha1(Utils.sha1(Utils.sha1(mes2)))));
|
||||
do_check_eq(nest2, "1f6453867e3fb9876ae429918a64cdb8dc5ff2d0");
|
||||
}
|
@ -15,19 +15,15 @@ tail =
|
||||
[test_utils_deepEquals.js]
|
||||
[test_utils_deferGetSet.js]
|
||||
[test_utils_deriveKey.js]
|
||||
[test_utils_encodeBase32.js]
|
||||
[test_utils_keyEncoding.js]
|
||||
[test_utils_getErrorString.js]
|
||||
[test_utils_getIcon.js]
|
||||
[test_utils_hkdfExpand.js]
|
||||
[test_utils_httpmac.js]
|
||||
[test_utils_json.js]
|
||||
[test_utils_lazyStrings.js]
|
||||
[test_utils_lock.js]
|
||||
[test_utils_makeGUID.js]
|
||||
[test_utils_notify.js]
|
||||
[test_utils_passphrase.js]
|
||||
[test_utils_pbkdf2.js]
|
||||
[test_utils_sha1.js]
|
||||
|
||||
# We have a number of other libraries that are pretty much standalone.
|
||||
[test_httpd_sync_server.js]
|
||||
|
@ -1 +1 @@
|
||||
1.16.0
|
||||
1.17.0
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user