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');
15 // Generate an Authorization header for a given request
18 uri: 'http://example.com/resource?a=b' or object from Url.parse()
19 method: HTTP verb (e.g. 'GET', 'POST')
26 key: 'aoijedoaijsdlaksjdl',
27 algorithm: 'sha256' // 'sha1', 'sha256'
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
44 exports.header = function (uri, method, options) {
53 if (!uri || (typeof uri !== 'string' && typeof uri !== 'object') ||
54 !method || typeof method !== 'string' ||
55 !options || typeof options !== 'object') {
57 result.err = 'Invalid argument type';
63 var timestamp = options.timestamp || Utils.nowSecs(options.localtimeOffsetMsec);
65 // Validate credentials
67 var credentials = options.credentials;
71 !credentials.algorithm) {
73 result.err = 'Invalid credential object';
77 if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) {
78 result.err = 'Unknown algorithm';
84 if (typeof uri === 'string') {
88 // Calculate signature
92 nonce: options.nonce || Cryptiles.randomString(6),
94 resource: uri.pathname + (uri.search || ''), // Maintain trailing '?'
96 port: uri.port || (uri.protocol === 'http:' ? 80 : 443),
103 result.artifacts = artifacts;
105 // Calculate payload hash
107 if (!artifacts.hash &&
108 (options.payload || options.payload === '')) {
110 artifacts.hash = Crypto.calculatePayloadHash(options.payload, credentials.algorithm, options.contentType);
113 var mac = Crypto.calculateMac('header', credentials, artifacts);
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 + '"';
126 header += ', app="' + artifacts.app +
127 (artifacts.dlg ? '", dlg="' + artifacts.dlg : '') + '"';
130 result.field = header;
136 // Validate server response
139 res: node's response object
140 artifacts: object received from header().artifacts
142 payload: optional payload received
143 required: specifies if a Server-Authorization header is required. Defaults to 'false'
147 exports.authenticate = function (res, credentials, artifacts, options) {
149 artifacts = Hoek.clone(artifacts);
150 options = options || {};
152 if (res.headers['www-authenticate']) {
154 // Parse HTTP WWW-Authenticate header
156 var wwwAttributes = Utils.parseAuthorizationHeader(res.headers['www-authenticate'], ['ts', 'tsm', 'error']);
157 if (wwwAttributes instanceof Error) {
161 // Validate server timestamp (not used to update clock since it is done via the SNPT client)
163 if (wwwAttributes.ts) {
164 var tsm = Crypto.calculateTsMac(wwwAttributes.ts, credentials);
165 if (tsm !== wwwAttributes.tsm) {
171 // Parse HTTP Server-Authorization header
173 if (!res.headers['server-authorization'] &&
179 var attributes = Utils.parseAuthorizationHeader(res.headers['server-authorization'], ['mac', 'ext', 'hash']);
180 if (attributes instanceof Error) {
184 artifacts.ext = attributes.ext;
185 artifacts.hash = attributes.hash;
187 var mac = Crypto.calculateMac('response', credentials, artifacts);
188 if (mac !== attributes.mac) {
192 if (!options.payload &&
193 options.payload !== '') {
198 if (!attributes.hash) {
202 var calculatedHash = Crypto.calculatePayloadHash(options.payload, credentials.algorithm, res.headers['content-type']);
203 return (calculatedHash === attributes.hash);
207 // Generate a bewit value for a given URI
210 uri: 'http://example.com/resource?a=b' or object from Url.parse()
217 key: 'aoijedoaijsdlaksjdl',
218 algorithm: 'sha256' // 'sha1', 'sha256'
220 ttlSec: 60 * 60, // TTL in seconds
224 ext: 'application-specific', // Application specific data sent via the ext attribute
225 localtimeOffsetMsec: 400 // Time offset to sync with server time
229 exports.getBewit = function (uri, options) {
234 (typeof uri !== 'string' && typeof uri !== 'object') ||
236 typeof options !== 'object' ||
242 options.ext = (options.ext === null || options.ext === undefined ? '' : options.ext); // Zero is valid value
246 var now = Utils.now(options.localtimeOffsetMsec);
248 // Validate credentials
250 var credentials = options.credentials;
254 !credentials.algorithm) {
259 if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) {
265 if (typeof uri === 'string') {
266 uri = Url.parse(uri);
269 // Calculate signature
271 var exp = Math.floor(now / 1000) + options.ttlSec;
272 var mac = Crypto.calculateMac('bewit', credentials, {
276 resource: uri.pathname + (uri.search || ''), // Maintain trailing '?'
278 port: uri.port || (uri.protocol === 'http:' ? 80 : 443),
282 // Construct bewit: id\exp\mac\ext
284 var bewit = credentials.id + '\\' + exp + '\\' + mac + '\\' + options.ext;
285 return Hoek.base64urlEncode(bewit);
289 // Generate an authorization string for a message
294 message: '{"some":"payload"}', // UTF-8 encoded string for body hash generation
301 key: 'aoijedoaijsdlaksjdl',
302 algorithm: 'sha256' // 'sha1', 'sha256'
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)
313 exports.message = function (host, port, message, options) {
317 if (!host || typeof host !== 'string' ||
318 !port || typeof port !== 'number' ||
319 message === null || message === undefined || typeof message !== 'string' ||
320 !options || typeof options !== 'object') {
327 var timestamp = options.timestamp || Utils.nowSecs(options.localtimeOffsetMsec);
329 // Validate credentials
331 var credentials = options.credentials;
335 !credentials.algorithm) {
337 // Invalid credential object
341 if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) {
345 // Calculate signature
349 nonce: options.nonce || Cryptiles.randomString(6),
352 hash: Crypto.calculatePayloadHash(message, credentials.algorithm)
355 // Construct authorization
360 nonce: artifacts.nonce,
361 hash: artifacts.hash,
362 mac: Crypto.calculateMac('message', credentials, artifacts)