var AWS = require('../core'); var inherit = AWS.util.inherit; /** * @api private */ var cachedSecret = {}; /** * @api private */ AWS.Signers.V4 = inherit(AWS.Signers.RequestSigner, { constructor: function V4(request, serviceName) { AWS.Signers.RequestSigner.call(this, request); this.serviceName = serviceName; }, addAuthorization: function addAuthorization(credentials, date) { var datetime = AWS.util.date.iso8601(date).replace(/[:\-]|\.\d{3}/g, ''); this.addHeaders(credentials, datetime); this.updateBody(credentials); 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; } }, updateBody: function updateBody(credentials) { if (this.request.params) { this.request.params.AWSAccessKeyId = credentials.accessKeyId; if (credentials.sessionToken) { this.request.params.SecurityToken = credentials.sessionToken; } this.request.body = AWS.util.queryParamsToString(this.request.params); this.request.headers['Content-Length'] = this.request.body.length; } }, authorization: function authorization(credentials, datetime) { var parts = []; var credString = this.credentialString(datetime); parts.push('AWS4-HMAC-SHA256 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 cache = cachedSecret[this.serviceName]; var date = datetime.substr(0, 8); if (!cache || cache.akid !== credentials.accessKeyId || cache.region !== this.request.region || cache.date !== date) { var kSecret = credentials.secretAccessKey; var kDate = AWS.util.crypto.hmac('AWS4' + kSecret, date, 'buffer'); var kRegion = AWS.util.crypto.hmac(kDate, this.request.region, 'buffer'); var kService = AWS.util.crypto.hmac(kRegion, this.serviceName, 'buffer'); var kCredentials = AWS.util.crypto.hmac(kService, 'aws4_request', 'buffer'); cachedSecret[this.serviceName] = { region: this.request.region, date: date, key: kCredentials, akid: credentials.accessKeyId }; } var key = cachedSecret[this.serviceName].key; return AWS.util.crypto.hmac(key, 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 = []; parts.push(this.request.method); parts.push(this.request.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)) { parts.push(key + ':' + this.canonicalHeaderValues(item[1].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) { var parts = []; parts.push(datetime.substr(0, 8)); parts.push(this.request.region); parts.push(this.serviceName); parts.push('aws4_request'); return parts.join('/'); }, hexEncodedHash: function hash(string) { return AWS.util.crypto.sha256(string, 'hex'); }, hexEncodedBodyHash: function hexEncodedBodyHash() { if (this.request.headers['X-Amz-Content-Sha256']) { return this.request.headers['X-Amz-Content-Sha256']; } else { return this.hexEncodedHash(this.request.body || ''); } }, unsignableHeaders: ['authorization', 'content-type', 'user-agent', 'x-amz-user-agent', 'x-amz-content-sha256'], isSignableHeader: function isSignableHeader(key) { return this.unsignableHeaders.indexOf(key) < 0; } }); module.exports = AWS.Signers.V4;