services/crypto/tests/unit/test_utils_hawk.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/services/crypto/tests/unit/test_utils_hawk.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,301 @@
     1.4 +/* Any copyright is dedicated to the Public Domain.
     1.5 + * http://creativecommons.org/publicdomain/zero/1.0/ */
     1.6 +
     1.7 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
     1.8 +Cu.import("resource://services-common/utils.js");
     1.9 +Cu.import("resource://services-crypto/utils.js");
    1.10 +
    1.11 +function run_test() {
    1.12 +  initTestLogging();
    1.13 +
    1.14 +  run_next_test();
    1.15 +}
    1.16 +
    1.17 +add_test(function test_hawk() {
    1.18 +  let compute = CryptoUtils.computeHAWK;
    1.19 +
    1.20 +  // vectors copied from the HAWK (node.js) tests
    1.21 +  let credentials_sha1 = {
    1.22 +    id: "123456",
    1.23 +    key: "2983d45yun89q",
    1.24 +    algorithm: "sha1",
    1.25 +  };
    1.26 +
    1.27 +  let method = "POST";
    1.28 +  let ts = 1353809207;
    1.29 +  let nonce = "Ygvqdz";
    1.30 +  let result;
    1.31 +
    1.32 +  let uri_http = CommonUtils.makeURI("http://example.net/somewhere/over/the/rainbow");
    1.33 +  let sha1_opts = { credentials: credentials_sha1,
    1.34 +                    ext: "Bazinga!",
    1.35 +                    ts: ts,
    1.36 +                    nonce: nonce,
    1.37 +                    payload: "something to write about",
    1.38 +                  };
    1.39 +  result = compute(uri_http, method, sha1_opts);
    1.40 +
    1.41 +  // The HAWK spec uses non-urlsafe base64 (+/) for its output MAC string.
    1.42 +  do_check_eq(result.field,
    1.43 +              'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
    1.44 +              'hash="bsvY3IfUllw6V5rvk4tStEvpBhE=", ext="Bazinga!", ' +
    1.45 +              'mac="qbf1ZPG/r/e06F4ht+T77LXi5vw="'
    1.46 +             );
    1.47 +  do_check_eq(result.artifacts.ts, ts);
    1.48 +  do_check_eq(result.artifacts.nonce, nonce);
    1.49 +  do_check_eq(result.artifacts.method, method);
    1.50 +  do_check_eq(result.artifacts.resource, "/somewhere/over/the/rainbow");
    1.51 +  do_check_eq(result.artifacts.host, "example.net");
    1.52 +  do_check_eq(result.artifacts.port, 80);
    1.53 +  // artifacts.hash is the *payload* hash, not the overall request MAC.
    1.54 +  do_check_eq(result.artifacts.hash, "bsvY3IfUllw6V5rvk4tStEvpBhE=");
    1.55 +  do_check_eq(result.artifacts.ext, "Bazinga!");
    1.56 +
    1.57 +  let credentials_sha256 = {
    1.58 +    id: "123456",
    1.59 +    key: "2983d45yun89q",
    1.60 +    algorithm: "sha256",
    1.61 +  };
    1.62 +
    1.63 +  let uri_https = CommonUtils.makeURI("https://example.net/somewhere/over/the/rainbow");
    1.64 +  let sha256_opts = { credentials: credentials_sha256,
    1.65 +                      ext: "Bazinga!",
    1.66 +                      ts: ts,
    1.67 +                      nonce: nonce,
    1.68 +                      payload: "something to write about",
    1.69 +                      contentType: "text/plain",
    1.70 +                    };
    1.71 +
    1.72 +  result = compute(uri_https, method, sha256_opts);
    1.73 +  do_check_eq(result.field,
    1.74 +              'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
    1.75 +              'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
    1.76 +              'ext="Bazinga!", ' +
    1.77 +              'mac="q1CwFoSHzPZSkbIvl0oYlD+91rBUEvFk763nMjMndj8="'
    1.78 +             );
    1.79 +  do_check_eq(result.artifacts.ts, ts);
    1.80 +  do_check_eq(result.artifacts.nonce, nonce);
    1.81 +  do_check_eq(result.artifacts.method, method);
    1.82 +  do_check_eq(result.artifacts.resource, "/somewhere/over/the/rainbow");
    1.83 +  do_check_eq(result.artifacts.host, "example.net");
    1.84 +  do_check_eq(result.artifacts.port, 443);
    1.85 +  do_check_eq(result.artifacts.hash, "2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=");
    1.86 +  do_check_eq(result.artifacts.ext, "Bazinga!");
    1.87 +
    1.88 +  let sha256_opts_noext = { credentials: credentials_sha256,
    1.89 +                            ts: ts,
    1.90 +                            nonce: nonce,
    1.91 +                            payload: "something to write about",
    1.92 +                            contentType: "text/plain",
    1.93 +                          };
    1.94 +  result = compute(uri_https, method, sha256_opts_noext);
    1.95 +  do_check_eq(result.field,
    1.96 +              'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
    1.97 +              'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
    1.98 +              'mac="HTgtd0jPI6E4izx8e4OHdO36q00xFCU0FolNq3RiCYs="'
    1.99 +             );
   1.100 +  do_check_eq(result.artifacts.ts, ts);
   1.101 +  do_check_eq(result.artifacts.nonce, nonce);
   1.102 +  do_check_eq(result.artifacts.method, method);
   1.103 +  do_check_eq(result.artifacts.resource, "/somewhere/over/the/rainbow");
   1.104 +  do_check_eq(result.artifacts.host, "example.net");
   1.105 +  do_check_eq(result.artifacts.port, 443);
   1.106 +  do_check_eq(result.artifacts.hash, "2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=");
   1.107 +
   1.108 +  /* Leaving optional fields out should work, although of course then we can't
   1.109 +   * assert much about the resulting hashes. The resulting header should look
   1.110 +   * roughly like:
   1.111 +   * Hawk id="123456", ts="1378764955", nonce="QkynqsrS44M=", mac="/C5NsoAs2fVn+d/I5wMfwe2Gr1MZyAJ6pFyDHG4Gf9U="
   1.112 +   */
   1.113 +
   1.114 +  result = compute(uri_https, method, { credentials: credentials_sha256 });
   1.115 +  let fields = result.field.split(" ");
   1.116 +  do_check_eq(fields[0], "Hawk");
   1.117 +  do_check_eq(fields[1], 'id="123456",'); // from creds.id
   1.118 +  do_check_true(fields[2].startsWith('ts="'));
   1.119 +  /* The HAWK spec calls for seconds-since-epoch, not ms-since-epoch.
   1.120 +   * Warning: this test will fail in the year 33658, and for time travellers
   1.121 +   * who journey earlier than 2001. Please plan accordingly. */
   1.122 +  do_check_true(result.artifacts.ts > 1000*1000*1000);
   1.123 +  do_check_true(result.artifacts.ts < 1000*1000*1000*1000);
   1.124 +  do_check_true(fields[3].startsWith('nonce="'));
   1.125 +  do_check_eq(fields[3].length, ('nonce="12345678901=",').length);
   1.126 +  do_check_eq(result.artifacts.nonce.length, ("12345678901=").length);
   1.127 +
   1.128 +  let result2 = compute(uri_https, method, { credentials: credentials_sha256 });
   1.129 +  do_check_neq(result.artifacts.nonce, result2.artifacts.nonce);
   1.130 +
   1.131 +  /* Using an upper-case URI hostname shouldn't affect the hash. */
   1.132 +
   1.133 +  let uri_https_upper = CommonUtils.makeURI("https://EXAMPLE.NET/somewhere/over/the/rainbow");
   1.134 +  result = compute(uri_https_upper, method, sha256_opts);
   1.135 +  do_check_eq(result.field,
   1.136 +              'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
   1.137 +              'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
   1.138 +              'ext="Bazinga!", ' +
   1.139 +              'mac="q1CwFoSHzPZSkbIvl0oYlD+91rBUEvFk763nMjMndj8="'
   1.140 +             );
   1.141 +
   1.142 +  /* Using a lower-case method name shouldn't affect the hash. */
   1.143 +  result = compute(uri_https_upper, method.toLowerCase(), sha256_opts);
   1.144 +  do_check_eq(result.field,
   1.145 +              'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ' +
   1.146 +              'hash="2QfCt3GuY9HQnHWyWD3wX68ZOKbynqlfYmuO2ZBRqtY=", ' +
   1.147 +              'ext="Bazinga!", ' +
   1.148 +              'mac="q1CwFoSHzPZSkbIvl0oYlD+91rBUEvFk763nMjMndj8="'
   1.149 +             );
   1.150 +
   1.151 +  /* The localtimeOffsetMsec field should be honored. HAWK uses this to
   1.152 +   * compensate for clock skew between client and server: if the request is
   1.153 +   * rejected with a timestamp out-of-range error, the error includes the
   1.154 +   * server's time, and the client computes its clock offset and tries again.
   1.155 +   * Clients can remember this offset for a while.
   1.156 +   */
   1.157 +
   1.158 +  result = compute(uri_https, method, { credentials: credentials_sha256,
   1.159 +                                        now: 1378848968650,
   1.160 +                                      });
   1.161 +  do_check_eq(result.artifacts.ts, 1378848968);
   1.162 +
   1.163 +  result = compute(uri_https, method, { credentials: credentials_sha256,
   1.164 +                                        now: 1378848968650,
   1.165 +                                        localtimeOffsetMsec: 1000*1000,
   1.166 +                                      });
   1.167 +  do_check_eq(result.artifacts.ts, 1378848968 + 1000);
   1.168 +
   1.169 +  /* Search/query-args in URIs should be included in the hash. */
   1.170 +  let makeURI = CommonUtils.makeURI;
   1.171 +  result = compute(makeURI("http://example.net/path"), method, sha256_opts);
   1.172 +  do_check_eq(result.artifacts.resource, "/path");
   1.173 +  do_check_eq(result.artifacts.mac, "WyKHJjWaeYt8aJD+H9UeCWc0Y9C+07ooTmrcrOW4MPI=");
   1.174 +
   1.175 +  result = compute(makeURI("http://example.net/path/"), method, sha256_opts);
   1.176 +  do_check_eq(result.artifacts.resource, "/path/");
   1.177 +  do_check_eq(result.artifacts.mac, "xAYp2MgZQFvTKJT9u8nsvMjshCRRkuaeYqQbYSFp9Qw=");
   1.178 +
   1.179 +  result = compute(makeURI("http://example.net/path?query=search"), method, sha256_opts);
   1.180 +  do_check_eq(result.artifacts.resource, "/path?query=search");
   1.181 +  do_check_eq(result.artifacts.mac, "C06a8pip2rA4QkBiosEmC32WcgFcW/R5SQC6kUWyqho=");
   1.182 +
   1.183 +  /* Test handling of the payload, which is supposed to be a bytestring
   1.184 +  (String with codepoints from U+0000 to U+00FF, pre-encoded). */
   1.185 +
   1.186 +  result = compute(makeURI("http://example.net/path"), method,
   1.187 +                   { credentials: credentials_sha256,
   1.188 +                     ts: 1353809207,
   1.189 +                     nonce: "Ygvqdz",
   1.190 +                   });
   1.191 +  do_check_eq(result.artifacts.hash, undefined);
   1.192 +  do_check_eq(result.artifacts.mac, "S3f8E4hAURAqJxOlsYugkPZxLoRYrClgbSQ/3FmKMbY=");
   1.193 +
   1.194 +  // Empty payload changes nothing.
   1.195 +  result = compute(makeURI("http://example.net/path"), method,
   1.196 +                   { credentials: credentials_sha256,
   1.197 +                     ts: 1353809207,
   1.198 +                     nonce: "Ygvqdz",
   1.199 +                     payload: null,
   1.200 +                   });
   1.201 +  do_check_eq(result.artifacts.hash, undefined);
   1.202 +  do_check_eq(result.artifacts.mac, "S3f8E4hAURAqJxOlsYugkPZxLoRYrClgbSQ/3FmKMbY=");
   1.203 +
   1.204 +  result = compute(makeURI("http://example.net/path"), method,
   1.205 +                   { credentials: credentials_sha256,
   1.206 +                     ts: 1353809207,
   1.207 +                     nonce: "Ygvqdz",
   1.208 +                     payload: "hello",
   1.209 +                   });
   1.210 +  do_check_eq(result.artifacts.hash, "uZJnFj0XVBA6Rs1hEvdIDf8NraM0qRNXdFbR3NEQbVA=");
   1.211 +  do_check_eq(result.artifacts.mac, "pLsHHzngIn5CTJhWBtBr+BezUFvdd/IadpTp/FYVIRM=");
   1.212 +
   1.213 +  // update, utf-8 payload
   1.214 +  result = compute(makeURI("http://example.net/path"), method,
   1.215 +                   { credentials: credentials_sha256,
   1.216 +                     ts: 1353809207,
   1.217 +                     nonce: "Ygvqdz",
   1.218 +                     payload: "andré@example.org", // non-ASCII
   1.219 +                   });
   1.220 +  do_check_eq(result.artifacts.hash, "66DiyapJ0oGgj09IXWdMv8VCg9xk0PL5RqX7bNnQW2k=");
   1.221 +  do_check_eq(result.artifacts.mac, "2B++3x5xfHEZbPZGDiK3IwfPZctkV4DUr2ORg1vIHvk=");
   1.222 +
   1.223 +  /* If "hash" is provided, "payload" is ignored. */
   1.224 +  result = compute(makeURI("http://example.net/path"), method,
   1.225 +                   { credentials: credentials_sha256,
   1.226 +                     ts: 1353809207,
   1.227 +                     nonce: "Ygvqdz",
   1.228 +                     hash: "66DiyapJ0oGgj09IXWdMv8VCg9xk0PL5RqX7bNnQW2k=",
   1.229 +                     payload: "something else",
   1.230 +                   });
   1.231 +  do_check_eq(result.artifacts.hash, "66DiyapJ0oGgj09IXWdMv8VCg9xk0PL5RqX7bNnQW2k=");
   1.232 +  do_check_eq(result.artifacts.mac, "2B++3x5xfHEZbPZGDiK3IwfPZctkV4DUr2ORg1vIHvk=");
   1.233 +
   1.234 +  // the payload "hash" is also non-urlsafe base64 (+/)
   1.235 +  result = compute(makeURI("http://example.net/path"), method,
   1.236 +                   { credentials: credentials_sha256,
   1.237 +                     ts: 1353809207,
   1.238 +                     nonce: "Ygvqdz",
   1.239 +                     payload: "something else",
   1.240 +                   });
   1.241 +  do_check_eq(result.artifacts.hash, "lERFXr/IKOaAoYw+eBseDUSwmqZTX0uKZpcWLxsdzt8=");
   1.242 +  do_check_eq(result.artifacts.mac, "jiZuhsac35oD7IdcblhFncBr8tJFHcwWLr8NIYWr9PQ=");
   1.243 +
   1.244 +  /* Test non-ascii hostname. HAWK (via the node.js "url" module) punycodes
   1.245 +   * "ëxample.net" into "xn--xample-ova.net" before hashing. I still think
   1.246 +   * punycode was a bad joke that got out of the lab and into a spec.
   1.247 +   */
   1.248 +
   1.249 +  result = compute(makeURI("http://ëxample.net/path"), method,
   1.250 +                   { credentials: credentials_sha256,
   1.251 +                     ts: 1353809207,
   1.252 +                     nonce: "Ygvqdz",
   1.253 +                   });
   1.254 +  do_check_eq(result.artifacts.mac, "pILiHl1q8bbNQIdaaLwAFyaFmDU70MGehFuCs3AA5M0=");
   1.255 +  do_check_eq(result.artifacts.host, "xn--xample-ova.net");
   1.256 +
   1.257 +  result = compute(makeURI("http://example.net/path"), method,
   1.258 +                   { credentials: credentials_sha256,
   1.259 +                     ts: 1353809207,
   1.260 +                     nonce: "Ygvqdz",
   1.261 +                     ext: "backslash=\\ quote=\" EOF",
   1.262 +                   });
   1.263 +  do_check_eq(result.artifacts.mac, "BEMW76lwaJlPX4E/dajF970T6+GzWvaeyLzUt8eOTOc=");
   1.264 +  do_check_eq(result.field, 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", ext="backslash=\\\\ quote=\\\" EOF", mac="BEMW76lwaJlPX4E/dajF970T6+GzWvaeyLzUt8eOTOc="');
   1.265 +
   1.266 +  result = compute(makeURI("http://example.net:1234/path"), method,
   1.267 +                   { credentials: credentials_sha256,
   1.268 +                     ts: 1353809207,
   1.269 +                     nonce: "Ygvqdz",
   1.270 +                   });
   1.271 +  do_check_eq(result.artifacts.mac, "6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE=");
   1.272 +  do_check_eq(result.field, 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", mac="6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE="');
   1.273 +
   1.274 +  /* HAWK (the node.js library) uses a URL parser which stores the "port"
   1.275 +   * field as a string, but makeURI() gives us an integer. So we'll diverge
   1.276 +   * on ports with a leading zero. This test vector would fail on the node.js
   1.277 +   * library (HAWK-1.1.1), where they get a MAC of
   1.278 +   * "T+GcAsDO8GRHIvZLeepSvXLwDlFJugcZroAy9+uAtcw=". I think HAWK should be
   1.279 +   * updated to do what we do here, so port="01234" should get the same hash
   1.280 +   * as port="1234".
   1.281 +   */
   1.282 +  result = compute(makeURI("http://example.net:01234/path"), method,
   1.283 +                   { credentials: credentials_sha256,
   1.284 +                     ts: 1353809207,
   1.285 +                     nonce: "Ygvqdz",
   1.286 +                   });
   1.287 +  do_check_eq(result.artifacts.mac, "6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE=");
   1.288 +  do_check_eq(result.field, 'Hawk id="123456", ts="1353809207", nonce="Ygvqdz", mac="6D3JSFDtozuq8QvJTNUc1JzeCfy6h5oRvlhmSTPv6LE="');
   1.289 +
   1.290 +  run_next_test();
   1.291 +});
   1.292 +
   1.293 +
   1.294 +add_test(function test_strip_header_attributes() {
   1.295 +  let strip = CryptoUtils.stripHeaderAttributes;
   1.296 +
   1.297 +  do_check_eq(strip(undefined), "");
   1.298 +  do_check_eq(strip("text/plain"), "text/plain");
   1.299 +  do_check_eq(strip("TEXT/PLAIN"), "text/plain");
   1.300 +  do_check_eq(strip("  text/plain  "), "text/plain");
   1.301 +  do_check_eq(strip("text/plain ; charset=utf-8  "), "text/plain");
   1.302 +
   1.303 +  run_next_test();
   1.304 +});

mercurial