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