"Fossies" - the Fresh Open Source Software Archive

Member "Atom/resources/app/apm/node_modules/request/node_modules/hawk/lib/server.js" (7 Feb 2017, 18546 Bytes) of archive /windows/misc/atom-windows.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Javascript source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 // Load modules
    2 
    3 var Boom = require('boom');
    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 // Hawk authentication
   16 
   17 /*
   18    req:                 node's HTTP request object or an object as follows:
   19 
   20                         var request = {
   21                             method: 'GET',
   22                             url: '/resource/4?a=1&b=2',
   23                             host: 'example.com',
   24                             port: 8080,
   25                             authorization: 'Hawk id="dh37fgj492je", ts="1353832234", nonce="j4h3g2", ext="some-app-ext-data", mac="6R4rV5iE+NPoym+WwjeHzjAGXUtLNIxmo1vpMofpLAE="'
   26                         };
   27 
   28    credentialsFunc:     required function to lookup the set of Hawk credentials based on the provided credentials id.
   29                         The credentials include the MAC key, MAC algorithm, and other attributes (such as username)
   30                         needed by the application. This function is the equivalent of verifying the username and
   31                         password in Basic authentication.
   32 
   33                         var credentialsFunc = function (id, callback) {
   34 
   35                             // Lookup credentials in database
   36                             db.lookup(id, function (err, item) {
   37 
   38                                 if (err || !item) {
   39                                     return callback(err);
   40                                 }
   41 
   42                                 var credentials = {
   43                                     // Required
   44                                     key: item.key,
   45                                     algorithm: item.algorithm,
   46                                     // Application specific
   47                                     user: item.user
   48                                 };
   49 
   50                                 return callback(null, credentials);
   51                             });
   52                         };
   53 
   54    options: {
   55 
   56         hostHeaderName:        optional header field name, used to override the default 'Host' header when used
   57                                behind a cache of a proxy. Apache2 changes the value of the 'Host' header while preserving
   58                                the original (which is what the module must verify) in the 'x-forwarded-host' header field.
   59                                Only used when passed a node Http.ServerRequest object.
   60 
   61         nonceFunc:             optional nonce validation function. The function signature is function(key, nonce, ts, callback)
   62                                where 'callback' must be called using the signature function(err).
   63 
   64         timestampSkewSec:      optional number of seconds of permitted clock skew for incoming timestamps. Defaults to 60 seconds.
   65                                Provides a +/- skew which means actual allowed window is double the number of seconds.
   66 
   67         localtimeOffsetMsec:   optional local clock time offset express in a number of milliseconds (positive or negative).
   68                                Defaults to 0.
   69 
   70         payload:               optional payload for validation. The client calculates the hash value and includes it via the 'hash'
   71                                header attribute. The server always ensures the value provided has been included in the request
   72                                MAC. When this option is provided, it validates the hash value itself. Validation is done by calculating
   73                                a hash value over the entire payload (assuming it has already be normalized to the same format and
   74                                encoding used by the client to calculate the hash on request). If the payload is not available at the time
   75                                of authentication, the authenticatePayload() method can be used by passing it the credentials and
   76                                attributes.hash returned in the authenticate callback.
   77 
   78         host:                  optional host name override. Only used when passed a node request object.
   79         port:                  optional port override. Only used when passed a node request object.
   80     }
   81 
   82     callback: function (err, credentials, artifacts) { }
   83  */
   84 
   85 exports.authenticate = function (req, credentialsFunc, options, callback) {
   86 
   87     callback = Hoek.nextTick(callback);
   88 
   89     // Default options
   90 
   91     options.nonceFunc = options.nonceFunc || internals.nonceFunc;
   92     options.timestampSkewSec = options.timestampSkewSec || 60;                                                  // 60 seconds
   93 
   94     // Application time
   95 
   96     var now = Utils.now(options.localtimeOffsetMsec);                           // Measure now before any other processing
   97 
   98     // Convert node Http request object to a request configuration object
   99 
  100     var request = Utils.parseRequest(req, options);
  101     if (request instanceof Error) {
  102         return callback(Boom.badRequest(request.message));
  103     }
  104 
  105     // Parse HTTP Authorization header
  106 
  107     var attributes = Utils.parseAuthorizationHeader(request.authorization);
  108     if (attributes instanceof Error) {
  109         return callback(attributes);
  110     }
  111 
  112     // Construct artifacts container
  113 
  114     var artifacts = {
  115         method: request.method,
  116         host: request.host,
  117         port: request.port,
  118         resource: request.url,
  119         ts: attributes.ts,
  120         nonce: attributes.nonce,
  121         hash: attributes.hash,
  122         ext: attributes.ext,
  123         app: attributes.app,
  124         dlg: attributes.dlg,
  125         mac: attributes.mac,
  126         id: attributes.id
  127     };
  128 
  129     // Verify required header attributes
  130 
  131     if (!attributes.id ||
  132         !attributes.ts ||
  133         !attributes.nonce ||
  134         !attributes.mac) {
  135 
  136         return callback(Boom.badRequest('Missing attributes'), null, artifacts);
  137     }
  138 
  139     // Fetch Hawk credentials
  140 
  141     credentialsFunc(attributes.id, function (err, credentials) {
  142 
  143         if (err) {
  144             return callback(err, credentials || null, artifacts);
  145         }
  146 
  147         if (!credentials) {
  148             return callback(Boom.unauthorized('Unknown credentials', 'Hawk'), null, artifacts);
  149         }
  150 
  151         if (!credentials.key ||
  152             !credentials.algorithm) {
  153 
  154             return callback(Boom.internal('Invalid credentials'), credentials, artifacts);
  155         }
  156 
  157         if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) {
  158             return callback(Boom.internal('Unknown algorithm'), credentials, artifacts);
  159         }
  160 
  161         // Calculate MAC
  162 
  163         var mac = Crypto.calculateMac('header', credentials, artifacts);
  164         if (!Cryptiles.fixedTimeComparison(mac, attributes.mac)) {
  165             return callback(Boom.unauthorized('Bad mac', 'Hawk'), credentials, artifacts);
  166         }
  167 
  168         // Check payload hash
  169 
  170         if (options.payload ||
  171             options.payload === '') {
  172 
  173             if (!attributes.hash) {
  174                 return callback(Boom.unauthorized('Missing required payload hash', 'Hawk'), credentials, artifacts);
  175             }
  176 
  177             var hash = Crypto.calculatePayloadHash(options.payload, credentials.algorithm, request.contentType);
  178             if (!Cryptiles.fixedTimeComparison(hash, attributes.hash)) {
  179                 return callback(Boom.unauthorized('Bad payload hash', 'Hawk'), credentials, artifacts);
  180             }
  181         }
  182 
  183         // Check nonce
  184 
  185         options.nonceFunc(credentials.key, attributes.nonce, attributes.ts, function (err) {
  186 
  187             if (err) {
  188                 return callback(Boom.unauthorized('Invalid nonce', 'Hawk'), credentials, artifacts);
  189             }
  190 
  191             // Check timestamp staleness
  192 
  193             if (Math.abs((attributes.ts * 1000) - now) > (options.timestampSkewSec * 1000)) {
  194                 var tsm = Crypto.timestampMessage(credentials, options.localtimeOffsetMsec);
  195                 return callback(Boom.unauthorized('Stale timestamp', 'Hawk', tsm), credentials, artifacts);
  196             }
  197 
  198             // Successful authentication
  199 
  200             return callback(null, credentials, artifacts);
  201         });
  202     });
  203 };
  204 
  205 
  206 // Authenticate payload hash - used when payload cannot be provided during authenticate()
  207 
  208 /*
  209     payload:        raw request payload
  210     credentials:    from authenticate callback
  211     artifacts:      from authenticate callback
  212     contentType:    req.headers['content-type']
  213 */
  214 
  215 exports.authenticatePayload = function (payload, credentials, artifacts, contentType) {
  216 
  217     var calculatedHash = Crypto.calculatePayloadHash(payload, credentials.algorithm, contentType);
  218     return Cryptiles.fixedTimeComparison(calculatedHash, artifacts.hash);
  219 };
  220 
  221 
  222 // Authenticate payload hash - used when payload cannot be provided during authenticate()
  223 
  224 /*
  225     calculatedHash: the payload hash calculated using Crypto.calculatePayloadHash()
  226     artifacts:      from authenticate callback
  227 */
  228 
  229 exports.authenticatePayloadHash = function (calculatedHash, artifacts) {
  230 
  231     return Cryptiles.fixedTimeComparison(calculatedHash, artifacts.hash);
  232 };
  233 
  234 
  235 // Generate a Server-Authorization header for a given response
  236 
  237 /*
  238     credentials: {},                                        // Object received from authenticate()
  239     artifacts: {}                                           // Object received from authenticate(); 'mac', 'hash', and 'ext' - ignored
  240     options: {
  241         ext: 'application-specific',                        // Application specific data sent via the ext attribute
  242         payload: '{"some":"payload"}',                      // UTF-8 encoded string for body hash generation (ignored if hash provided)
  243         contentType: 'application/json',                    // Payload content-type (ignored if hash provided)
  244         hash: 'U4MKKSmiVxk37JCCrAVIjV='                     // Pre-calculated payload hash
  245     }
  246 */
  247 
  248 exports.header = function (credentials, artifacts, options) {
  249 
  250     // Prepare inputs
  251 
  252     options = options || {};
  253 
  254     if (!artifacts ||
  255         typeof artifacts !== 'object' ||
  256         typeof options !== 'object') {
  257 
  258         return '';
  259     }
  260 
  261     artifacts = Hoek.clone(artifacts);
  262     delete artifacts.mac;
  263     artifacts.hash = options.hash;
  264     artifacts.ext = options.ext;
  265 
  266     // Validate credentials
  267 
  268     if (!credentials ||
  269         !credentials.key ||
  270         !credentials.algorithm) {
  271 
  272         // Invalid credential object
  273         return '';
  274     }
  275 
  276     if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) {
  277         return '';
  278     }
  279 
  280     // Calculate payload hash
  281 
  282     if (!artifacts.hash &&
  283         (options.payload || options.payload === '')) {
  284 
  285         artifacts.hash = Crypto.calculatePayloadHash(options.payload, credentials.algorithm, options.contentType);
  286     }
  287 
  288     var mac = Crypto.calculateMac('response', credentials, artifacts);
  289 
  290     // Construct header
  291 
  292     var header = 'Hawk mac="' + mac + '"' +
  293                  (artifacts.hash ? ', hash="' + artifacts.hash + '"' : '');
  294 
  295     if (artifacts.ext !== null &&
  296         artifacts.ext !== undefined &&
  297         artifacts.ext !== '') {                       // Other falsey values allowed
  298 
  299         header += ', ext="' + Hoek.escapeHeaderAttribute(artifacts.ext) + '"';
  300     }
  301 
  302     return header;
  303 };
  304 
  305 
  306 /*
  307  * Arguments and options are the same as authenticate() with the exception that the only supported options are:
  308  * 'hostHeaderName', 'localtimeOffsetMsec', 'host', 'port'
  309  */
  310 
  311 
  312 //                       1     2             3           4
  313 internals.bewitRegex = /^(\/.*)([\?&])bewit\=([^&$]*)(?:&(.+))?$/;
  314 
  315 
  316 exports.authenticateBewit = function (req, credentialsFunc, options, callback) {
  317 
  318     callback = Hoek.nextTick(callback);
  319 
  320     // Application time
  321 
  322     var now = Utils.now(options.localtimeOffsetMsec);
  323 
  324     // Convert node Http request object to a request configuration object
  325 
  326     var request = Utils.parseRequest(req, options);
  327     if (request instanceof Error) {
  328         return callback(Boom.badRequest(request.message));
  329     }
  330 
  331     // Extract bewit
  332 
  333     if (request.url.length > Utils.limits.maxMatchLength) {
  334         return callback(Boom.badRequest('Resource path exceeds max length'));
  335     }
  336 
  337     var resource = request.url.match(internals.bewitRegex);
  338     if (!resource) {
  339         return callback(Boom.unauthorized(null, 'Hawk'));
  340     }
  341 
  342     // Bewit not empty
  343 
  344     if (!resource[3]) {
  345         return callback(Boom.unauthorized('Empty bewit', 'Hawk'));
  346     }
  347 
  348     // Verify method is GET
  349 
  350     if (request.method !== 'GET' &&
  351         request.method !== 'HEAD') {
  352 
  353         return callback(Boom.unauthorized('Invalid method', 'Hawk'));
  354     }
  355 
  356     // No other authentication
  357 
  358     if (request.authorization) {
  359         return callback(Boom.badRequest('Multiple authentications'));
  360     }
  361 
  362     // Parse bewit
  363 
  364     var bewitString = Hoek.base64urlDecode(resource[3]);
  365     if (bewitString instanceof Error) {
  366         return callback(Boom.badRequest('Invalid bewit encoding'));
  367     }
  368 
  369     // Bewit format: id\exp\mac\ext ('\' is used because it is a reserved header attribute character)
  370 
  371     var bewitParts = bewitString.split('\\');
  372     if (bewitParts.length !== 4) {
  373         return callback(Boom.badRequest('Invalid bewit structure'));
  374     }
  375 
  376     var bewit = {
  377         id: bewitParts[0],
  378         exp: parseInt(bewitParts[1], 10),
  379         mac: bewitParts[2],
  380         ext: bewitParts[3] || ''
  381     };
  382 
  383     if (!bewit.id ||
  384         !bewit.exp ||
  385         !bewit.mac) {
  386 
  387         return callback(Boom.badRequest('Missing bewit attributes'));
  388     }
  389 
  390     // Construct URL without bewit
  391 
  392     var url = resource[1];
  393     if (resource[4]) {
  394         url += resource[2] + resource[4];
  395     }
  396 
  397     // Check expiration
  398 
  399     if (bewit.exp * 1000 <= now) {
  400         return callback(Boom.unauthorized('Access expired', 'Hawk'), null, bewit);
  401     }
  402 
  403     // Fetch Hawk credentials
  404 
  405     credentialsFunc(bewit.id, function (err, credentials) {
  406 
  407         if (err) {
  408             return callback(err, credentials || null, bewit.ext);
  409         }
  410 
  411         if (!credentials) {
  412             return callback(Boom.unauthorized('Unknown credentials', 'Hawk'), null, bewit);
  413         }
  414 
  415         if (!credentials.key ||
  416             !credentials.algorithm) {
  417 
  418             return callback(Boom.internal('Invalid credentials'), credentials, bewit);
  419         }
  420 
  421         if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) {
  422             return callback(Boom.internal('Unknown algorithm'), credentials, bewit);
  423         }
  424 
  425         // Calculate MAC
  426 
  427         var mac = Crypto.calculateMac('bewit', credentials, {
  428             ts: bewit.exp,
  429             nonce: '',
  430             method: 'GET',
  431             resource: url,
  432             host: request.host,
  433             port: request.port,
  434             ext: bewit.ext
  435         });
  436 
  437         if (!Cryptiles.fixedTimeComparison(mac, bewit.mac)) {
  438             return callback(Boom.unauthorized('Bad mac', 'Hawk'), credentials, bewit);
  439         }
  440 
  441         // Successful authentication
  442 
  443         return callback(null, credentials, bewit);
  444     });
  445 };
  446 
  447 
  448 /*
  449  *  options are the same as authenticate() with the exception that the only supported options are:
  450  * 'nonceFunc', 'timestampSkewSec', 'localtimeOffsetMsec'
  451  */
  452 
  453 exports.authenticateMessage = function (host, port, message, authorization, credentialsFunc, options, callback) {
  454 
  455     callback = Hoek.nextTick(callback);
  456 
  457     // Default options
  458 
  459     options.nonceFunc = options.nonceFunc || internals.nonceFunc;
  460     options.timestampSkewSec = options.timestampSkewSec || 60;                                                  // 60 seconds
  461 
  462     // Application time
  463 
  464     var now = Utils.now(options.localtimeOffsetMsec);                       // Measure now before any other processing
  465 
  466     // Validate authorization
  467 
  468     if (!authorization.id ||
  469         !authorization.ts ||
  470         !authorization.nonce ||
  471         !authorization.hash ||
  472         !authorization.mac) {
  473 
  474         return callback(Boom.badRequest('Invalid authorization'));
  475     }
  476 
  477     // Fetch Hawk credentials
  478 
  479     credentialsFunc(authorization.id, function (err, credentials) {
  480 
  481         if (err) {
  482             return callback(err, credentials || null);
  483         }
  484 
  485         if (!credentials) {
  486             return callback(Boom.unauthorized('Unknown credentials', 'Hawk'));
  487         }
  488 
  489         if (!credentials.key ||
  490             !credentials.algorithm) {
  491 
  492             return callback(Boom.internal('Invalid credentials'), credentials);
  493         }
  494 
  495         if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) {
  496             return callback(Boom.internal('Unknown algorithm'), credentials);
  497         }
  498 
  499         // Construct artifacts container
  500 
  501         var artifacts = {
  502             ts: authorization.ts,
  503             nonce: authorization.nonce,
  504             host: host,
  505             port: port,
  506             hash: authorization.hash
  507         };
  508 
  509         // Calculate MAC
  510 
  511         var mac = Crypto.calculateMac('message', credentials, artifacts);
  512         if (!Cryptiles.fixedTimeComparison(mac, authorization.mac)) {
  513             return callback(Boom.unauthorized('Bad mac', 'Hawk'), credentials);
  514         }
  515 
  516         // Check payload hash
  517 
  518         var hash = Crypto.calculatePayloadHash(message, credentials.algorithm);
  519         if (!Cryptiles.fixedTimeComparison(hash, authorization.hash)) {
  520             return callback(Boom.unauthorized('Bad message hash', 'Hawk'), credentials);
  521         }
  522 
  523         // Check nonce
  524 
  525         options.nonceFunc(credentials.key, authorization.nonce, authorization.ts, function (err) {
  526 
  527             if (err) {
  528                 return callback(Boom.unauthorized('Invalid nonce', 'Hawk'), credentials);
  529             }
  530 
  531             // Check timestamp staleness
  532 
  533             if (Math.abs((authorization.ts * 1000) - now) > (options.timestampSkewSec * 1000)) {
  534                 return callback(Boom.unauthorized('Stale timestamp'), credentials);
  535             }
  536 
  537             // Successful authentication
  538 
  539             return callback(null, credentials);
  540         });
  541     });
  542 };
  543 
  544 
  545 internals.nonceFunc = function (key, nonce, ts, nonceCallback) {
  546 
  547     return nonceCallback();         // No validation
  548 };