tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_utils_hawk.js (10934B)


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