Bug 729659 - Implement HTTP MAC authorization signing API; r=rnewman, dchan

This commit is contained in:
Gregory Szorc 2012-02-27 17:52:48 -08:00
Родитель b889aed36f
Коммит b83b6f73ed
4 изменённых файлов: 197 добавлений и 1 удалений

Просмотреть файл

@ -755,6 +755,130 @@ 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 +'"';
},
makeURI: function Weave_makeURI(URIString) {
if (!URIString)
return null;

Просмотреть файл

@ -5,7 +5,8 @@ Cu.import("resource://services-sync/async.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://services-sync/record.js");
Cu.import("resource://services-sync/engines.js");
var btoa;
let btoa;
let atob;
let provider = {
getFile: function(prop, persistent) {
@ -39,6 +40,7 @@ function waitForZeroTimer(callback) {
}
btoa = Cu.import("resource://services-sync/log4moz.js").btoa;
atob = Cu.import("resource://services-sync/log4moz.js").atob;
function getTestLogger(component) {
return Log4Moz.repository.getLogger("Testing");
}

Просмотреть файл

@ -0,0 +1,69 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://services-sync/util.js");
function run_test() {
initTestLogging();
run_next_test();
}
add_test(function test_sha1() {
_("Ensure HTTP MAC SHA1 generation works as expected.");
let id = "vmo1txkttblmn51u2p3zk2xiy16hgvm5ok8qiv1yyi86ffjzy9zj0ez9x6wnvbx7";
let key = "b8u1cc5iiio5o319og7hh8faf2gi5ym4aq0zwf112cv1287an65fudu5zj7zo7dz";
let ts = 1329181221;
let method = "GET";
let nonce = "wGX71";
let uri = Utils.makeURI("http://10.250.2.176/alias/");
let result = Utils.computeHTTPMACSHA1(id, key, method, uri, {ts: ts,
nonce: nonce});
do_check_eq(btoa(result.mac), "jzh5chjQc2zFEvLbyHnPdX11Yck=");
do_check_eq(result.getHeader(),
'MAC id="vmo1txkttblmn51u2p3zk2xiy16hgvm5ok8qiv1yyi86ffjzy9zj0ez9x6wnvbx7", ' +
'ts="1329181221", nonce="wGX71", mac="jzh5chjQc2zFEvLbyHnPdX11Yck="');
let ext = "EXTRA DATA; foo,bar=1";
let result = Utils.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", ' +
'ts="1329181221", nonce="wGX71", mac="bNf4Fnt5k6DnhmyipLPkuZroH68=", ' +
'ext="EXTRA DATA; foo,bar=1"');
run_next_test();
});
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, {
nonce_bytes: length
});
}
let result = get_mac(12);
do_check_eq(12, atob(result.nonce).length);
let result = get_mac(2);
do_check_eq(2, atob(result.nonce).length);
let result = get_mac(0);
do_check_eq(8, atob(result.nonce).length);
let result = get_mac(-1);
do_check_eq(8, atob(result.nonce).length);
run_next_test();
});

Просмотреть файл

@ -115,6 +115,7 @@ skip-if = os == "android"
[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]