"Fossies" - the Fresh Open Source Software Archive

Member "Atom/resources/app/apm/node_modules/request/node_modules/http-signature/lib/signer.js" (7 Feb 2017, 12934 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 // Copyright 2012 Joyent, Inc.  All rights reserved.
    2 
    3 var assert = require('assert-plus');
    4 var crypto = require('crypto');
    5 var http = require('http');
    6 var util = require('util');
    7 var sshpk = require('sshpk');
    8 var jsprim = require('jsprim');
    9 var utils = require('./utils');
   10 
   11 var sprintf = require('util').format;
   12 
   13 var HASH_ALGOS = utils.HASH_ALGOS;
   14 var PK_ALGOS = utils.PK_ALGOS;
   15 var InvalidAlgorithmError = utils.InvalidAlgorithmError;
   16 var HttpSignatureError = utils.HttpSignatureError;
   17 var validateAlgorithm = utils.validateAlgorithm;
   18 
   19 ///--- Globals
   20 
   21 var AUTHZ_FMT =
   22   'Signature keyId="%s",algorithm="%s",headers="%s",signature="%s"';
   23 
   24 ///--- Specific Errors
   25 
   26 function MissingHeaderError(message) {
   27   HttpSignatureError.call(this, message, MissingHeaderError);
   28 }
   29 util.inherits(MissingHeaderError, HttpSignatureError);
   30 
   31 function StrictParsingError(message) {
   32   HttpSignatureError.call(this, message, StrictParsingError);
   33 }
   34 util.inherits(StrictParsingError, HttpSignatureError);
   35 
   36 /* See createSigner() */
   37 function RequestSigner(options) {
   38   assert.object(options, 'options');
   39 
   40   var alg = [];
   41   if (options.algorithm !== undefined) {
   42     assert.string(options.algorithm, 'options.algorithm');
   43     alg = validateAlgorithm(options.algorithm);
   44   }
   45   this.rs_alg = alg;
   46 
   47   /*
   48    * RequestSigners come in two varieties: ones with an rs_signFunc, and ones
   49    * with an rs_signer.
   50    *
   51    * rs_signFunc-based RequestSigners have to build up their entire signing
   52    * string within the rs_lines array and give it to rs_signFunc as a single
   53    * concat'd blob. rs_signer-based RequestSigners can add a line at a time to
   54    * their signing state by using rs_signer.update(), thus only needing to
   55    * buffer the hash function state and one line at a time.
   56    */
   57   if (options.sign !== undefined) {
   58     assert.func(options.sign, 'options.sign');
   59     this.rs_signFunc = options.sign;
   60 
   61   } else if (alg[0] === 'hmac' && options.key !== undefined) {
   62     assert.string(options.keyId, 'options.keyId');
   63     this.rs_keyId = options.keyId;
   64 
   65     if (typeof (options.key) !== 'string' && !Buffer.isBuffer(options.key))
   66       throw (new TypeError('options.key for HMAC must be a string or Buffer'));
   67 
   68     /*
   69      * Make an rs_signer for HMACs, not a rs_signFunc -- HMACs digest their
   70      * data in chunks rather than requiring it all to be given in one go
   71      * at the end, so they are more similar to signers than signFuncs.
   72      */
   73     this.rs_signer = crypto.createHmac(alg[1].toUpperCase(), options.key);
   74     this.rs_signer.sign = function () {
   75       var digest = this.digest('base64');
   76       return ({
   77         hashAlgorithm: alg[1],
   78         toString: function () { return (digest); }
   79       });
   80     };
   81 
   82   } else if (options.key !== undefined) {
   83     var key = options.key;
   84     if (typeof (key) === 'string' || Buffer.isBuffer(key))
   85       key = sshpk.parsePrivateKey(key);
   86 
   87     assert.ok(sshpk.PrivateKey.isPrivateKey(key, [1, 2]),
   88       'options.key must be a sshpk.PrivateKey');
   89     this.rs_key = key;
   90 
   91     assert.string(options.keyId, 'options.keyId');
   92     this.rs_keyId = options.keyId;
   93 
   94     if (!PK_ALGOS[key.type]) {
   95       throw (new InvalidAlgorithmError(key.type.toUpperCase() + ' type ' +
   96         'keys are not supported'));
   97     }
   98 
   99     if (alg[0] !== undefined && key.type !== alg[0]) {
  100       throw (new InvalidAlgorithmError('options.key must be a ' +
  101         alg[0].toUpperCase() + ' key, was given a ' +
  102         key.type.toUpperCase() + ' key instead'));
  103     }
  104 
  105     this.rs_signer = key.createSign(alg[1]);
  106 
  107   } else {
  108     throw (new TypeError('options.sign (func) or options.key is required'));
  109   }
  110 
  111   this.rs_headers = [];
  112   this.rs_lines = [];
  113 }
  114 
  115 /**
  116  * Adds a header to be signed, with its value, into this signer.
  117  *
  118  * @param {String} header
  119  * @param {String} value
  120  * @return {String} value written
  121  */
  122 RequestSigner.prototype.writeHeader = function (header, value) {
  123   assert.string(header, 'header');
  124   header = header.toLowerCase();
  125   assert.string(value, 'value');
  126 
  127   this.rs_headers.push(header);
  128 
  129   if (this.rs_signFunc) {
  130     this.rs_lines.push(header + ': ' + value);
  131 
  132   } else {
  133     var line = header + ': ' + value;
  134     if (this.rs_headers.length > 0)
  135       line = '\n' + line;
  136     this.rs_signer.update(line);
  137   }
  138 
  139   return (value);
  140 };
  141 
  142 /**
  143  * Adds a default Date header, returning its value.
  144  *
  145  * @return {String}
  146  */
  147 RequestSigner.prototype.writeDateHeader = function () {
  148   return (this.writeHeader('date', jsprim.rfc1123(new Date())));
  149 };
  150 
  151 /**
  152  * Adds the request target line to be signed.
  153  *
  154  * @param {String} method, HTTP method (e.g. 'get', 'post', 'put')
  155  * @param {String} path
  156  */
  157 RequestSigner.prototype.writeTarget = function (method, path) {
  158   assert.string(method, 'method');
  159   assert.string(path, 'path');
  160   method = method.toLowerCase();
  161   this.writeHeader('(request-target)', method + ' ' + path);
  162 };
  163 
  164 /**
  165  * Calculate the value for the Authorization header on this request
  166  * asynchronously.
  167  *
  168  * @param {Func} callback (err, authz)
  169  */
  170 RequestSigner.prototype.sign = function (cb) {
  171   assert.func(cb, 'callback');
  172 
  173   if (this.rs_headers.length < 1)
  174     throw (new Error('At least one header must be signed'));
  175 
  176   var alg, authz;
  177   if (this.rs_signFunc) {
  178     var data = this.rs_lines.join('\n');
  179     var self = this;
  180     this.rs_signFunc(data, function (err, sig) {
  181       if (err) {
  182         cb(err);
  183         return;
  184       }
  185       try {
  186         assert.object(sig, 'signature');
  187         assert.string(sig.keyId, 'signature.keyId');
  188         assert.string(sig.algorithm, 'signature.algorithm');
  189         assert.string(sig.signature, 'signature.signature');
  190         alg = validateAlgorithm(sig.algorithm);
  191 
  192         authz = sprintf(AUTHZ_FMT,
  193           sig.keyId,
  194           sig.algorithm,
  195           self.rs_headers.join(' '),
  196           sig.signature);
  197       } catch (e) {
  198         cb(e);
  199         return;
  200       }
  201       cb(null, authz);
  202     });
  203 
  204   } else {
  205     try {
  206       var sigObj = this.rs_signer.sign();
  207     } catch (e) {
  208       cb(e);
  209       return;
  210     }
  211     alg = (this.rs_alg[0] || this.rs_key.type) + '-' + sigObj.hashAlgorithm;
  212     var signature = sigObj.toString();
  213     authz = sprintf(AUTHZ_FMT,
  214       this.rs_keyId,
  215       alg,
  216       this.rs_headers.join(' '),
  217       signature);
  218     cb(null, authz);
  219   }
  220 };
  221 
  222 ///--- Exported API
  223 
  224 module.exports = {
  225   /**
  226    * Identifies whether a given object is a request signer or not.
  227    *
  228    * @param {Object} object, the object to identify
  229    * @returns {Boolean}
  230    */
  231   isSigner: function (obj) {
  232     if (typeof (obj) === 'object' && obj instanceof RequestSigner)
  233       return (true);
  234     return (false);
  235   },
  236 
  237   /**
  238    * Creates a request signer, used to asynchronously build a signature
  239    * for a request (does not have to be an http.ClientRequest).
  240    *
  241    * @param {Object} options, either:
  242    *                   - {String} keyId
  243    *                   - {String|Buffer} key
  244    *                   - {String} algorithm (optional, required for HMAC)
  245    *                 or:
  246    *                   - {Func} sign (data, cb)
  247    * @return {RequestSigner}
  248    */
  249   createSigner: function createSigner(options) {
  250     return (new RequestSigner(options));
  251   },
  252 
  253   /**
  254    * Adds an 'Authorization' header to an http.ClientRequest object.
  255    *
  256    * Note that this API will add a Date header if it's not already set. Any
  257    * other headers in the options.headers array MUST be present, or this
  258    * will throw.
  259    *
  260    * You shouldn't need to check the return type; it's just there if you want
  261    * to be pedantic.
  262    *
  263    * The optional flag indicates whether parsing should use strict enforcement
  264    * of the version draft-cavage-http-signatures-04 of the spec or beyond.
  265    * The default is to be loose and support
  266    * older versions for compatibility.
  267    *
  268    * @param {Object} request an instance of http.ClientRequest.
  269    * @param {Object} options signing parameters object:
  270    *                   - {String} keyId required.
  271    *                   - {String} key required (either a PEM or HMAC key).
  272    *                   - {Array} headers optional; defaults to ['date'].
  273    *                   - {String} algorithm optional (unless key is HMAC);
  274    *                              default is the same as the sshpk default
  275    *                              signing algorithm for the type of key given
  276    *                   - {String} httpVersion optional; defaults to '1.1'.
  277    *                   - {Boolean} strict optional; defaults to 'false'.
  278    * @return {Boolean} true if Authorization (and optionally Date) were added.
  279    * @throws {TypeError} on bad parameter types (input).
  280    * @throws {InvalidAlgorithmError} if algorithm was bad or incompatible with
  281    *                                 the given key.
  282    * @throws {sshpk.KeyParseError} if key was bad.
  283    * @throws {MissingHeaderError} if a header to be signed was specified but
  284    *                              was not present.
  285    */
  286   signRequest: function signRequest(request, options) {
  287     assert.object(request, 'request');
  288     assert.object(options, 'options');
  289     assert.optionalString(options.algorithm, 'options.algorithm');
  290     assert.string(options.keyId, 'options.keyId');
  291     assert.optionalArrayOfString(options.headers, 'options.headers');
  292     assert.optionalString(options.httpVersion, 'options.httpVersion');
  293 
  294     if (!request.getHeader('Date'))
  295       request.setHeader('Date', jsprim.rfc1123(new Date()));
  296     if (!options.headers)
  297       options.headers = ['date'];
  298     if (!options.httpVersion)
  299       options.httpVersion = '1.1';
  300 
  301     var alg = [];
  302     if (options.algorithm) {
  303       options.algorithm = options.algorithm.toLowerCase();
  304       alg = validateAlgorithm(options.algorithm);
  305     }
  306 
  307     var i;
  308     var stringToSign = '';
  309     for (i = 0; i < options.headers.length; i++) {
  310       if (typeof (options.headers[i]) !== 'string')
  311         throw new TypeError('options.headers must be an array of Strings');
  312 
  313       var h = options.headers[i].toLowerCase();
  314 
  315       if (h === 'request-line') {
  316         if (!options.strict) {
  317           /**
  318            * We allow headers from the older spec drafts if strict parsing isn't
  319            * specified in options.
  320            */
  321           stringToSign +=
  322             request.method + ' ' + request.path + ' HTTP/' +
  323             options.httpVersion;
  324         } else {
  325           /* Strict parsing doesn't allow older draft headers. */
  326           throw (new StrictParsingError('request-line is not a valid header ' +
  327             'with strict parsing enabled.'));
  328         }
  329       } else if (h === '(request-target)') {
  330         stringToSign +=
  331           '(request-target): ' + request.method.toLowerCase() + ' ' +
  332           request.path;
  333       } else {
  334         var value = request.getHeader(h);
  335         if (value === undefined || value === '') {
  336           throw new MissingHeaderError(h + ' was not in the request');
  337         }
  338         stringToSign += h + ': ' + value;
  339       }
  340 
  341       if ((i + 1) < options.headers.length)
  342         stringToSign += '\n';
  343     }
  344 
  345     /* This is just for unit tests. */
  346     if (request.hasOwnProperty('_stringToSign')) {
  347       request._stringToSign = stringToSign;
  348     }
  349 
  350     var signature;
  351     if (alg[0] === 'hmac') {
  352       if (typeof (options.key) !== 'string' && !Buffer.isBuffer(options.key))
  353         throw (new TypeError('options.key must be a string or Buffer'));
  354 
  355       var hmac = crypto.createHmac(alg[1].toUpperCase(), options.key);
  356       hmac.update(stringToSign);
  357       signature = hmac.digest('base64');
  358 
  359     } else {
  360       var key = options.key;
  361       if (typeof (key) === 'string' || Buffer.isBuffer(key))
  362         key = sshpk.parsePrivateKey(options.key);
  363 
  364       assert.ok(sshpk.PrivateKey.isPrivateKey(key, [1, 2]),
  365         'options.key must be a sshpk.PrivateKey');
  366 
  367       if (!PK_ALGOS[key.type]) {
  368         throw (new InvalidAlgorithmError(key.type.toUpperCase() + ' type ' +
  369           'keys are not supported'));
  370       }
  371 
  372       if (alg[0] !== undefined && key.type !== alg[0]) {
  373         throw (new InvalidAlgorithmError('options.key must be a ' +
  374           alg[0].toUpperCase() + ' key, was given a ' +
  375           key.type.toUpperCase() + ' key instead'));
  376       }
  377 
  378       var signer = key.createSign(alg[1]);
  379       signer.update(stringToSign);
  380       var sigObj = signer.sign();
  381       if (!HASH_ALGOS[sigObj.hashAlgorithm]) {
  382         throw (new InvalidAlgorithmError(sigObj.hashAlgorithm.toUpperCase() +
  383           ' is not a supported hash algorithm'));
  384       }
  385       options.algorithm = key.type + '-' + sigObj.hashAlgorithm;
  386       signature = sigObj.toString();
  387       assert.notStrictEqual(signature, '', 'empty signature produced');
  388     }
  389 
  390     request.setHeader('Authorization', sprintf(AUTHZ_FMT,
  391                                                options.keyId,
  392                                                options.algorithm,
  393                                                options.headers.join(' '),
  394                                                signature));
  395 
  396     return true;
  397   }
  398 
  399 };