Skip to content

Instantly share code, notes, and snippets.

@bageljp
Created July 30, 2016 08:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bageljp/cfeb9e43ba513cadf8c83af488bbc6b5 to your computer and use it in GitHub Desktop.
Save bageljp/cfeb9e43ba513cadf8c83af488bbc6b5 to your computer and use it in GitHub Desktop.
ApiGateway SDK encoding (RFC3986 Non-reserved character)
/*
* 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] + '=' + encodeURIComponent(queryParams[sortedQueryParams[i]])
.replace("*", "%2A")
.replace("!", "%21")
.replace("'", "%27")
.replace("(", "%28")
.replace(")", "%29") + '&';
}
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 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;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment