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