-
-
Save bendavis78/a63e94afff636a69f71f to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* 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