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

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

mercurial