Skip to content

Instantly share code, notes, and snippets.

@bendavis78
Created January 13, 2016 18:40
Show Gist options
  • Select an option

  • Save bendavis78/a63e94afff636a69f71f to your computer and use it in GitHub Desktop.

Select an option

Save bendavis78/a63e94afff636a69f71f to your computer and use it in GitHub Desktop.
/* global CryptoJS */
(function() {
var AWS_SHA_256 = 'AWS4-HMAC-SHA256';
var AWS4_REQUEST = 'aws4_request';
var AWS4 = 'AWS4';
var X_AMZ_DATE = 'x-amz-date';
var X_AMZ_SECURITY_TOKEN = 'x-amz-security-token';
var HOST = 'host';
var AUTHORIZATION = 'Authorization';
var ACCEPT = 'Accept';
var CONTENT_TYPE = 'Content-Type';
function assertDefined(object, name) {
if (object === undefined) {
throw name + ' must be defined';
} else {
return object;
}
}
function copy(obj) {
if (null === obj || 'object' !== typeof obj) {
return obj;
}
var copyObj = obj.constructor();
for (var attr in obj) {
if (obj.hasOwnProperty(attr)) {
copyObj[attr] = obj[attr];
}
}
return copy;
}
function hash(value) {
return CryptoJS.SHA256(value);
}
function hexEncode(value) {
return value.toString(CryptoJS.enc.Hex);
}
function hmac(secret, value) {
return CryptoJS.HmacSHA256(value, secret, {asBytes: true});
}
function buildCanonicalUri(uri) {
return encodeURI(uri);
}
function buildCanonicalQueryString(queryParams) {
if (Object.keys(queryParams).length < 1) {
return '';
}
var sortedQueryParams = [];
for (var property in queryParams) {
if (queryParams.hasOwnProperty(property)) {
sortedQueryParams.push(property);
}
}
sortedQueryParams.sort();
var canonicalQueryString = '';
for (var i = 0; i < sortedQueryParams.length; i++) {
canonicalQueryString += sortedQueryParams[i] + '=' + encodeURIComponent(queryParams[sortedQueryParams[i]]) + '&';
}
return canonicalQueryString.substr(0, canonicalQueryString.length - 1);
}
function buildCanonicalHeaders(headers) {
var canonicalHeaders = '';
var sortedKeys = [];
for (var property in headers) {
if (headers.hasOwnProperty(property)) {
sortedKeys.push(property);
}
}
sortedKeys.sort();
for (var i = 0; i < sortedKeys.length; i++) {
canonicalHeaders += sortedKeys[i].toLowerCase() + ':' + headers[sortedKeys[i]] + '\n';
}
return canonicalHeaders;
}
function buildCanonicalSignedHeaders(headers) {
var sortedKeys = [];
for (var property in headers) {
if (headers.hasOwnProperty(property)) {
sortedKeys.push(property.toLowerCase());
}
}
sortedKeys.sort();
return sortedKeys.join(';');
}
function buildCanonicalRequest(method, path, queryParams, headers, payload) {
return method + '\n' +
buildCanonicalUri(path) + '\n' +
buildCanonicalQueryString(queryParams) + '\n' +
buildCanonicalHeaders(headers) + '\n' +
buildCanonicalSignedHeaders(headers) + '\n' +
hexEncode(hash(payload));
}
function hashCanonicalRequest(request) {
return hexEncode(hash(request));
}
function buildStringToSign(isoDateTime, credentialScope, hashedCanonicalRequest) {
return AWS_SHA_256 + '\n' +
isoDateTime + '\n' +
credentialScope + '\n' +
hashedCanonicalRequest;
}
function buildCredentialScope(isoDate, region, service) {
return isoDate + '/' + region + '/' + service + '/' + AWS4_REQUEST;
}
function calculateSigningKey(secretKey, isoDate, region, service) {
return hmac(hmac(hmac(hmac(AWS4 + secretKey, isoDate), region), service), AWS4_REQUEST);
}
function calculateSignature(key, stringToSign) {
return hexEncode(hmac(key, stringToSign));
}
function buildAuthorizationHeader(accessKey, credentialScope, headers, signature) {
return AWS_SHA_256 + ' Credential=' + accessKey + '/' + credentialScope + ', SignedHeaders=' + buildCanonicalSignedHeaders(headers) + ', Signature=' + signature;
}
var AWSSigV4RequestSigner = function(config) {
this.accessKey = assertDefined(config.accessKey, 'accessKey');
this.secretKey = assertDefined(config.secretKey, 'secretKey');
this.sessionToken = config.sessionToken;
this.serviceName = assertDefined(config.serviceName, 'serviceName');
this.region = assertDefined(config.region, 'region');
this.defaultContentType = config.defaultContentType || 'application/json';
this.defaultAcceptType = config.defaultAcceptType || 'application/json';
};
AWSSigV4RequestSigner.prototype.apply = function(request) {
if (this.accessKey === undefined || this.secretKey === undefined) {
return false;
}
var verb = assertDefined(request.method, 'verb');
var path = assertDefined(request.path, 'path');
var queryParams = assertDefined(request.queryParams, 'queryParams');
queryParams = copy(request.queryParams);
if (queryParams === undefined) {
queryParams = {};
}
var headers = copy(request.headers);
if (headers === undefined) {
headers = {};
}
//If the user has not specified an override for Content type the use default
if (headers[CONTENT_TYPE] === undefined) {
headers[CONTENT_TYPE] = this.defaultContentType;
}
//If the user has not specified an override for Accept type the use default
if (headers[ACCEPT] === undefined) {
headers[ACCEPT] = this.defaultAcceptType;
}
var body = copy(request.body);
if (body === undefined || verb === 'GET') { // override request body and set to empty when signing GET requests
body = '';
} else {
body = JSON.stringify(body);
}
//If there is no body remove the content-type header so it is not included in SigV4 calculation
if(body === '' || body === undefined || body === null) {
delete headers[CONTENT_TYPE];
}
var date = new Date();
var isoDateTime = date.toISOString();
var isoDate = isoDateTime.split('T')[0];
headers[X_AMZ_DATE] = isoDateTime;
var parser = document.createElement('a');
parser.href = request.url;
headers[HOST] = parser.hostname;
var canonicalRequest = buildCanonicalRequest(verb, path, queryParams, headers, body);
var hashedCanonicalRequest = hashCanonicalRequest(canonicalRequest);
var credentialScope = buildCredentialScope(isoDate, this.region, this.serviceName);
var stringToSign = buildStringToSign(isoDateTime, credentialScope, hashedCanonicalRequest);
var signingKey = calculateSigningKey(this.secretKey, date, this.region, this.serviceName);
var signature = calculateSignature(signingKey, stringToSign);
headers[AUTHORIZATION] = buildAuthorizationHeader(this.accessKey, credentialScope, headers, signature);
if(this.sessionToken !== undefined && this.sessionToken !== '') {
headers[X_AMZ_SECURITY_TOKEN] = this.sessionToken;
}
delete headers[HOST];
//Need to re-attach Content-Type if it is not specified at this point
if(headers['Content-Type'] === undefined) {
headers['Content-Type'] = this.defaultContentType;
}
for (var header in headers) {
request.headers[header] = headers[header];
}
return true;
};
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment