test_utils_hawk.js 13 KB

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