216 lines
6.6 KiB
JavaScript
216 lines
6.6 KiB
JavaScript
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() && this.serviceName === 's3' && !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;
|