346 lines
11 KiB
JavaScript
346 lines
11 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
const { CryptoUtils } = ChromeUtils.importESModule(
|
|
"resource://services-crypto/utils.sys.mjs"
|
|
);
|
|
|
|
function run_test() {
|
|
initTestLogging();
|
|
|
|
run_next_test();
|
|
}
|
|
|
|
add_task(async function test_hawk() {
|
|
let compute = CryptoUtils.computeHAWK;
|
|
|
|
let method = "POST";
|
|
let ts = 1353809207;
|
|
let nonce = "Ygvqdz";
|
|
|
|
let credentials = {
|
|
id: "123456",
|
|
key: "2983d45yun89q",
|
|
};
|
|
|
|
let uri_https = CommonUtils.makeURI(
|
|
"https://example.net/somewhere/over/the/rainbow"
|
|
);
|
|
let opts = {
|
|
credentials,
|
|
ext: "Bazinga!",
|
|
ts,
|
|
nonce,
|
|
payload: "something to write about",
|
|
contentType: "text/plain",
|
|
};
|
|
|
|
let result = await compute(uri_https, method, opts);
|
|
Assert.equal(
|
|
result.field,
|
|
'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
|
|
'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
|
|
'ext="Bazinga!", ' +
|
|
'mac="q1CwFoSHzPZSkbIvl0oYlD+91rBUEvFk763nMjMndj8="'
|
|
);
|
|
Assert.equal(result.artifacts.ts, ts);
|
|
Assert.equal(result.artifacts.nonce, nonce);
|
|
Assert.equal(result.artifacts.method, method);
|
|
Assert.equal(result.artifacts.resource, "/somewhere/over/the/rainbow");
|
|
Assert.equal(result.artifacts.host, "example.net");
|
|
Assert.equal(result.artifacts.port, 443);
|
|
Assert.equal(
|
|
result.artifacts.hash,
|
|
"2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY="
|
|
);
|
|
Assert.equal(result.artifacts.ext, "Bazinga!");
|
|
|
|
let opts_noext = {
|
|
credentials,
|
|
ts,
|
|
nonce,
|
|
payload: "something to write about",
|
|
contentType: "text/plain",
|
|
};
|
|
result = await compute(uri_https, method, opts_noext);
|
|
Assert.equal(
|
|
result.field,
|
|
'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
|
|
'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
|
|
'mac="HTgtd0jPI6E4izx8e4OHdO36q00xFCU0FolNq3RiCYs="'
|
|
);
|
|
Assert.equal(result.artifacts.ts, ts);
|
|
Assert.equal(result.artifacts.nonce, nonce);
|
|
Assert.equal(result.artifacts.method, method);
|
|
Assert.equal(result.artifacts.resource, "/somewhere/over/the/rainbow");
|
|
Assert.equal(result.artifacts.host, "example.net");
|
|
Assert.equal(result.artifacts.port, 443);
|
|
Assert.equal(
|
|
result.artifacts.hash,
|
|
"2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY="
|
|
);
|
|
|
|
/* Leaving optional fields out should work, although of course then we can't
|
|
* assert much about the resulting hashes. The resulting header should look
|
|
* roughly like:
|
|
* Hawk id="123456", ts="1378764955", nonce="QkynqsrS44M=", mac="/C5NsoAs2fVn+d/I5wMfwe2Gr1MZyAJ6pFyDHG4Gf9U="
|
|
*/
|
|
|
|
result = await compute(uri_https, method, { credentials });
|
|
let fields = result.field.split(" ");
|
|
Assert.equal(fields[0], "Hawk");
|
|
Assert.equal(fields[1], 'id="123456",'); // from creds.id
|
|
Assert.ok(fields[2].startsWith('ts="'));
|
|
/* The HAWK spec calls for seconds-since-epoch, not ms-since-epoch.
|
|
* Warning: this test will fail in the year 33658, and for time travellers
|
|
* who journey earlier than 2001. Please plan accordingly. */
|
|
Assert.ok(result.artifacts.ts > 1000 * 1000 * 1000);
|
|
Assert.ok(result.artifacts.ts < 1000 * 1000 * 1000 * 1000);
|
|
Assert.ok(fields[3].startsWith('nonce="'));
|
|
Assert.equal(fields[3].length, 'nonce="12345678901=",'.length);
|
|
Assert.equal(result.artifacts.nonce.length, "12345678901=".length);
|
|
|
|
let result2 = await compute(uri_https, method, { credentials });
|
|
Assert.notEqual(result.artifacts.nonce, result2.artifacts.nonce);
|
|
|
|
/* Using an upper-case URI hostname shouldn't affect the hash. */
|
|
|
|
let uri_https_upper = CommonUtils.makeURI(
|
|
"https://EXAMPLE.NET/somewhere/over/the/rainbow"
|
|
);
|
|
result = await compute(uri_https_upper, method, opts);
|
|
Assert.equal(
|
|
result.field,
|
|
'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
|
|
'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
|
|
'ext="Bazinga!", ' +
|
|
'mac="q1CwFoSHzPZSkbIvl0oYlD+91rBUEvFk763nMjMndj8="'
|
|
);
|
|
|
|
/* Using a lower-case method name shouldn't affect the hash. */
|
|
result = await compute(uri_https_upper, method.toLowerCase(), opts);
|
|
Assert.equal(
|
|
result.field,
|
|
'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
|
|
'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
|
|
'ext="Bazinga!", ' +
|
|
'mac="q1CwFoSHzPZSkbIvl0oYlD+91rBUEvFk763nMjMndj8="'
|
|
);
|
|
|
|
/* The localtimeOffsetMsec field should be honored. HAWK uses this to
|
|
* compensate for clock skew between client and server: if the request is
|
|
* rejected with a timestamp out-of-range error, the error includes the
|
|
* server's time, and the client computes its clock offset and tries again.
|
|
* Clients can remember this offset for a while.
|
|
*/
|
|
|
|
result = await compute(uri_https, method, {
|
|
credentials,
|
|
now: 1378848968650,
|
|
});
|
|
Assert.equal(result.artifacts.ts, 1378848968);
|
|
|
|
result = await compute(uri_https, method, {
|
|
credentials,
|
|
now: 1378848968650,
|
|
localtimeOffsetMsec: 1000 * 1000,
|
|
});
|
|
Assert.equal(result.artifacts.ts, 1378848968 + 1000);
|
|
|
|
/* Search/query-args in URIs should be included in the hash. */
|
|
let makeURI = CommonUtils.makeURI;
|
|
result = await compute(makeURI("http://example.net/path"), method, opts);
|
|
Assert.equal(result.artifacts.resource, "/path");
|
|
Assert.equal(
|
|
result.artifacts.mac,
|
|
"WyKHJjWaeYt8aJD+H9UeCWc0Y9C+07ooTmrcrOW4MPI="
|
|
);
|
|
|
|
result = await compute(makeURI("http://example.net/path/"), method, opts);
|
|
Assert.equal(result.artifacts.resource, "/path/");
|
|
Assert.equal(
|
|
result.artifacts.mac,
|
|
"xAYp2MgZQFvTKJT9u8nsvMjshCRRkuaeYqQbYSFp9Qw="
|
|
);
|
|
|
|
result = await compute(
|
|
makeURI("http://example.net/path?query=search"),
|
|
method,
|
|
opts
|
|
);
|
|
Assert.equal(result.artifacts.resource, "/path?query=search");
|
|
Assert.equal(
|
|
result.artifacts.mac,
|
|
"C06a8pip2rA4QkBiosEmC32WcgFcW/R5SQC6kUWyqho="
|
|
);
|
|
|
|
/* Test handling of the payload, which is supposed to be a bytestring
|
|
(String with codepoints from U+0000 to U+00FF, pre-encoded). */
|
|
|
|
result = await compute(makeURI("http://example.net/path"), method, {
|
|
credentials,
|
|
ts: 1353809207,
|
|
nonce: "Ygvqdz",
|
|
});
|
|
Assert.equal(result.artifacts.hash, undefined);
|
|
Assert.equal(
|
|
result.artifacts.mac,
|
|
"S3f8E4hAURAqJxOlsYugkPZxLoRYrClgbSQ/3FmKMbY="
|
|
);
|
|
|
|
// Empty payload changes nothing.
|
|
result = await compute(makeURI("http://example.net/path"), method, {
|
|
credentials,
|
|
ts: 1353809207,
|
|
nonce: "Ygvqdz",
|
|
payload: null,
|
|
});
|
|
Assert.equal(result.artifacts.hash, undefined);
|
|
Assert.equal(
|
|
result.artifacts.mac,
|
|
"S3f8E4hAURAqJxOlsYugkPZxLoRYrClgbSQ/3FmKMbY="
|
|
);
|
|
|
|
result = await compute(makeURI("http://example.net/path"), method, {
|
|
credentials,
|
|
ts: 1353809207,
|
|
nonce: "Ygvqdz",
|
|
payload: "hello",
|
|
});
|
|
Assert.equal(
|
|
result.artifacts.hash,
|
|
"uZJnFj0XVBA6Rs1hEvdIDf8NraM0qRNXdFbR3NEQbVA="
|
|
);
|
|
Assert.equal(
|
|
result.artifacts.mac,
|
|
"pLsHHzngIn5CTJhWBtBr+BezUFvdd/IadpTp/FYVIRM="
|
|
);
|
|
|
|
// update, utf-8 payload
|
|
result = await compute(makeURI("http://example.net/path"), method, {
|
|
credentials,
|
|
ts: 1353809207,
|
|
nonce: "Ygvqdz",
|
|
payload: "andré@example.org", // non-ASCII
|
|
});
|
|
Assert.equal(
|
|
result.artifacts.hash,
|
|
"66DiyapJ0oGgj09IXWdMv8VCg9xk0PL5RqX7bNnQW2k="
|
|
);
|
|
Assert.equal(
|
|
result.artifacts.mac,
|
|
"2B++3x5xfHEZbPZGDiK3IwfPZctkV4DUr2ORg1vIHvk="
|
|
);
|
|
|
|
/* If "hash" is provided, "payload" is ignored. */
|
|
result = await compute(makeURI("http://example.net/path"), method, {
|
|
credentials,
|
|
ts: 1353809207,
|
|
nonce: "Ygvqdz",
|
|
hash: "66DiyapJ0oGgj09IXWdMv8VCg9xk0PL5RqX7bNnQW2k=",
|
|
payload: "something else",
|
|
});
|
|
Assert.equal(
|
|
result.artifacts.hash,
|
|
"66DiyapJ0oGgj09IXWdMv8VCg9xk0PL5RqX7bNnQW2k="
|
|
);
|
|
Assert.equal(
|
|
result.artifacts.mac,
|
|
"2B++3x5xfHEZbPZGDiK3IwfPZctkV4DUr2ORg1vIHvk="
|
|
);
|
|
|
|
// the payload "hash" is also non-urlsafe base64 (+/)
|
|
result = await compute(makeURI("http://example.net/path"), method, {
|
|
credentials,
|
|
ts: 1353809207,
|
|
nonce: "Ygvqdz",
|
|
payload: "something else",
|
|
});
|
|
Assert.equal(
|
|
result.artifacts.hash,
|
|
"lERFXr/IKOaAoYw+eBseDUSwmqZTX0uKZpcWLxsdzt8="
|
|
);
|
|
Assert.equal(
|
|
result.artifacts.mac,
|
|
"jiZuhsac35oD7IdcblhFncBr8tJFHcwWLr8NIYWr9PQ="
|
|
);
|
|
|
|
/* Test non-ascii hostname. HAWK (via the node.js "url" module) punycodes
|
|
* "ëxample.net" into "xn--xample-ova.net" before hashing. I still think
|
|
* punycode was a bad joke that got out of the lab and into a spec.
|
|
*/
|
|
|
|
result = await compute(makeURI("http://ëxample.net/path"), method, {
|
|
credentials,
|
|
ts: 1353809207,
|
|
nonce: "Ygvqdz",
|
|
});
|
|
Assert.equal(
|
|
result.artifacts.mac,
|
|
"pILiHl1q8bbNQIdaaLwAFyaFmDU70MGehFuCs3AA5M0="
|
|
);
|
|
Assert.equal(result.artifacts.host, "xn--xample-ova.net");
|
|
|
|
result = await compute(makeURI("http://example.net/path"), method, {
|
|
credentials,
|
|
ts: 1353809207,
|
|
nonce: "Ygvqdz",
|
|
ext: 'backslash=\\ quote=" EOF',
|
|
});
|
|
Assert.equal(
|
|
result.artifacts.mac,
|
|
"BEMW76lwaJlPX4E/dajF970T6+GzWvaeyLzUt8eOTOc="
|
|
);
|
|
Assert.equal(
|
|
result.field,
|
|
'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ext="backslash=\\\\ quote=\\" EOF", mac="BEMW76lwaJlPX4E/dajF970T6+GzWvaeyLzUt8eOTOc="'
|
|
);
|
|
|
|
result = await compute(makeURI("http://example.net:1234/path"), method, {
|
|
credentials,
|
|
ts: 1353809207,
|
|
nonce: "Ygvqdz",
|
|
});
|
|
Assert.equal(
|
|
result.artifacts.mac,
|
|
"6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE="
|
|
);
|
|
Assert.equal(
|
|
result.field,
|
|
'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", mac="6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE="'
|
|
);
|
|
|
|
/* HAWK (the node.js library) uses a URL parser which stores the "port"
|
|
* field as a string, but makeURI() gives us an integer. So we'll diverge
|
|
* on ports with a leading zero. This test vector would fail on the node.js
|
|
* library (HAWK-1.1.1), where they get a MAC of
|
|
* "T+GcAsDO8GRHIvZLeepSvXLwDlFJugcZroAy9+uAtcw=". I think HAWK should be
|
|
* updated to do what we do here, so port="01234" should get the same hash
|
|
* as port="1234".
|
|
*/
|
|
result = await compute(makeURI("http://example.net:01234/path"), method, {
|
|
credentials,
|
|
ts: 1353809207,
|
|
nonce: "Ygvqdz",
|
|
});
|
|
Assert.equal(
|
|
result.artifacts.mac,
|
|
"6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE="
|
|
);
|
|
Assert.equal(
|
|
result.field,
|
|
'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", mac="6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE="'
|
|
);
|
|
});
|
|
|
|
add_test(function test_strip_header_attributes() {
|
|
let strip = CryptoUtils.stripHeaderAttributes;
|
|
|
|
Assert.equal(strip(undefined), "");
|
|
Assert.equal(strip("text/plain"), "text/plain");
|
|
Assert.equal(strip("TEXT/PLAIN"), "text/plain");
|
|
Assert.equal(strip(" text/plain "), "text/plain");
|
|
Assert.equal(strip("text/plain ; charset=utf-8 "), "text/plain");
|
|
|
|
run_next_test();
|
|
});
|