Skip to content

Instantly share code, notes, and snippets.

@andrewhodel
Last active October 13, 2020 17:59
Show Gist options
  • Save andrewhodel/87c3886e76b4b4b762d455e800e84f81 to your computer and use it in GitHub Desktop.
Save andrewhodel/87c3886e76b4b4b762d455e800e84f81 to your computer and use it in GitHub Desktop.
aws.js AWS library for Node.JS
var https = require('https');
var crypto = require('crypto');
var get_signature = function(secret_key, auth_date, region, service) {
// return the AWS version 4 signature
// by chaining through 4 different strings and creating a sha256 hash for each
// to generate a 256 bit key
//console.log('get_signature():', secret_key, auth_date, region, service);
var k_auth_date_hmac = crypto.createHmac('sha256', 'AWS4' + secret_key);
var k_auth_date = k_auth_date_hmac.update(auth_date);
var k_region_hmac = crypto.createHmac('sha256', k_auth_date.digest());
var k_region = k_region_hmac.update(region);
var k_service_hmac = crypto.createHmac('sha256', k_region.digest());
var k_service = k_service_hmac.update(service);
var k_signing_hmac = crypto.createHmac('sha256', k_service.digest());
var k_signing = k_signing_hmac.update('aws4_request');
return k_signing.digest();
}
var get_amz_date = function(date_str) {
date_str = String(date_str);
// convert an iso8601 date string to the format amazon wants
// get an iso8601 date string with: new Date().toISOString()
// YYYY-MM-DDTHH:mm:ss.sssZ (24 characters long, forget about the other format
if (date_str.length != 24) {
console.log('\n\nERROR: date string must be 24 characters long and is ' + date_str.length + ' characters long: ' + date_str);
process.exit(1);
}
// remove each character in chars from date_str
var chars = [':', '-'];
var c = 0;
while (c < chars.length) {
date_str = date_str.replace(new RegExp(chars[c], 'g'), '');
c++;
}
// remove everything after and including the first .
var t = date_str.split('.');
date_str = t[0];
// add a Z to the end of the string and return it
return date_str + 'Z';
}
exports.request = function(aws_r, cb, method='GET', service='ec2', host='ec2.amazonaws.com', region='us-east-1') {
// aws_r is an object that must have at least
// access_key
// secret_key
// request_parameters (only Version=2016-11-15 has been found to support tags)
// and may also include
// method
// service
// host
// region
// cb is a callback that returns: error, data, res
// cb = function(error, data, res)
var iso8601_date_string = new Date().toISOString();
var amz_date = get_amz_date(iso8601_date_string);
var auth_date = amz_date.split('T')[0];
//console.log('ISO8601 DATE: ' + iso8601_date_string);
//console.log('AMZ DATE: ' + amz_date);
//console.log('AUTH DATE: ' + auth_date);
var aws = {};
aws.method = 'GET';
aws.service = 'ec2';
aws.host = 'ec2.amazonaws.com';
aws.region = 'us-east-1';
aws.access_key = aws_r.access_key;
aws.secret_key = aws_r.secret_key;
// make the request_parameters string from the aws_r.request_parameters object and sort it as AWS requires
var request_parameters = '';
var setVersion = true;
var t = [];
for (r in aws_r.request_parameters) {
if (r == 'Version') {
setVersion = false;
}
t.push(r);
}
t.sort();
var c = 0;
while (c < t.length) {
request_parameters += t[c] + '=' + aws_r.request_parameters[t[c]] + '&';
c++;
}
request_parameters = request_parameters.slice(0, -1);
if (setVersion) {
request_parameters += '&Version=2016-11-15';
}
console.log('\nAWS REQUEST: ' + request_parameters);
// delete the request_parameters object from aws_r
delete aws_r.request_parameters;
// add the rest of the aws_r parameters to the aws object, overwriting the default settings if they are modified in aws_r
for (key in aws_r) {
aws[key] = aws_r[key];
}
// ************* TASK 1: CREATE A CANONICAL REQUEST *************
// http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
var canonical_uri = '/';
// Create the canonical query string. In this example (a GET request),
// request parameters are in the query string. Query string values must
// be URL-encoded (space=%20). The parameters must be sorted by name.
// For this example, the query string is pre-formatted in the request_parameters variable.
var canonical_querystring = request_parameters;
// Create the canonical headers and signed headers. Header names
// must be trimmed and lowercase, and sorted in code point order from
// low to high. Note that there is a trailing \n.
var canonical_headers = 'host:' + aws.host + '\n' + 'x-amz-date:' + amz_date + '\n';
// Create the list of signed headers. This lists the headers
// in the canonical_headers list, delimited with ";" and in alpha order.
// Note: The request can include any headers; canonical_headers and
// signed_headers lists those that you want to be included in the
// hash of the request. "Host" and "x-amz-date" are always required.
var signed_headers = 'host;x-amz-date';
// Create payload hash (hash of the request body content). For GET
// requests, the payload is an empty string ("").
var content = '';
var payload_hash = crypto.createHash('sha256');
payload_hash.update(content);
payload_hash = payload_hash.digest('hex');
// Combine elements to create canonical request
var canonical_request = aws.method + '\n' + canonical_uri + '\n' + canonical_querystring + '\n' + canonical_headers + '\n' + signed_headers + '\n' + payload_hash;
// ************* TASK 2: CREATE THE STRING TO SIGN*************
// Match the algorithm to the hashing algorithm you use, either SHA-1 or
// SHA-256 (recommended)
var algorithm = 'AWS4-HMAC-SHA256';
// Get a sha256 hash of the canonical_request
var canonical_request_hash = crypto.createHash('sha256');
canonical_request_hash.update(canonical_request);
canonical_request_hash = canonical_request_hash.digest('hex');
var credential_scope = auth_date + '/' + aws.region + '/' + aws.service + '/' + 'aws4_request';
var string_to_sign = algorithm + '\n' + amz_date + '\n' + credential_scope + '\n' + canonical_request_hash;
// ************* TASK 3: CALCULATE THE SIGNATURE *************
var signing_key = get_signature(aws.secret_key, auth_date, aws.region, aws.service);
// Sign the string_to_sign using the signing_key
var signature = crypto.createHmac('sha256', signing_key);
signature.update(string_to_sign);
signature = signature.digest('hex');
// ************* TASK 4: ADD SIGNING INFORMATION TO THE REQUEST *************
// The signing information can be either in a query string value or in
// a header named Authorization. This code shows how to use a header.
// Create authorization header and add to request headers
var authorization_header = algorithm + ' ' + 'Credential=' + aws.access_key + '/' + credential_scope + ', ' + 'SignedHeaders=' + signed_headers + ', ' + 'Signature=' + signature;
// The request can include any headers, but MUST include "host", "x-amz-date",
// and (for this scenario) "Authorization". "host" and "x-amz-date" must
// be included in the canonical_headers and signed_headers, as noted
// earlier. Order here is not significant.
// note: The 'host' header may be added automatically by the request code.
var headers = {'x-amz-date': amz_date, 'Authorization': authorization_header};
// ************* SEND THE REQUEST *************
var opts = {
hostname: aws.host,
port: 443,
path: '?' + canonical_querystring,
method: aws.method,
headers: headers
};
var req = https.request(opts, function(res) {
//console.log('HTTPS Response:');
//console.log('\tstatus code: ' + res.statusCode);
//console.log('\nheaders:', res.headers);
var data = Buffer.alloc(0);
res.on('data', function(d) {
data = Buffer.concat([data, d]);
});
res.on('end', function() {
cb(false, data, res);
});
});
req.on('error', function(e) {
cb(e);
});
// make the request
req.end();
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment