services/crypto/tests/unit/test_utils_hawk.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* Any copyright is dedicated to the Public Domain.
michael@0 2 * http://creativecommons.org/publicdomain/zero/1.0/ */
michael@0 3
michael@0 4 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 5 Cu.import("resource://services-common/utils.js");
michael@0 6 Cu.import("resource://services-crypto/utils.js");
michael@0 7
michael@0 8 function run_test() {
michael@0 9 initTestLogging();
michael@0 10
michael@0 11 run_next_test();
michael@0 12 }
michael@0 13
michael@0 14 add_test(function test_hawk() {
michael@0 15 let compute = CryptoUtils.computeHAWK;
michael@0 16
michael@0 17 // vectors copied from the HAWK (node.js) tests
michael@0 18 let credentials_sha1 = {
michael@0 19 id: "123456",
michael@0 20 key: "2983d45yun89q",
michael@0 21 algorithm: "sha1",
michael@0 22 };
michael@0 23
michael@0 24 let method = "POST";
michael@0 25 let ts = 1353809207;
michael@0 26 let nonce = "Ygvqdz";
michael@0 27 let result;
michael@0 28
michael@0 29 let uri_http = CommonUtils.makeURI("http://example.net/somewhere/over/the/rainbow");
michael@0 30 let sha1_opts = { credentials: credentials_sha1,
michael@0 31 ext: "Bazinga!",
michael@0 32 ts: ts,
michael@0 33 nonce: nonce,
michael@0 34 payload: "something to write about",
michael@0 35 };
michael@0 36 result = compute(uri_http, method, sha1_opts);
michael@0 37
michael@0 38 // The HAWK spec uses non-urlsafe base64 (+/) for its output MAC string.
michael@0 39 do_check_eq(result.field,
michael@0 40 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
michael@0 41 'hash="bsvY3IfUllw6V5rvk4tStEvpBhE=", ext="Bazinga!", ' +
michael@0 42 'mac="qbf1ZPG/r/e06F4ht+T77LXi5vw="'
michael@0 43 );
michael@0 44 do_check_eq(result.artifacts.ts, ts);
michael@0 45 do_check_eq(result.artifacts.nonce, nonce);
michael@0 46 do_check_eq(result.artifacts.method, method);
michael@0 47 do_check_eq(result.artifacts.resource, "/somewhere/over/the/rainbow");
michael@0 48 do_check_eq(result.artifacts.host, "example.net");
michael@0 49 do_check_eq(result.artifacts.port, 80);
michael@0 50 // artifacts.hash is the *payload* hash, not the overall request MAC.
michael@0 51 do_check_eq(result.artifacts.hash, "bsvY3IfUllw6V5rvk4tStEvpBhE=");
michael@0 52 do_check_eq(result.artifacts.ext, "Bazinga!");
michael@0 53
michael@0 54 let credentials_sha256 = {
michael@0 55 id: "123456",
michael@0 56 key: "2983d45yun89q",
michael@0 57 algorithm: "sha256",
michael@0 58 };
michael@0 59
michael@0 60 let uri_https = CommonUtils.makeURI("https://example.net/somewhere/over/the/rainbow");
michael@0 61 let sha256_opts = { credentials: credentials_sha256,
michael@0 62 ext: "Bazinga!",
michael@0 63 ts: ts,
michael@0 64 nonce: nonce,
michael@0 65 payload: "something to write about",
michael@0 66 contentType: "text/plain",
michael@0 67 };
michael@0 68
michael@0 69 result = compute(uri_https, method, sha256_opts);
michael@0 70 do_check_eq(result.field,
michael@0 71 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
michael@0 72 'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
michael@0 73 'ext="Bazinga!", ' +
michael@0 74 'mac="q1CwFoSHzPZSkbIvl0oYlD+91rBUEvFk763nMjMndj8="'
michael@0 75 );
michael@0 76 do_check_eq(result.artifacts.ts, ts);
michael@0 77 do_check_eq(result.artifacts.nonce, nonce);
michael@0 78 do_check_eq(result.artifacts.method, method);
michael@0 79 do_check_eq(result.artifacts.resource, "/somewhere/over/the/rainbow");
michael@0 80 do_check_eq(result.artifacts.host, "example.net");
michael@0 81 do_check_eq(result.artifacts.port, 443);
michael@0 82 do_check_eq(result.artifacts.hash, "2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=");
michael@0 83 do_check_eq(result.artifacts.ext, "Bazinga!");
michael@0 84
michael@0 85 let sha256_opts_noext = { credentials: credentials_sha256,
michael@0 86 ts: ts,
michael@0 87 nonce: nonce,
michael@0 88 payload: "something to write about",
michael@0 89 contentType: "text/plain",
michael@0 90 };
michael@0 91 result = compute(uri_https, method, sha256_opts_noext);
michael@0 92 do_check_eq(result.field,
michael@0 93 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
michael@0 94 'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
michael@0 95 'mac="HTgtd0jPI6E4izx8e4OHdO36q00xFCU0FolNq3RiCYs="'
michael@0 96 );
michael@0 97 do_check_eq(result.artifacts.ts, ts);
michael@0 98 do_check_eq(result.artifacts.nonce, nonce);
michael@0 99 do_check_eq(result.artifacts.method, method);
michael@0 100 do_check_eq(result.artifacts.resource, "/somewhere/over/the/rainbow");
michael@0 101 do_check_eq(result.artifacts.host, "example.net");
michael@0 102 do_check_eq(result.artifacts.port, 443);
michael@0 103 do_check_eq(result.artifacts.hash, "2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=");
michael@0 104
michael@0 105 /* Leaving optional fields out should work, although of course then we can't
michael@0 106 * assert much about the resulting hashes. The resulting header should look
michael@0 107 * roughly like:
michael@0 108 * Hawk id="123456", ts="1378764955", nonce="QkynqsrS44M=", mac="/C5NsoAs2fVn+d/I5wMfwe2Gr1MZyAJ6pFyDHG4Gf9U="
michael@0 109 */
michael@0 110
michael@0 111 result = compute(uri_https, method, { credentials: credentials_sha256 });
michael@0 112 let fields = result.field.split(" ");
michael@0 113 do_check_eq(fields[0], "Hawk");
michael@0 114 do_check_eq(fields[1], 'id="123456",'); // from creds.id
michael@0 115 do_check_true(fields[2].startsWith('ts="'));
michael@0 116 /* The HAWK spec calls for seconds-since-epoch, not ms-since-epoch.
michael@0 117 * Warning: this test will fail in the year 33658, and for time travellers
michael@0 118 * who journey earlier than 2001. Please plan accordingly. */
michael@0 119 do_check_true(result.artifacts.ts > 1000*1000*1000);
michael@0 120 do_check_true(result.artifacts.ts < 1000*1000*1000*1000);
michael@0 121 do_check_true(fields[3].startsWith('nonce="'));
michael@0 122 do_check_eq(fields[3].length, ('nonce="12345678901=",').length);
michael@0 123 do_check_eq(result.artifacts.nonce.length, ("12345678901=").length);
michael@0 124
michael@0 125 let result2 = compute(uri_https, method, { credentials: credentials_sha256 });
michael@0 126 do_check_neq(result.artifacts.nonce, result2.artifacts.nonce);
michael@0 127
michael@0 128 /* Using an upper-case URI hostname shouldn't affect the hash. */
michael@0 129
michael@0 130 let uri_https_upper = CommonUtils.makeURI("https://EXAMPLE.NET/somewhere/over/the/rainbow");
michael@0 131 result = compute(uri_https_upper, method, sha256_opts);
michael@0 132 do_check_eq(result.field,
michael@0 133 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
michael@0 134 'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
michael@0 135 'ext="Bazinga!", ' +
michael@0 136 'mac="q1CwFoSHzPZSkbIvl0oYlD+91rBUEvFk763nMjMndj8="'
michael@0 137 );
michael@0 138
michael@0 139 /* Using a lower-case method name shouldn't affect the hash. */
michael@0 140 result = compute(uri_https_upper, method.toLowerCase(), sha256_opts);
michael@0 141 do_check_eq(result.field,
michael@0 142 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
michael@0 143 'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
michael@0 144 'ext="Bazinga!", ' +
michael@0 145 'mac="q1CwFoSHzPZSkbIvl0oYlD+91rBUEvFk763nMjMndj8="'
michael@0 146 );
michael@0 147
michael@0 148 /* The localtimeOffsetMsec field should be honored. HAWK uses this to
michael@0 149 * compensate for clock skew between client and server: if the request is
michael@0 150 * rejected with a timestamp out-of-range error, the error includes the
michael@0 151 * server's time, and the client computes its clock offset and tries again.
michael@0 152 * Clients can remember this offset for a while.
michael@0 153 */
michael@0 154
michael@0 155 result = compute(uri_https, method, { credentials: credentials_sha256,
michael@0 156 now: 1378848968650,
michael@0 157 });
michael@0 158 do_check_eq(result.artifacts.ts, 1378848968);
michael@0 159
michael@0 160 result = compute(uri_https, method, { credentials: credentials_sha256,
michael@0 161 now: 1378848968650,
michael@0 162 localtimeOffsetMsec: 1000*1000,
michael@0 163 });
michael@0 164 do_check_eq(result.artifacts.ts, 1378848968 + 1000);
michael@0 165
michael@0 166 /* Search/query-args in URIs should be included in the hash. */
michael@0 167 let makeURI = CommonUtils.makeURI;
michael@0 168 result = compute(makeURI("http://example.net/path"), method, sha256_opts);
michael@0 169 do_check_eq(result.artifacts.resource, "/path");
michael@0 170 do_check_eq(result.artifacts.mac, "WyKHJjWaeYt8aJD+H9UeCWc0Y9C+07ooTmrcrOW4MPI=");
michael@0 171
michael@0 172 result = compute(makeURI("http://example.net/path/"), method, sha256_opts);
michael@0 173 do_check_eq(result.artifacts.resource, "/path/");
michael@0 174 do_check_eq(result.artifacts.mac, "xAYp2MgZQFvTKJT9u8nsvMjshCRRkuaeYqQbYSFp9Qw=");
michael@0 175
michael@0 176 result = compute(makeURI("http://example.net/path?query=search"), method, sha256_opts);
michael@0 177 do_check_eq(result.artifacts.resource, "/path?query=search");
michael@0 178 do_check_eq(result.artifacts.mac, "C06a8pip2rA4QkBiosEmC32WcgFcW/R5SQC6kUWyqho=");
michael@0 179
michael@0 180 /* Test handling of the payload, which is supposed to be a bytestring
michael@0 181 (String with codepoints from U+0000 to U+00FF, pre-encoded). */
michael@0 182
michael@0 183 result = compute(makeURI("http://example.net/path"), method,
michael@0 184 { credentials: credentials_sha256,
michael@0 185 ts: 1353809207,
michael@0 186 nonce: "Ygvqdz",
michael@0 187 });
michael@0 188 do_check_eq(result.artifacts.hash, undefined);
michael@0 189 do_check_eq(result.artifacts.mac, "S3f8E4hAURAqJxOlsYugkPZxLoRYrClgbSQ/3FmKMbY=");
michael@0 190
michael@0 191 // Empty payload changes nothing.
michael@0 192 result = compute(makeURI("http://example.net/path"), method,
michael@0 193 { credentials: credentials_sha256,
michael@0 194 ts: 1353809207,
michael@0 195 nonce: "Ygvqdz",
michael@0 196 payload: null,
michael@0 197 });
michael@0 198 do_check_eq(result.artifacts.hash, undefined);
michael@0 199 do_check_eq(result.artifacts.mac, "S3f8E4hAURAqJxOlsYugkPZxLoRYrClgbSQ/3FmKMbY=");
michael@0 200
michael@0 201 result = compute(makeURI("http://example.net/path"), method,
michael@0 202 { credentials: credentials_sha256,
michael@0 203 ts: 1353809207,
michael@0 204 nonce: "Ygvqdz",
michael@0 205 payload: "hello",
michael@0 206 });
michael@0 207 do_check_eq(result.artifacts.hash, "uZJnFj0XVBA6Rs1hEvdIDf8NraM0qRNXdFbR3NEQbVA=");
michael@0 208 do_check_eq(result.artifacts.mac, "pLsHHzngIn5CTJhWBtBr+BezUFvdd/IadpTp/FYVIRM=");
michael@0 209
michael@0 210 // update, utf-8 payload
michael@0 211 result = compute(makeURI("http://example.net/path"), method,
michael@0 212 { credentials: credentials_sha256,
michael@0 213 ts: 1353809207,
michael@0 214 nonce: "Ygvqdz",
michael@0 215 payload: "andré@example.org", // non-ASCII
michael@0 216 });
michael@0 217 do_check_eq(result.artifacts.hash, "66DiyapJ0oGgj09IXWdMv8VCg9xk0PL5RqX7bNnQW2k=");
michael@0 218 do_check_eq(result.artifacts.mac, "2B++3x5xfHEZbPZGDiK3IwfPZctkV4DUr2ORg1vIHvk=");
michael@0 219
michael@0 220 /* If "hash" is provided, "payload" is ignored. */
michael@0 221 result = compute(makeURI("http://example.net/path"), method,
michael@0 222 { credentials: credentials_sha256,
michael@0 223 ts: 1353809207,
michael@0 224 nonce: "Ygvqdz",
michael@0 225 hash: "66DiyapJ0oGgj09IXWdMv8VCg9xk0PL5RqX7bNnQW2k=",
michael@0 226 payload: "something else",
michael@0 227 });
michael@0 228 do_check_eq(result.artifacts.hash, "66DiyapJ0oGgj09IXWdMv8VCg9xk0PL5RqX7bNnQW2k=");
michael@0 229 do_check_eq(result.artifacts.mac, "2B++3x5xfHEZbPZGDiK3IwfPZctkV4DUr2ORg1vIHvk=");
michael@0 230
michael@0 231 // the payload "hash" is also non-urlsafe base64 (+/)
michael@0 232 result = compute(makeURI("http://example.net/path"), method,
michael@0 233 { credentials: credentials_sha256,
michael@0 234 ts: 1353809207,
michael@0 235 nonce: "Ygvqdz",
michael@0 236 payload: "something else",
michael@0 237 });
michael@0 238 do_check_eq(result.artifacts.hash, "lERFXr/IKOaAoYw+eBseDUSwmqZTX0uKZpcWLxsdzt8=");
michael@0 239 do_check_eq(result.artifacts.mac, "jiZuhsac35oD7IdcblhFncBr8tJFHcwWLr8NIYWr9PQ=");
michael@0 240
michael@0 241 /* Test non-ascii hostname. HAWK (via the node.js "url" module) punycodes
michael@0 242 * "ëxample.net" into "xn--xample-ova.net" before hashing. I still think
michael@0 243 * punycode was a bad joke that got out of the lab and into a spec.
michael@0 244 */
michael@0 245
michael@0 246 result = compute(makeURI("http://ëxample.net/path"), method,
michael@0 247 { credentials: credentials_sha256,
michael@0 248 ts: 1353809207,
michael@0 249 nonce: "Ygvqdz",
michael@0 250 });
michael@0 251 do_check_eq(result.artifacts.mac, "pILiHl1q8bbNQIdaaLwAFyaFmDU70MGehFuCs3AA5M0=");
michael@0 252 do_check_eq(result.artifacts.host, "xn--xample-ova.net");
michael@0 253
michael@0 254 result = compute(makeURI("http://example.net/path"), method,
michael@0 255 { credentials: credentials_sha256,
michael@0 256 ts: 1353809207,
michael@0 257 nonce: "Ygvqdz",
michael@0 258 ext: "backslash=\\ quote=\" EOF",
michael@0 259 });
michael@0 260 do_check_eq(result.artifacts.mac, "BEMW76lwaJlPX4E/dajF970T6+GzWvaeyLzUt8eOTOc=");
michael@0 261 do_check_eq(result.field, 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ext="backslash=\\\\ quote=\\\" EOF", mac="BEMW76lwaJlPX4E/dajF970T6+GzWvaeyLzUt8eOTOc="');
michael@0 262
michael@0 263 result = compute(makeURI("http://example.net:1234/path"), method,
michael@0 264 { credentials: credentials_sha256,
michael@0 265 ts: 1353809207,
michael@0 266 nonce: "Ygvqdz",
michael@0 267 });
michael@0 268 do_check_eq(result.artifacts.mac, "6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE=");
michael@0 269 do_check_eq(result.field, 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", mac="6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE="');
michael@0 270
michael@0 271 /* HAWK (the node.js library) uses a URL parser which stores the "port"
michael@0 272 * field as a string, but makeURI() gives us an integer. So we'll diverge
michael@0 273 * on ports with a leading zero. This test vector would fail on the node.js
michael@0 274 * library (HAWK-1.1.1), where they get a MAC of
michael@0 275 * "T+GcAsDO8GRHIvZLeepSvXLwDlFJugcZroAy9+uAtcw=". I think HAWK should be
michael@0 276 * updated to do what we do here, so port="01234" should get the same hash
michael@0 277 * as port="1234".
michael@0 278 */
michael@0 279 result = compute(makeURI("http://example.net:01234/path"), method,
michael@0 280 { credentials: credentials_sha256,
michael@0 281 ts: 1353809207,
michael@0 282 nonce: "Ygvqdz",
michael@0 283 });
michael@0 284 do_check_eq(result.artifacts.mac, "6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE=");
michael@0 285 do_check_eq(result.field, 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", mac="6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE="');
michael@0 286
michael@0 287 run_next_test();
michael@0 288 });
michael@0 289
michael@0 290
michael@0 291 add_test(function test_strip_header_attributes() {
michael@0 292 let strip = CryptoUtils.stripHeaderAttributes;
michael@0 293
michael@0 294 do_check_eq(strip(undefined), "");
michael@0 295 do_check_eq(strip("text/plain"), "text/plain");
michael@0 296 do_check_eq(strip("TEXT/PLAIN"), "text/plain");
michael@0 297 do_check_eq(strip(" text/plain "), "text/plain");
michael@0 298 do_check_eq(strip("text/plain ; charset=utf-8 "), "text/plain");
michael@0 299
michael@0 300 run_next_test();
michael@0 301 });

mercurial