Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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 | }); |