| 'use strict' |
| |
| // Load modules |
| |
| ; |
| |
| var _typeof = function (obj) { |
| |
| return obj && typeof Symbol !== 'undefined' && obj.constructor === Symbol ? 'symbol' : typeof obj; |
| }; |
| |
| var Url = require('url'); |
| var Hoek = require('hoek'); |
| var Cryptiles = require('cryptiles'); |
| var Crypto = require('./crypto'); |
| var Utils = require('./utils'); |
| |
| // Declare internals |
| |
| var internals = {}; |
| |
| // Generate an Authorization header for a given request |
| |
| /* |
| uri: 'http://example.com/resource?a=b' or object from Url.parse() |
| method: HTTP verb (e.g. 'GET', 'POST') |
| options: { |
| |
| // Required |
| |
| credentials: { |
| id: 'dh37fgj492je', |
| key: 'aoijedoaijsdlaksjdl', |
| algorithm: 'sha256' // 'sha1', 'sha256' |
| }, |
| |
| // Optional |
| |
| ext: 'application-specific', // Application specific data sent via the ext attribute |
| timestamp: Date.now(), // A pre-calculated timestamp |
| nonce: '2334f34f', // A pre-generated nonce |
| localtimeOffsetMsec: 400, // Time offset to sync with server time (ignored if timestamp provided) |
| payload: '{"some":"payload"}', // UTF-8 encoded string for body hash generation (ignored if hash provided) |
| contentType: 'application/json', // Payload content-type (ignored if hash provided) |
| hash: 'U4MKKSmiVxk37JCCrAVIjV=', // Pre-calculated payload hash |
| app: '24s23423f34dx', // Oz application id |
| dlg: '234sz34tww3sd' // Oz delegated-by application id |
| } |
| */ |
| |
| exports.header = function (uri, method, options) { |
| |
| var result = { |
| field: '', |
| artifacts: {} |
| }; |
| |
| // Validate inputs |
| |
| if (!uri || typeof uri !== 'string' && (typeof uri === 'undefined' ? 'undefined' : _typeof(uri)) !== 'object' || !method || typeof method !== 'string' || !options || (typeof options === 'undefined' ? 'undefined' : _typeof(options)) !== 'object') { |
| |
| result.err = 'Invalid argument type'; |
| return result; |
| } |
| |
| // Application time |
| |
| var timestamp = options.timestamp || Utils.nowSecs(options.localtimeOffsetMsec); |
| |
| // Validate credentials |
| |
| var credentials = options.credentials; |
| if (!credentials || !credentials.id || !credentials.key || !credentials.algorithm) { |
| |
| result.err = 'Invalid credential object'; |
| return result; |
| } |
| |
| if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) { |
| result.err = 'Unknown algorithm'; |
| return result; |
| } |
| |
| // Parse URI |
| |
| if (typeof uri === 'string') { |
| uri = Url.parse(uri); |
| } |
| |
| // Calculate signature |
| |
| var artifacts = { |
| ts: timestamp, |
| nonce: options.nonce || Cryptiles.randomString(6), |
| method: method, |
| resource: uri.pathname + (uri.search || ''), // Maintain trailing '?' |
| host: uri.hostname, |
| port: uri.port || (uri.protocol === 'http:' ? 80 : 443), |
| hash: options.hash, |
| ext: options.ext, |
| app: options.app, |
| dlg: options.dlg |
| }; |
| |
| result.artifacts = artifacts; |
| |
| // Calculate payload hash |
| |
| if (!artifacts.hash && (options.payload || options.payload === '')) { |
| |
| artifacts.hash = Crypto.calculatePayloadHash(options.payload, credentials.algorithm, options.contentType); |
| } |
| |
| var mac = Crypto.calculateMac('header', credentials, artifacts); |
| |
| // Construct header |
| |
| var hasExt = artifacts.ext !== null && artifacts.ext !== undefined && artifacts.ext !== ''; // Other falsey values allowed |
| var header = 'Hawk id="' + credentials.id + '", ts="' + artifacts.ts + '", nonce="' + artifacts.nonce + (artifacts.hash ? '", hash="' + artifacts.hash : '') + (hasExt ? '", ext="' + Hoek.escapeHeaderAttribute(artifacts.ext) : '') + '", mac="' + mac + '"'; |
| |
| if (artifacts.app) { |
| header = header + ', app="' + artifacts.app + (artifacts.dlg ? '", dlg="' + artifacts.dlg : '') + '"'; |
| } |
| |
| result.field = header; |
| |
| return result; |
| }; |
| |
| // Validate server response |
| |
| /* |
| res: node's response object |
| artifacts: object received from header().artifacts |
| options: { |
| payload: optional payload received |
| required: specifies if a Server-Authorization header is required. Defaults to 'false' |
| } |
| */ |
| |
| exports.authenticate = function (res, credentials, artifacts, options) { |
| |
| artifacts = Hoek.clone(artifacts); |
| options = options || {}; |
| |
| if (res.headers['www-authenticate']) { |
| |
| // Parse HTTP WWW-Authenticate header |
| |
| var wwwAttributes = Utils.parseAuthorizationHeader(res.headers['www-authenticate'], ['ts', 'tsm', 'error']); |
| if (wwwAttributes instanceof Error) { |
| return false; |
| } |
| |
| // Validate server timestamp (not used to update clock since it is done via the SNPT client) |
| |
| if (wwwAttributes.ts) { |
| var tsm = Crypto.calculateTsMac(wwwAttributes.ts, credentials); |
| if (tsm !== wwwAttributes.tsm) { |
| return false; |
| } |
| } |
| } |
| |
| // Parse HTTP Server-Authorization header |
| |
| if (!res.headers['server-authorization'] && !options.required) { |
| |
| return true; |
| } |
| |
| var attributes = Utils.parseAuthorizationHeader(res.headers['server-authorization'], ['mac', 'ext', 'hash']); |
| if (attributes instanceof Error) { |
| return false; |
| } |
| |
| artifacts.ext = attributes.ext; |
| artifacts.hash = attributes.hash; |
| |
| var mac = Crypto.calculateMac('response', credentials, artifacts); |
| if (mac !== attributes.mac) { |
| return false; |
| } |
| |
| if (!options.payload && options.payload !== '') { |
| |
| return true; |
| } |
| |
| if (!attributes.hash) { |
| return false; |
| } |
| |
| var calculatedHash = Crypto.calculatePayloadHash(options.payload, credentials.algorithm, res.headers['content-type']); |
| return calculatedHash === attributes.hash; |
| }; |
| |
| // Generate a bewit value for a given URI |
| |
| /* |
| uri: 'http://example.com/resource?a=b' or object from Url.parse() |
| options: { |
| |
| // Required |
| |
| credentials: { |
| id: 'dh37fgj492je', |
| key: 'aoijedoaijsdlaksjdl', |
| algorithm: 'sha256' // 'sha1', 'sha256' |
| }, |
| ttlSec: 60 * 60, // TTL in seconds |
| |
| // Optional |
| |
| ext: 'application-specific', // Application specific data sent via the ext attribute |
| localtimeOffsetMsec: 400 // Time offset to sync with server time |
| }; |
| */ |
| |
| exports.getBewit = function (uri, options) { |
| |
| // Validate inputs |
| |
| if (!uri || typeof uri !== 'string' && (typeof uri === 'undefined' ? 'undefined' : _typeof(uri)) !== 'object' || !options || (typeof options === 'undefined' ? 'undefined' : _typeof(options)) !== 'object' || !options.ttlSec) { |
| |
| return ''; |
| } |
| |
| options.ext = options.ext === null || options.ext === undefined ? '' : options.ext; // Zero is valid value |
| |
| // Application time |
| |
| var now = Utils.now(options.localtimeOffsetMsec); |
| |
| // Validate credentials |
| |
| var credentials = options.credentials; |
| if (!credentials || !credentials.id || !credentials.key || !credentials.algorithm) { |
| |
| return ''; |
| } |
| |
| if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) { |
| return ''; |
| } |
| |
| // Parse URI |
| |
| if (typeof uri === 'string') { |
| uri = Url.parse(uri); |
| } |
| |
| // Calculate signature |
| |
| var exp = Math.floor(now / 1000) + options.ttlSec; |
| var mac = Crypto.calculateMac('bewit', credentials, { |
| ts: exp, |
| nonce: '', |
| method: 'GET', |
| resource: uri.pathname + (uri.search || ''), // Maintain trailing '?' |
| host: uri.hostname, |
| port: uri.port || (uri.protocol === 'http:' ? 80 : 443), |
| ext: options.ext |
| }); |
| |
| // Construct bewit: id\exp\mac\ext |
| |
| var bewit = credentials.id + '\\' + exp + '\\' + mac + '\\' + options.ext; |
| return Hoek.base64urlEncode(bewit); |
| }; |
| |
| // Generate an authorization string for a message |
| |
| /* |
| host: 'example.com', |
| port: 8000, |
| message: '{"some":"payload"}', // UTF-8 encoded string for body hash generation |
| options: { |
| |
| // Required |
| |
| credentials: { |
| id: 'dh37fgj492je', |
| key: 'aoijedoaijsdlaksjdl', |
| algorithm: 'sha256' // 'sha1', 'sha256' |
| }, |
| |
| // Optional |
| |
| timestamp: Date.now(), // A pre-calculated timestamp |
| nonce: '2334f34f', // A pre-generated nonce |
| localtimeOffsetMsec: 400, // Time offset to sync with server time (ignored if timestamp provided) |
| } |
| */ |
| |
| exports.message = function (host, port, message, options) { |
| |
| // Validate inputs |
| |
| if (!host || typeof host !== 'string' || !port || typeof port !== 'number' || message === null || message === undefined || typeof message !== 'string' || !options || (typeof options === 'undefined' ? 'undefined' : _typeof(options)) !== 'object') { |
| |
| return null; |
| } |
| |
| // Application time |
| |
| var timestamp = options.timestamp || Utils.nowSecs(options.localtimeOffsetMsec); |
| |
| // Validate credentials |
| |
| var credentials = options.credentials; |
| if (!credentials || !credentials.id || !credentials.key || !credentials.algorithm) { |
| |
| // Invalid credential object |
| return null; |
| } |
| |
| if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) { |
| return null; |
| } |
| |
| // Calculate signature |
| |
| var artifacts = { |
| ts: timestamp, |
| nonce: options.nonce || Cryptiles.randomString(6), |
| host: host, |
| port: port, |
| hash: Crypto.calculatePayloadHash(message, credentials.algorithm) |
| }; |
| |
| // Construct authorization |
| |
| var result = { |
| id: credentials.id, |
| ts: artifacts.ts, |
| nonce: artifacts.nonce, |
| hash: artifacts.hash, |
| mac: Crypto.calculateMac('message', credentials, artifacts) |
| }; |
| |
| return result; |
| }; |