source: trip-planner-front/node_modules/http-signature/lib/parser.js@ 76712b2

Last change on this file since 76712b2 was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

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