123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- var AWS = require('../core');
- var v4Credentials = require('./v4_credentials');
- var inherit = AWS.util.inherit;
- /**
- * @api private
- */
- var expiresHeader = 'presigned-expires';
- /**
- * @api private
- */
- AWS.Signers.V4 = inherit(AWS.Signers.RequestSigner, {
- constructor: function V4(request, serviceName, options) {
- AWS.Signers.RequestSigner.call(this, request);
- this.serviceName = serviceName;
- options = options || {};
- this.signatureCache = typeof options.signatureCache === 'boolean' ? options.signatureCache : true;
- this.operation = options.operation;
- this.signatureVersion = options.signatureVersion;
- },
- algorithm: 'AWS4-HMAC-SHA256',
- addAuthorization: function addAuthorization(credentials, date) {
- var datetime = AWS.util.date.iso8601(date).replace(/[:\-]|\.\d{3}/g, '');
- if (this.isPresigned()) {
- this.updateForPresigned(credentials, datetime);
- } else {
- this.addHeaders(credentials, datetime);
- }
- this.request.headers['Authorization'] =
- this.authorization(credentials, datetime);
- },
- addHeaders: function addHeaders(credentials, datetime) {
- this.request.headers['X-Amz-Date'] = datetime;
- if (credentials.sessionToken) {
- this.request.headers['x-amz-security-token'] = credentials.sessionToken;
- }
- },
- updateForPresigned: function updateForPresigned(credentials, datetime) {
- var credString = this.credentialString(datetime);
- var qs = {
- 'X-Amz-Date': datetime,
- 'X-Amz-Algorithm': this.algorithm,
- 'X-Amz-Credential': credentials.accessKeyId + '/' + credString,
- 'X-Amz-Expires': this.request.headers[expiresHeader],
- 'X-Amz-SignedHeaders': this.signedHeaders()
- };
- if (credentials.sessionToken) {
- qs['X-Amz-Security-Token'] = credentials.sessionToken;
- }
- if (this.request.headers['Content-Type']) {
- qs['Content-Type'] = this.request.headers['Content-Type'];
- }
- if (this.request.headers['Content-MD5']) {
- qs['Content-MD5'] = this.request.headers['Content-MD5'];
- }
- if (this.request.headers['Cache-Control']) {
- qs['Cache-Control'] = this.request.headers['Cache-Control'];
- }
- // need to pull in any other X-Amz-* headers
- AWS.util.each.call(this, this.request.headers, function(key, value) {
- if (key === expiresHeader) return;
- if (this.isSignableHeader(key)) {
- var lowerKey = key.toLowerCase();
- // Metadata should be normalized
- if (lowerKey.indexOf('x-amz-meta-') === 0) {
- qs[lowerKey] = value;
- } else if (lowerKey.indexOf('x-amz-') === 0) {
- qs[key] = value;
- }
- }
- });
- var sep = this.request.path.indexOf('?') >= 0 ? '&' : '?';
- this.request.path += sep + AWS.util.queryParamsToString(qs);
- },
- authorization: function authorization(credentials, datetime) {
- var parts = [];
- var credString = this.credentialString(datetime);
- parts.push(this.algorithm + ' Credential=' +
- credentials.accessKeyId + '/' + credString);
- parts.push('SignedHeaders=' + this.signedHeaders());
- parts.push('Signature=' + this.signature(credentials, datetime));
- return parts.join(', ');
- },
- signature: function signature(credentials, datetime) {
- var signingKey = v4Credentials.getSigningKey(
- credentials,
- datetime.substr(0, 8),
- this.request.region,
- this.serviceName,
- this.signatureCache
- );
- return AWS.util.crypto.hmac(signingKey, this.stringToSign(datetime), 'hex');
- },
- stringToSign: function stringToSign(datetime) {
- var parts = [];
- parts.push('AWS4-HMAC-SHA256');
- parts.push(datetime);
- parts.push(this.credentialString(datetime));
- parts.push(this.hexEncodedHash(this.canonicalString()));
- return parts.join('\n');
- },
- canonicalString: function canonicalString() {
- var parts = [], pathname = this.request.pathname();
- if (this.serviceName !== 's3' && this.signatureVersion !== 's3v4') pathname = AWS.util.uriEscapePath(pathname);
- parts.push(this.request.method);
- parts.push(pathname);
- parts.push(this.request.search());
- parts.push(this.canonicalHeaders() + '\n');
- parts.push(this.signedHeaders());
- parts.push(this.hexEncodedBodyHash());
- return parts.join('\n');
- },
- canonicalHeaders: function canonicalHeaders() {
- var headers = [];
- AWS.util.each.call(this, this.request.headers, function (key, item) {
- headers.push([key, item]);
- });
- headers.sort(function (a, b) {
- return a[0].toLowerCase() < b[0].toLowerCase() ? -1 : 1;
- });
- var parts = [];
- AWS.util.arrayEach.call(this, headers, function (item) {
- var key = item[0].toLowerCase();
- if (this.isSignableHeader(key)) {
- var value = item[1];
- if (typeof value === 'undefined' || value === null || typeof value.toString !== 'function') {
- throw AWS.util.error(new Error('Header ' + key + ' contains invalid value'), {
- code: 'InvalidHeader'
- });
- }
- parts.push(key + ':' +
- this.canonicalHeaderValues(value.toString()));
- }
- });
- return parts.join('\n');
- },
- canonicalHeaderValues: function canonicalHeaderValues(values) {
- return values.replace(/\s+/g, ' ').replace(/^\s+|\s+$/g, '');
- },
- signedHeaders: function signedHeaders() {
- var keys = [];
- AWS.util.each.call(this, this.request.headers, function (key) {
- key = key.toLowerCase();
- if (this.isSignableHeader(key)) keys.push(key);
- });
- return keys.sort().join(';');
- },
- credentialString: function credentialString(datetime) {
- return v4Credentials.createScope(
- datetime.substr(0, 8),
- this.request.region,
- this.serviceName
- );
- },
- hexEncodedHash: function hash(string) {
- return AWS.util.crypto.sha256(string, 'hex');
- },
- hexEncodedBodyHash: function hexEncodedBodyHash() {
- var request = this.request;
- if (this.isPresigned() && (['s3', 's3-object-lambda'].indexOf(this.serviceName) > -1) && !request.body) {
- return 'UNSIGNED-PAYLOAD';
- } else if (request.headers['X-Amz-Content-Sha256']) {
- return request.headers['X-Amz-Content-Sha256'];
- } else {
- return this.hexEncodedHash(this.request.body || '');
- }
- },
- unsignableHeaders: [
- 'authorization',
- 'content-type',
- 'content-length',
- 'user-agent',
- expiresHeader,
- 'expect',
- 'x-amzn-trace-id'
- ],
- isSignableHeader: function isSignableHeader(key) {
- if (key.toLowerCase().indexOf('x-amz-') === 0) return true;
- return this.unsignableHeaders.indexOf(key) < 0;
- },
- isPresigned: function isPresigned() {
- return this.request.headers[expiresHeader] ? true : false;
- }
- });
- /**
- * @api private
- */
- module.exports = AWS.Signers.V4;
|