"Fossies" - the Fresh Open Source Software Archive

Member "Atom/resources/app/apm/node_modules/hawk/lib/server.js" (11 Apr 2017, 18546 Bytes) of package /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(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 || function (nonce, ts, nonceCallback) { return nonceCallback(); };   // No validation
   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(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 exports.authenticateBewit = function (req, credentialsFunc, options, callback) {
  312 
  313     callback = Hoek.nextTick(callback);
  314 
  315     // Application time
  316 
  317     var now = Utils.now(options.localtimeOffsetMsec);
  318 
  319     // Convert node Http request object to a request configuration object
  320 
  321     var request = Utils.parseRequest(req, options);
  322     if (request instanceof Error) {
  323         return callback(Boom.badRequest(request.message));
  324     }
  325 
  326     // Extract bewit
  327 
  328     //                                 1     2             3           4     
  329     var resource = request.url.match(/^(\/.*)([\?&])bewit\=([^&$]*)(?:&(.+))?$/);
  330     if (!resource) {
  331         return callback(Boom.unauthorized(null, 'Hawk'));
  332     }
  333 
  334     // Bewit not empty
  335 
  336     if (!resource[3]) {
  337         return callback(Boom.unauthorized('Empty bewit', 'Hawk'));
  338     }
  339 
  340     // Verify method is GET
  341 
  342     if (request.method !== 'GET' &&
  343         request.method !== 'HEAD') {
  344 
  345         return callback(Boom.unauthorized('Invalid method', 'Hawk'));
  346     }
  347 
  348     // No other authentication
  349 
  350     if (request.authorization) {
  351         return callback(Boom.badRequest('Multiple authentications'));
  352     }
  353 
  354     // Parse bewit
  355 
  356     var bewitString = Hoek.base64urlDecode(resource[3]);
  357     if (bewitString instanceof Error) {
  358         return callback(Boom.badRequest('Invalid bewit encoding'));
  359     }
  360 
  361     // Bewit format: id\exp\mac\ext ('\' is used because it is a reserved header attribute character)
  362 
  363     var bewitParts = bewitString.split('\\');
  364     if (bewitParts.length !== 4) {
  365         return callback(Boom.badRequest('Invalid bewit structure'));
  366     }
  367 
  368     var bewit = {
  369         id: bewitParts[0],
  370         exp: parseInt(bewitParts[1], 10),
  371         mac: bewitParts[2],
  372         ext: bewitParts[3] || ''
  373     };
  374 
  375     if (!bewit.id ||
  376         !bewit.exp ||
  377         !bewit.mac) {
  378 
  379         return callback(Boom.badRequest('Missing bewit attributes'));
  380     }
  381 
  382     // Construct URL without bewit
  383 
  384     var url = resource[1];
  385     if (resource[4]) {
  386         url += resource[2] + resource[4];
  387     }
  388 
  389     // Check expiration
  390 
  391     if (bewit.exp * 1000 <= now) {
  392         return callback(Boom.unauthorized('Access expired', 'Hawk'), null, bewit);
  393     }
  394 
  395     // Fetch Hawk credentials
  396 
  397     credentialsFunc(bewit.id, function (err, credentials) {
  398 
  399         if (err) {
  400             return callback(err, credentials || null, bewit.ext);
  401         }
  402 
  403         if (!credentials) {
  404             return callback(Boom.unauthorized('Unknown credentials', 'Hawk'), null, bewit);
  405         }
  406 
  407         if (!credentials.key ||
  408             !credentials.algorithm) {
  409 
  410             return callback(Boom.internal('Invalid credentials'), credentials, bewit);
  411         }
  412 
  413         if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) {
  414             return callback(Boom.internal('Unknown algorithm'), credentials, bewit);
  415         }
  416 
  417         // Calculate MAC
  418 
  419         var mac = Crypto.calculateMac('bewit', credentials, {
  420             ts: bewit.exp,
  421             nonce: '',
  422             method: 'GET',
  423             resource: url,
  424             host: request.host,
  425             port: request.port,
  426             ext: bewit.ext
  427         });
  428 
  429         if (!Cryptiles.fixedTimeComparison(mac, bewit.mac)) {
  430             return callback(Boom.unauthorized('Bad mac', 'Hawk'), credentials, bewit);
  431         }
  432 
  433         // Successful authentication
  434 
  435         return callback(null, credentials, bewit);
  436     });
  437 };
  438 
  439 
  440 /*
  441  *  options are the same as authenticate() with the exception that the only supported options are:
  442  * 'nonceFunc', 'timestampSkewSec', 'localtimeOffsetMsec'
  443  */
  444 
  445 exports.authenticateMessage = function (host, port, message, authorization, credentialsFunc, options, callback) {
  446 
  447     callback = Hoek.nextTick(callback);
  448     
  449     // Default options
  450 
  451     options.nonceFunc = options.nonceFunc || function (nonce, ts, nonceCallback) { return nonceCallback(); };   // No validation
  452     options.timestampSkewSec = options.timestampSkewSec || 60;                                                  // 60 seconds
  453 
  454     // Application time
  455 
  456     var now = Utils.now(options.localtimeOffsetMsec);                       // Measure now before any other processing
  457 
  458     // Validate authorization
  459     
  460     if (!authorization.id ||
  461         !authorization.ts ||
  462         !authorization.nonce ||
  463         !authorization.hash ||
  464         !authorization.mac) {
  465         
  466             return callback(Boom.badRequest('Invalid authorization'))
  467     }
  468 
  469     // Fetch Hawk credentials
  470 
  471     credentialsFunc(authorization.id, function (err, credentials) {
  472 
  473         if (err) {
  474             return callback(err, credentials || null);
  475         }
  476 
  477         if (!credentials) {
  478             return callback(Boom.unauthorized('Unknown credentials', 'Hawk'));
  479         }
  480 
  481         if (!credentials.key ||
  482             !credentials.algorithm) {
  483 
  484             return callback(Boom.internal('Invalid credentials'), credentials);
  485         }
  486 
  487         if (Crypto.algorithms.indexOf(credentials.algorithm) === -1) {
  488             return callback(Boom.internal('Unknown algorithm'), credentials);
  489         }
  490 
  491         // Construct artifacts container
  492 
  493         var artifacts = {
  494             ts: authorization.ts,
  495             nonce: authorization.nonce,
  496             host: host,
  497             port: port,
  498             hash: authorization.hash
  499         };
  500 
  501         // Calculate MAC
  502 
  503         var mac = Crypto.calculateMac('message', credentials, artifacts);
  504         if (!Cryptiles.fixedTimeComparison(mac, authorization.mac)) {
  505             return callback(Boom.unauthorized('Bad mac', 'Hawk'), credentials);
  506         }
  507 
  508         // Check payload hash
  509 
  510         var hash = Crypto.calculatePayloadHash(message, credentials.algorithm);
  511         if (!Cryptiles.fixedTimeComparison(hash, authorization.hash)) {
  512             return callback(Boom.unauthorized('Bad message hash', 'Hawk'), credentials);
  513         }
  514 
  515         // Check nonce
  516 
  517         options.nonceFunc(authorization.nonce, authorization.ts, function (err) {
  518 
  519             if (err) {
  520                 return callback(Boom.unauthorized('Invalid nonce', 'Hawk'), credentials);
  521             }
  522 
  523             // Check timestamp staleness
  524 
  525             if (Math.abs((authorization.ts * 1000) - now) > (options.timestampSkewSec * 1000)) {
  526                 return callback(Boom.unauthorized('Stale timestamp'), credentials);
  527             }
  528 
  529             // Successful authentication
  530 
  531             return callback(null, credentials);
  532         });
  533     });
  534 };