/* * Copyright 2010-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ var apiGateway = apiGateway || {}; apiGateway.core = apiGateway.core || {}; apiGateway.core.sigV4ClientFactory = {}; apiGateway.core.sigV4ClientFactory.newClient = function (config) { 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'; 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 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 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] + '=' + fixedEncodeURIComponent(queryParams[sortedQueryParams[i]]) + '&'; } return canonicalQueryString.substr(0, canonicalQueryString.length - 1); } function fixedEncodeURIComponent (str) { return encodeURIComponent(str).replace(/[!'()*]/g, function(c) { return '%' + c.charCodeAt(0).toString(16).toUpperCase(); }); } 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 buildStringToSign(datetime, credentialScope, hashedCanonicalRequest) { return AWS_SHA_256 + '\n' + datetime + '\n' + credentialScope + '\n' + hashedCanonicalRequest; } function buildCredentialScope(datetime, region, service) { return datetime.substr(0, 8) + '/' + region + '/' + service + '/' + AWS4_REQUEST } function calculateSigningKey(secretKey, datetime, region, service) { return hmac(hmac(hmac(hmac(AWS4 + secretKey, datetime.substr(0, 8)), 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 awsSigV4Client = { }; if(config.accessKey === undefined || config.secretKey === undefined) { return awsSigV4Client; } awsSigV4Client.accessKey = apiGateway.core.utils.assertDefined(config.accessKey, 'accessKey'); awsSigV4Client.secretKey = apiGateway.core.utils.assertDefined(config.secretKey, 'secretKey'); awsSigV4Client.sessionToken = config.sessionToken; awsSigV4Client.serviceName = apiGateway.core.utils.assertDefined(config.serviceName, 'serviceName'); awsSigV4Client.region = apiGateway.core.utils.assertDefined(config.region, 'region'); awsSigV4Client.endpoint = apiGateway.core.utils.assertDefined(config.endpoint, 'endpoint'); awsSigV4Client.makeRequest = function (request) { var verb = apiGateway.core.utils.assertDefined(request.verb, 'verb'); var path = apiGateway.core.utils.assertDefined(request.path, 'path'); var queryParams = apiGateway.core.utils.copy(request.queryParams); if (queryParams === undefined) { queryParams = {}; } var headers = apiGateway.core.utils.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'] = config.defaultContentType; } //If the user has not specified an override for Accept type the use default if(headers['Accept'] === undefined) { headers['Accept'] = config.defaultAcceptType; } var body = apiGateway.core.utils.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 datetime = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z').replace(/[:\-]|\.\d{3}/g, ''); headers[X_AMZ_DATE] = datetime; var parser = document.createElement('a'); parser.href = awsSigV4Client.endpoint; headers[HOST] = parser.hostname; var canonicalRequest = buildCanonicalRequest(verb, path, queryParams, headers, body); var hashedCanonicalRequest = hashCanonicalRequest(canonicalRequest); var credentialScope = buildCredentialScope(datetime, awsSigV4Client.region, awsSigV4Client.serviceName); var stringToSign = buildStringToSign(datetime, credentialScope, hashedCanonicalRequest); var signingKey = calculateSigningKey(awsSigV4Client.secretKey, datetime, awsSigV4Client.region, awsSigV4Client.serviceName); var signature = calculateSignature(signingKey, stringToSign); headers[AUTHORIZATION] = buildAuthorizationHeader(awsSigV4Client.accessKey, credentialScope, headers, signature); if(awsSigV4Client.sessionToken !== undefined && awsSigV4Client.sessionToken !== '') { headers[X_AMZ_SECURITY_TOKEN] = awsSigV4Client.sessionToken; } delete headers[HOST]; var url = config.endpoint + path; var queryString = buildCanonicalQueryString(queryParams); if (queryString != '') { url += '?' + queryString; } //Need to re-attach Content-Type if it is not specified at this point if(headers['Content-Type'] === undefined) { headers['Content-Type'] = config.defaultContentType; } var signedRequest = { method: verb, url: url, headers: headers, data: body }; return axios(signedRequest); }; return awsSigV4Client; };