Initial commit
[yaffs-website] / node_modules / hawk / lib / client.js
1 // Load modules\r
2 \r
3 var Url = require('url');\r
4 var Hoek = require('hoek');\r
5 var Cryptiles = require('cryptiles');\r
6 var Crypto = require('./crypto');\r
7 var Utils = require('./utils');\r
8 \r
9 \r
10 // Declare internals\r
11 \r
12 var internals = {};\r
13 \r
14 \r
15 // Generate an Authorization header for a given request\r
16 \r
17 /*\r
18     uri: 'http://example.com/resource?a=b' or object from Url.parse()\r
19     method: HTTP verb (e.g. 'GET', 'POST')\r
20     options: {\r
21 \r
22         // Required\r
23 \r
24         credentials: {\r
25             id: 'dh37fgj492je',\r
26             key: 'aoijedoaijsdlaksjdl',\r
27             algorithm: 'sha256'                                 // 'sha1', 'sha256'\r
28         },\r
29 \r
30         // Optional\r
31 \r
32         ext: 'application-specific',                        // Application specific data sent via the ext attribute\r
33         timestamp: Date.now(),                              // A pre-calculated timestamp\r
34         nonce: '2334f34f',                                  // A pre-generated nonce\r
35         localtimeOffsetMsec: 400,                           // Time offset to sync with server time (ignored if timestamp provided)\r
36         payload: '{"some":"payload"}',                      // UTF-8 encoded string for body hash generation (ignored if hash provided)\r
37         contentType: 'application/json',                    // Payload content-type (ignored if hash provided)\r
38         hash: 'U4MKKSmiVxk37JCCrAVIjV=',                    // Pre-calculated payload hash\r
39         app: '24s23423f34dx',                               // Oz application id\r
40         dlg: '234sz34tww3sd'                                // Oz delegated-by application id\r
41     }\r
42 */\r
43 \r
44 exports.header = function (uri, method, options) {\r
45 \r
46     var result = {\r
47         field: '',\r
48         artifacts: {}\r
49     };\r
50 \r
51     // Validate inputs\r
52 \r
53     if (!uri || (typeof uri !== 'string' && typeof uri !== 'object') ||\r
54         !method || typeof method !== 'string' ||\r
55         !options || typeof options !== 'object') {\r
56 \r
57         result.err = 'Invalid argument type';\r
58         return result;\r
59     }\r
60 \r
61     // Application time\r
62 \r
63     var timestamp = options.timestamp || Utils.nowSecs(options.localtimeOffsetMsec);\r
64 \r
65     // Validate credentials\r
66 \r
67     var credentials = options.credentials;\r
68     if (!credentials ||\r
69         !credentials.id ||\r
70         !credentials.key ||\r
71         !credentials.algorithm) {\r
72 \r
73         result.err = 'Invalid credential object';\r
74         return result;\r
75     }\r
76 \r
77     if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) {\r
78         result.err = 'Unknown algorithm';\r
79         return result;\r
80     }\r
81 \r
82     // Parse URI\r
83 \r
84     if (typeof uri === 'string') {\r
85         uri = Url.parse(uri);\r
86     }\r
87 \r
88     // Calculate signature\r
89 \r
90     var artifacts = {\r
91         ts: timestamp,\r
92         nonce: options.nonce || Cryptiles.randomString(6),\r
93         method: method,\r
94         resource: uri.pathname + (uri.search || ''),                            // Maintain trailing '?'\r
95         host: uri.hostname,\r
96         port: uri.port || (uri.protocol === 'http:' ? 80 : 443),\r
97         hash: options.hash,\r
98         ext: options.ext,\r
99         app: options.app,\r
100         dlg: options.dlg\r
101     };\r
102 \r
103     result.artifacts = artifacts;\r
104 \r
105     // Calculate payload hash\r
106 \r
107     if (!artifacts.hash &&\r
108         (options.payload || options.payload === '')) {\r
109 \r
110         artifacts.hash = Crypto.calculatePayloadHash(options.payload, credentials.algorithm, options.contentType);\r
111     }\r
112 \r
113     var mac = Crypto.calculateMac('header', credentials, artifacts);\r
114 \r
115     // Construct header\r
116 \r
117     var hasExt = artifacts.ext !== null && artifacts.ext !== undefined && artifacts.ext !== '';       // Other falsey values allowed\r
118     var header = 'Hawk id="' + credentials.id +\r
119                  '", ts="' + artifacts.ts +\r
120                  '", nonce="' + artifacts.nonce +\r
121                  (artifacts.hash ? '", hash="' + artifacts.hash : '') +\r
122                  (hasExt ? '", ext="' + Hoek.escapeHeaderAttribute(artifacts.ext) : '') +\r
123                  '", mac="' + mac + '"';\r
124 \r
125     if (artifacts.app) {\r
126         header += ', app="' + artifacts.app +\r
127                   (artifacts.dlg ? '", dlg="' + artifacts.dlg : '') + '"';\r
128     }\r
129 \r
130     result.field = header;\r
131 \r
132     return result;\r
133 };\r
134 \r
135 \r
136 // Validate server response\r
137 \r
138 /*\r
139     res:        node's response object\r
140     artifacts:  object received from header().artifacts\r
141     options: {\r
142         payload:    optional payload received\r
143         required:   specifies if a Server-Authorization header is required. Defaults to 'false'\r
144     }\r
145 */\r
146 \r
147 exports.authenticate = function (res, credentials, artifacts, options) {\r
148 \r
149     artifacts = Hoek.clone(artifacts);\r
150     options = options || {};\r
151 \r
152     if (res.headers['www-authenticate']) {\r
153 \r
154         // Parse HTTP WWW-Authenticate header\r
155 \r
156         var wwwAttributes = Utils.parseAuthorizationHeader(res.headers['www-authenticate'], ['ts', 'tsm', 'error']);\r
157         if (wwwAttributes instanceof Error) {\r
158             return false;\r
159         }\r
160 \r
161         // Validate server timestamp (not used to update clock since it is done via the SNPT client)\r
162 \r
163         if (wwwAttributes.ts) {\r
164             var tsm = Crypto.calculateTsMac(wwwAttributes.ts, credentials);\r
165             if (tsm !== wwwAttributes.tsm) {\r
166                 return false;\r
167             }\r
168         }\r
169     }\r
170 \r
171     // Parse HTTP Server-Authorization header\r
172 \r
173     if (!res.headers['server-authorization'] &&\r
174         !options.required) {\r
175 \r
176         return true;\r
177     }\r
178 \r
179     var attributes = Utils.parseAuthorizationHeader(res.headers['server-authorization'], ['mac', 'ext', 'hash']);\r
180     if (attributes instanceof Error) {\r
181         return false;\r
182     }\r
183 \r
184     artifacts.ext = attributes.ext;\r
185     artifacts.hash = attributes.hash;\r
186 \r
187     var mac = Crypto.calculateMac('response', credentials, artifacts);\r
188     if (mac !== attributes.mac) {\r
189         return false;\r
190     }\r
191 \r
192     if (!options.payload &&\r
193         options.payload !== '') {\r
194 \r
195         return true;\r
196     }\r
197 \r
198     if (!attributes.hash) {\r
199         return false;\r
200     }\r
201 \r
202     var calculatedHash = Crypto.calculatePayloadHash(options.payload, credentials.algorithm, res.headers['content-type']);\r
203     return (calculatedHash === attributes.hash);\r
204 };\r
205 \r
206 \r
207 // Generate a bewit value for a given URI\r
208 \r
209 /*\r
210     uri: 'http://example.com/resource?a=b' or object from Url.parse()\r
211     options: {\r
212 \r
213         // Required\r
214 \r
215         credentials: {\r
216             id: 'dh37fgj492je',\r
217             key: 'aoijedoaijsdlaksjdl',\r
218             algorithm: 'sha256'                             // 'sha1', 'sha256'\r
219         },\r
220         ttlSec: 60 * 60,                                    // TTL in seconds\r
221 \r
222         // Optional\r
223 \r
224         ext: 'application-specific',                        // Application specific data sent via the ext attribute\r
225         localtimeOffsetMsec: 400                            // Time offset to sync with server time\r
226     };\r
227 */\r
228 \r
229 exports.getBewit = function (uri, options) {\r
230 \r
231     // Validate inputs\r
232 \r
233     if (!uri ||\r
234         (typeof uri !== 'string' && typeof uri !== 'object') ||\r
235         !options ||\r
236         typeof options !== 'object' ||\r
237         !options.ttlSec) {\r
238 \r
239         return '';\r
240     }\r
241 \r
242     options.ext = (options.ext === null || options.ext === undefined ? '' : options.ext);       // Zero is valid value\r
243 \r
244     // Application time\r
245 \r
246     var now = Utils.now(options.localtimeOffsetMsec);\r
247 \r
248     // Validate credentials\r
249 \r
250     var credentials = options.credentials;\r
251     if (!credentials ||\r
252         !credentials.id ||\r
253         !credentials.key ||\r
254         !credentials.algorithm) {\r
255 \r
256         return '';\r
257     }\r
258 \r
259     if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) {\r
260         return '';\r
261     }\r
262 \r
263     // Parse URI\r
264 \r
265     if (typeof uri === 'string') {\r
266         uri = Url.parse(uri);\r
267     }\r
268 \r
269     // Calculate signature\r
270 \r
271     var exp = Math.floor(now / 1000) + options.ttlSec;\r
272     var mac = Crypto.calculateMac('bewit', credentials, {\r
273         ts: exp,\r
274         nonce: '',\r
275         method: 'GET',\r
276         resource: uri.pathname + (uri.search || ''),                            // Maintain trailing '?'\r
277         host: uri.hostname,\r
278         port: uri.port || (uri.protocol === 'http:' ? 80 : 443),\r
279         ext: options.ext\r
280     });\r
281 \r
282     // Construct bewit: id\exp\mac\ext\r
283 \r
284     var bewit = credentials.id + '\\' + exp + '\\' + mac + '\\' + options.ext;\r
285     return Hoek.base64urlEncode(bewit);\r
286 };\r
287 \r
288 \r
289 // Generate an authorization string for a message\r
290 \r
291 /*\r
292     host: 'example.com',\r
293     port: 8000,\r
294     message: '{"some":"payload"}',                          // UTF-8 encoded string for body hash generation\r
295     options: {\r
296 \r
297         // Required\r
298 \r
299         credentials: {\r
300             id: 'dh37fgj492je',\r
301             key: 'aoijedoaijsdlaksjdl',\r
302             algorithm: 'sha256'                             // 'sha1', 'sha256'\r
303         },\r
304 \r
305         // Optional\r
306 \r
307         timestamp: Date.now(),                              // A pre-calculated timestamp\r
308         nonce: '2334f34f',                                  // A pre-generated nonce\r
309         localtimeOffsetMsec: 400,                           // Time offset to sync with server time (ignored if timestamp provided)\r
310     }\r
311 */\r
312 \r
313 exports.message = function (host, port, message, options) {\r
314 \r
315     // Validate inputs\r
316 \r
317     if (!host || typeof host !== 'string' ||\r
318         !port || typeof port !== 'number' ||\r
319         message === null || message === undefined || typeof message !== 'string' ||\r
320         !options || typeof options !== 'object') {\r
321 \r
322         return null;\r
323     }\r
324 \r
325     // Application time\r
326 \r
327     var timestamp = options.timestamp || Utils.nowSecs(options.localtimeOffsetMsec);\r
328 \r
329     // Validate credentials\r
330 \r
331     var credentials = options.credentials;\r
332     if (!credentials ||\r
333         !credentials.id ||\r
334         !credentials.key ||\r
335         !credentials.algorithm) {\r
336 \r
337         // Invalid credential object\r
338         return null;\r
339     }\r
340 \r
341     if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) {\r
342         return null;\r
343     }\r
344 \r
345     // Calculate signature\r
346 \r
347     var artifacts = {\r
348         ts: timestamp,\r
349         nonce: options.nonce || Cryptiles.randomString(6),\r
350         host: host,\r
351         port: port,\r
352         hash: Crypto.calculatePayloadHash(message, credentials.algorithm)\r
353     };\r
354 \r
355     // Construct authorization\r
356 \r
357     var result = {\r
358         id: credentials.id,\r
359         ts: artifacts.ts,\r
360         nonce: artifacts.nonce,\r
361         hash: artifacts.hash,\r
362         mac: Crypto.calculateMac('message', credentials, artifacts)\r
363     };\r
364 \r
365     return result;\r
366 };\r
367 \r
368 \r
369 \r