| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318 | // Copyright 2012 Joyent, Inc.  All rights reserved.var assert = require('assert-plus');var util = require('util');var utils = require('./utils');///--- Globalsvar HASH_ALGOS = utils.HASH_ALGOS;var PK_ALGOS = utils.PK_ALGOS;var HttpSignatureError = utils.HttpSignatureError;var InvalidAlgorithmError = utils.InvalidAlgorithmError;var validateAlgorithm = utils.validateAlgorithm;var State = {  New: 0,  Params: 1};var ParamsState = {  Name: 0,  Quote: 1,  Value: 2,  Comma: 3};///--- Specific Errorsfunction ExpiredRequestError(message) {  HttpSignatureError.call(this, message, ExpiredRequestError);}util.inherits(ExpiredRequestError, HttpSignatureError);function InvalidHeaderError(message) {  HttpSignatureError.call(this, message, InvalidHeaderError);}util.inherits(InvalidHeaderError, HttpSignatureError);function InvalidParamsError(message) {  HttpSignatureError.call(this, message, InvalidParamsError);}util.inherits(InvalidParamsError, HttpSignatureError);function MissingHeaderError(message) {  HttpSignatureError.call(this, message, MissingHeaderError);}util.inherits(MissingHeaderError, HttpSignatureError);function StrictParsingError(message) {  HttpSignatureError.call(this, message, StrictParsingError);}util.inherits(StrictParsingError, HttpSignatureError);///--- Exported APImodule.exports = {  /**   * Parses the 'Authorization' header out of an http.ServerRequest object.   *   * Note that this API will fully validate the Authorization header, and throw   * on any error.  It will not however check the signature, or the keyId format   * as those are specific to your environment.  You can use the options object   * to pass in extra constraints.   *   * As a response object you can expect this:   *   *     {   *       "scheme": "Signature",   *       "params": {   *         "keyId": "foo",   *         "algorithm": "rsa-sha256",   *         "headers": [   *           "date" or "x-date",   *           "digest"   *         ],   *         "signature": "base64"   *       },   *       "signingString": "ready to be passed to crypto.verify()"   *     }   *   * @param {Object} request an http.ServerRequest.   * @param {Object} options an optional options object with:   *                   - clockSkew: allowed clock skew in seconds (default 300).   *                   - headers: required header names (def: date or x-date)   *                   - algorithms: algorithms to support (default: all).   *                   - strict: should enforce latest spec parsing   *                             (default: false).   * @return {Object} parsed out object (see above).   * @throws {TypeError} on invalid input.   * @throws {InvalidHeaderError} on an invalid Authorization header error.   * @throws {InvalidParamsError} if the params in the scheme are invalid.   * @throws {MissingHeaderError} if the params indicate a header not present,   *                              either in the request headers from the params,   *                              or not in the params from a required header   *                              in options.   * @throws {StrictParsingError} if old attributes are used in strict parsing   *                              mode.   * @throws {ExpiredRequestError} if the value of date or x-date exceeds skew.   */  parseRequest: function parseRequest(request, options) {    assert.object(request, 'request');    assert.object(request.headers, 'request.headers');    if (options === undefined) {      options = {};    }    if (options.headers === undefined) {      options.headers = [request.headers['x-date'] ? 'x-date' : 'date'];    }    assert.object(options, 'options');    assert.arrayOfString(options.headers, 'options.headers');    assert.optionalNumber(options.clockSkew, 'options.clockSkew');    if (!request.headers.authorization)      throw new MissingHeaderError('no authorization header present in ' +                                   'the request');    options.clockSkew = options.clockSkew || 300;    var i = 0;    var state = State.New;    var substate = ParamsState.Name;    var tmpName = '';    var tmpValue = '';    var parsed = {      scheme: '',      params: {},      signingString: '',      get algorithm() {        return this.params.algorithm.toUpperCase();      },      get keyId() {        return this.params.keyId;      }    };    var authz = request.headers.authorization;    for (i = 0; i < authz.length; i++) {      var c = authz.charAt(i);      switch (Number(state)) {      case State.New:        if (c !== ' ') parsed.scheme += c;        else state = State.Params;        break;      case State.Params:        switch (Number(substate)) {        case ParamsState.Name:          var code = c.charCodeAt(0);          // restricted name of A-Z / a-z          if ((code >= 0x41 && code <= 0x5a) || // A-Z              (code >= 0x61 && code <= 0x7a)) { // a-z            tmpName += c;          } else if (c === '=') {            if (tmpName.length === 0)              throw new InvalidHeaderError('bad param format');            substate = ParamsState.Quote;          } else {            throw new InvalidHeaderError('bad param format');          }          break;        case ParamsState.Quote:          if (c === '"') {            tmpValue = '';            substate = ParamsState.Value;          } else {            throw new InvalidHeaderError('bad param format');          }          break;        case ParamsState.Value:          if (c === '"') {            parsed.params[tmpName] = tmpValue;            substate = ParamsState.Comma;          } else {            tmpValue += c;          }          break;        case ParamsState.Comma:          if (c === ',') {            tmpName = '';            substate = ParamsState.Name;          } else {            throw new InvalidHeaderError('bad param format');          }          break;        default:          throw new Error('Invalid substate');        }        break;      default:        throw new Error('Invalid substate');      }    }    if (!parsed.params.headers || parsed.params.headers === '') {      if (request.headers['x-date']) {        parsed.params.headers = ['x-date'];      } else {        parsed.params.headers = ['date'];      }    } else {      parsed.params.headers = parsed.params.headers.split(' ');    }    // Minimally validate the parsed object    if (!parsed.scheme || parsed.scheme !== 'Signature')      throw new InvalidHeaderError('scheme was not "Signature"');    if (!parsed.params.keyId)      throw new InvalidHeaderError('keyId was not specified');    if (!parsed.params.algorithm)      throw new InvalidHeaderError('algorithm was not specified');    if (!parsed.params.signature)      throw new InvalidHeaderError('signature was not specified');    // Check the algorithm against the official list    parsed.params.algorithm = parsed.params.algorithm.toLowerCase();    try {      validateAlgorithm(parsed.params.algorithm);    } catch (e) {      if (e instanceof InvalidAlgorithmError)        throw (new InvalidParamsError(parsed.params.algorithm + ' is not ' +          'supported'));      else        throw (e);    }    // Build the signingString    for (i = 0; i < parsed.params.headers.length; i++) {      var h = parsed.params.headers[i].toLowerCase();      parsed.params.headers[i] = h;      if (h === 'request-line') {        if (!options.strict) {          /*           * We allow headers from the older spec drafts if strict parsing isn't           * specified in options.           */          parsed.signingString +=            request.method + ' ' + request.url + ' HTTP/' + request.httpVersion;        } else {          /* Strict parsing doesn't allow older draft headers. */          throw (new StrictParsingError('request-line is not a valid header ' +            'with strict parsing enabled.'));        }      } else if (h === '(request-target)') {        parsed.signingString +=          '(request-target): ' + request.method.toLowerCase() + ' ' +          request.url;      } else {        var value = request.headers[h];        if (value === undefined)          throw new MissingHeaderError(h + ' was not in the request');        parsed.signingString += h + ': ' + value;      }      if ((i + 1) < parsed.params.headers.length)        parsed.signingString += '\n';    }    // Check against the constraints    var date;    if (request.headers.date || request.headers['x-date']) {        if (request.headers['x-date']) {          date = new Date(request.headers['x-date']);        } else {          date = new Date(request.headers.date);        }      var now = new Date();      var skew = Math.abs(now.getTime() - date.getTime());      if (skew > options.clockSkew * 1000) {        throw new ExpiredRequestError('clock skew of ' +                                      (skew / 1000) +                                      's was greater than ' +                                      options.clockSkew + 's');      }    }    options.headers.forEach(function (hdr) {      // Remember that we already checked any headers in the params      // were in the request, so if this passes we're good.      if (parsed.params.headers.indexOf(hdr) < 0)        throw new MissingHeaderError(hdr + ' was not a signed header');    });    if (options.algorithms) {      if (options.algorithms.indexOf(parsed.params.algorithm) === -1)        throw new InvalidParamsError(parsed.params.algorithm +                                     ' is not a supported algorithm');    }    return parsed;  }};
 |