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: | }; |
