|
1 /* Any copyright is dedicated to the Public Domain. |
|
2 * http://creativecommons.org/publicdomain/zero/1.0/ */ |
|
3 |
|
4 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
5 Cu.import("resource://services-common/utils.js"); |
|
6 Cu.import("resource://services-crypto/utils.js"); |
|
7 |
|
8 function run_test() { |
|
9 initTestLogging(); |
|
10 |
|
11 run_next_test(); |
|
12 } |
|
13 |
|
14 add_test(function test_hawk() { |
|
15 let compute = CryptoUtils.computeHAWK; |
|
16 |
|
17 // vectors copied from the HAWK (node.js) tests |
|
18 let credentials_sha1 = { |
|
19 id: "123456", |
|
20 key: "2983d45yun89q", |
|
21 algorithm: "sha1", |
|
22 }; |
|
23 |
|
24 let method = "POST"; |
|
25 let ts = 1353809207; |
|
26 let nonce = "Ygvqdz"; |
|
27 let result; |
|
28 |
|
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); |
|
37 |
|
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!"); |
|
53 |
|
54 let credentials_sha256 = { |
|
55 id: "123456", |
|
56 key: "2983d45yun89q", |
|
57 algorithm: "sha256", |
|
58 }; |
|
59 |
|
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 }; |
|
68 |
|
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!"); |
|
84 |
|
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="); |
|
104 |
|
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 */ |
|
110 |
|
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); |
|
124 |
|
125 let result2 = compute(uri_https, method, { credentials: credentials_sha256 }); |
|
126 do_check_neq(result.artifacts.nonce, result2.artifacts.nonce); |
|
127 |
|
128 /* Using an upper-case URI hostname shouldn't affect the hash. */ |
|
129 |
|
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 ); |
|
138 |
|
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 ); |
|
147 |
|
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 */ |
|
154 |
|
155 result = compute(uri_https, method, { credentials: credentials_sha256, |
|
156 now: 1378848968650, |
|
157 }); |
|
158 do_check_eq(result.artifacts.ts, 1378848968); |
|
159 |
|
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); |
|
165 |
|
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="); |
|
171 |
|
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="); |
|
175 |
|
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="); |
|
179 |
|
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). */ |
|
182 |
|
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="); |
|
190 |
|
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="); |
|
200 |
|
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="); |
|
209 |
|
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="); |
|
219 |
|
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="); |
|
230 |
|
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="); |
|
240 |
|
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 */ |
|
245 |
|
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"); |
|
253 |
|
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="'); |
|
262 |
|
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="'); |
|
270 |
|
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="'); |
|
286 |
|
287 run_next_test(); |
|
288 }); |
|
289 |
|
290 |
|
291 add_test(function test_strip_header_attributes() { |
|
292 let strip = CryptoUtils.stripHeaderAttributes; |
|
293 |
|
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"); |
|
299 |
|
300 run_next_test(); |
|
301 }); |