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