Name: js-handler/node_modules/restify/node_modules/http-signature/lib/parser.js 
1:
// Copyright 2012 Joyent, Inc.  All rights reserved.
2:
 
3:
var assert = require('assert-plus');
4:
var util = require('util');
5:
 
6:
 
7:
 
8:
///--- Globals
9:
 
10:
var Algorithms = {
11:
  'rsa-sha1': true,
12:
  'rsa-sha256': true,
13:
  'rsa-sha512': true,
14:
  'dsa-sha1': true,
15:
  'hmac-sha1': true,
16:
  'hmac-sha256': true,
17:
  'hmac-sha512': true
18:
};
19:
 
20:
var State = {
21:
  New: 0,
22:
  Params: 1
23:
};
24:
 
25:
var ParamsState = {
26:
  Name: 0,
27:
  Quote: 1,
28:
  Value: 2,
29:
  Comma: 3
30:
};
31:
 
32:
 
33:
 
34:
///--- Specific Errors
35:
 
36:
function HttpSignatureError(message, caller) {
37:
  if (Error.captureStackTrace)
38:
    Error.captureStackTrace(this, caller || HttpSignatureError);
39:
 
40:
  this.message = message;
41:
  this.name = caller.name;
42:
}
43:
util.inherits(HttpSignatureError, Error);
44:
 
45:
function ExpiredRequestError(message) {
46:
  HttpSignatureError.call(this, message, ExpiredRequestError);
47:
}
48:
util.inherits(ExpiredRequestError, HttpSignatureError);
49:
 
50:
 
51:
function InvalidHeaderError(message) {
52:
  HttpSignatureError.call(this, message, InvalidHeaderError);
53:
}
54:
util.inherits(InvalidHeaderError, HttpSignatureError);
55:
 
56:
 
57:
function InvalidParamsError(message) {
58:
  HttpSignatureError.call(this, message, InvalidParamsError);
59:
}
60:
util.inherits(InvalidParamsError, HttpSignatureError);
61:
 
62:
 
63:
function MissingHeaderError(message) {
64:
  HttpSignatureError.call(this, message, MissingHeaderError);
65:
}
66:
util.inherits(MissingHeaderError, HttpSignatureError);
67:
 
68:
 
69:
 
70:
///--- Exported API
71:
 
72:
module.exports = {
73:
 
74:
  /**
75:
   * Parses the 'Authorization' header out of an http.ServerRequest object.
76:
   *
77:
   * Note that this API will fully validate the Authorization header, and throw
78:
   * on any error.  It will not however check the signature, or the keyId format
79:
   * as those are specific to your environment.  You can use the options object
80:
   * to pass in extra constraints.
81:
   *
82:
   * As a response object you can expect this:
83:
   *
84:
   *     {
85:
   *       "scheme": "Signature",
86:
   *       "params": {
87:
   *         "keyId": "foo",
88:
   *         "algorithm": "rsa-sha256",
89:
   *         "headers": [
90:
   *           "date" or "x-date",
91:
   *           "content-md5"
92:
   *         ],
93:
   *         "signature": "base64"
94:
   *       },
95:
   *       "signingString": "ready to be passed to crypto.verify()"
96:
   *     }
97:
   *
98:
   * @param {Object} request an http.ServerRequest.
99:
   * @param {Object} options an optional options object with:
100:
   *                   - clockSkew: allowed clock skew in seconds (default 300).
101:
   *                   - headers: required header names (def: date or x-date)
102:
   *                   - algorithms: algorithms to support (default: all).
103:
   * @return {Object} parsed out object (see above).
104:
   * @throws {TypeError} on invalid input.
105:
   * @throws {InvalidHeaderError} on an invalid Authorization header error.
106:
   * @throws {InvalidParamsError} if the params in the scheme are invalid.
107:
   * @throws {MissingHeaderError} if the params indicate a header not present,
108:
   *                              either in the request headers from the params,
109:
   *                              or not in the params from a required header
110:
   *                              in options.
111:
   * @throws {ExpiredRequestError} if the value of date or x-date exceeds skew.
112:
   */
113:
  parseRequest: function parseRequest(request, options) {
114:
    assert.object(request, 'request');
115:
    assert.object(request.headers, 'request.headers');
116:
    if (options === undefined) {
117:
      options = {};
118:
    }
119:
    if (options.headers === undefined) {
120:
      options.headers = [request.headers['x-date'] ? 'x-date' : 'date'];
121:
    }
122:
    assert.object(options, 'options');
123:
    assert.arrayOfString(options.headers, 'options.headers');
124:
    assert.optionalNumber(options.clockSkew, 'options.clockSkew');
125:
 
126:
    if (!request.headers.authorization)
127:
      throw new MissingHeaderError('no authorization header present in ' +
128:
                                   'the request');
129:
 
130:
    options.clockSkew = options.clockSkew || 300;
131:
 
132:
 
133:
    var i = 0;
134:
    var state = State.New;
135:
    var substate = ParamsState.Name;
136:
    var tmpName = '';
137:
    var tmpValue = '';
138:
 
139:
    var parsed = {
140:
      scheme: '',
141:
      params: {},
142:
      signingString: '',
143:
 
144:
      get algorithm() {
145:
        return this.params.algorithm.toUpperCase();
146:
      },
147:
 
148:
      get keyId() {
149:
        return this.params.keyId;
150:
      }
151:
 
152:
    };
153:
 
154:
    var authz = request.headers.authorization;
155:
    for (i = 0; i < authz.length; i++) {
156:
      var c = authz.charAt(i);
157:
 
158:
      switch (Number(state)) {
159:
 
160:
      case State.New:
161:
        if (c !== ' ') parsed.scheme += c;
162:
        else state = State.Params;
163:
        break;
164:
 
165:
      case State.Params:
166:
        switch (Number(substate)) {
167:
 
168:
        case ParamsState.Name:
169:
          var code = c.charCodeAt(0);
170:
          // restricted name of A-Z / a-z
171:
          if ((code >= 0x41 && code <= 0x5a) || // A-Z
172:
              (code >= 0x61 && code <= 0x7a)) { // a-z
173:
            tmpName += c;
174:
          } else if (c === '=') {
175:
            if (tmpName.length === 0)
176:
              throw new InvalidHeaderError('bad param format');
177:
            substate = ParamsState.Quote;
178:
          } else {
179:
            throw new InvalidHeaderError('bad param format');
180:
          }
181:
          break;
182:
 
183:
        case ParamsState.Quote:
184:
          if (c === '"') {
185:
            tmpValue = '';
186:
            substate = ParamsState.Value;
187:
          } else {
188:
            throw new InvalidHeaderError('bad param format');
189:
          }
190:
          break;
191:
 
192:
        case ParamsState.Value:
193:
          if (c === '"') {
194:
            parsed.params[tmpName] = tmpValue;
195:
            substate = ParamsState.Comma;
196:
          } else {
197:
            tmpValue += c;
198:
          }
199:
          break;
200:
 
201:
        case ParamsState.Comma:
202:
          if (c === ',') {
203:
            tmpName = '';
204:
            substate = ParamsState.Name;
205:
          } else {
206:
            throw new InvalidHeaderError('bad param format');
207:
          }
208:
          break;
209:
 
210:
        default:
211:
          throw new Error('Invalid substate');
212:
        }
213:
        break;
214:
 
215:
      default:
216:
        throw new Error('Invalid substate');
217:
      }
218:
 
219:
    }
220:
 
221:
    if (!parsed.params.headers || parsed.params.headers === '') {
222:
      if (request.headers['x-date']) {
223:
        parsed.params.headers = ['x-date'];
224:
      } else {
225:
        parsed.params.headers = ['date'];
226:
      }
227:
    } else {
228:
      parsed.params.headers = parsed.params.headers.split(' ');
229:
    }
230:
 
231:
    // Minimally validate the parsed object
232:
    if (!parsed.scheme || parsed.scheme !== 'Signature')
233:
      throw new InvalidHeaderError('scheme was not "Signature"');
234:
 
235:
    if (!parsed.params.keyId)
236:
      throw new InvalidHeaderError('keyId was not specified');
237:
 
238:
    if (!parsed.params.algorithm)
239:
      throw new InvalidHeaderError('algorithm was not specified');
240:
 
241:
    if (!parsed.params.signature)
242:
      throw new InvalidHeaderError('signature was not specified');
243:
 
244:
    // Check the algorithm against the official list
245:
    parsed.params.algorithm = parsed.params.algorithm.toLowerCase();
246:
    if (!Algorithms[parsed.params.algorithm])
247:
      throw new InvalidParamsError(parsed.params.algorithm +
248:
                                   ' is not supported');
249:
 
250:
    // Build the signingString
251:
    for (i = 0; i < parsed.params.headers.length; i++) {
252:
      var h = parsed.params.headers[i].toLowerCase();
253:
      parsed.params.headers[i] = h;
254:
 
255:
      if (h !== 'request-line') {
256:
        var value = request.headers[h];
257:
        if (!value)
258:
          throw new MissingHeaderError(h + ' was not in the request');
259:
        parsed.signingString += h + ': ' + value;
260:
      } else {
261:
        parsed.signingString +=
262:
          request.method + ' ' + request.url + ' HTTP/' + request.httpVersion;
263:
      }
264:
 
265:
      if ((i + 1) < parsed.params.headers.length)
266:
        parsed.signingString += '\n';
267:
    }
268:
 
269:
    // Check against the constraints
270:
    var date;
271:
    if (request.headers.date || request.headers['x-date']) {
272:
        if (request.headers['x-date']) {
273:
          date = new Date(request.headers['x-date']);
274:
        } else {
275:
          date = new Date(request.headers.date);
276:
        }
277:
      var now = new Date();
278:
      var skew = Math.abs(now.getTime() - date.getTime());
279:
 
280:
      if (skew > options.clockSkew * 1000) {
281:
        throw new ExpiredRequestError('clock skew of ' +
282:
                                      (skew / 1000) +
283:
                                      's was greater than ' +
284:
                                      options.clockSkew + 's');
285:
      }
286:
    }
287:
 
288:
    options.headers.forEach(function (hdr) {
289:
      // Remember that we already checked any headers in the params
290:
      // were in the request, so if this passes we're good.
291:
      if (parsed.params.headers.indexOf(hdr) < 0)
292:
        throw new MissingHeaderError(hdr + ' was not a signed header');
293:
    });
294:
 
295:
    if (options.algorithms) {
296:
      if (options.algorithms.indexOf(parsed.params.algorithm) === -1)
297:
        throw new InvalidParamsError(parsed.params.algorithm +
298:
                                     ' is not a supported algorithm');
299:
    }
300:
 
301:
    return parsed;
302:
  }
303:
 
304:
};