trouble-in-terror-town/node_modules/http-signature/lib/parser.js
Mikolaj 2bbacbea09 did some more work on networking and removed EOS in favor of LRM
did some more work on networking and removed EOS in favor of Light Reflective Mirror
2022-05-31 15:04:31 +02:00

315 lines
9.6 KiB
JavaScript

// Copyright 2012 Joyent, Inc. All rights reserved.
var assert = require('assert-plus');
var util = require('util');
var utils = require('./utils');
///--- Globals
var 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 Errors
function 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 API
module.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.optionalFinite(options.clockSkew, 'options.clockSkew');
var authzHeaderName = options.authorizationHeaderName || 'authorization';
if (!request.headers[authzHeaderName]) {
throw new MissingHeaderError('no ' + authzHeaderName + ' 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: ''
};
var authz = request.headers[authzHeaderName];
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.toLowerCase()) < 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');
}
parsed.algorithm = parsed.params.algorithm.toUpperCase();
parsed.keyId = parsed.params.keyId;
return parsed;
}
};