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