Home Reference Source Repository

src/index.js

import {hash, hmac, normalize} from 'deps';

/** @function
 * Computes the hash
 *
 * @name hash
 * @param {!string} data - The data to hash
 * @returns {Promise<string>} - The hashed output
 */
export {hash};

const algorithm = 'AWS4-HMAC-SHA256';
const emptyHash = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';

/**
 * Formats a Date object to an AWS date string
 *
 * @param {!Date} date - The date
 * @returns {string} - The formatted date string
 */
export function formatDateTime(date) {
	return date
		.toISOString()
		.replace(/-|:|(\.\d+)/g, '');
}

/**
 * Creates the canonical request
 * https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html
 *
 * @param {!string} httpRequestMethod - The HTTP request method (e.g. `GET` or `POST`)
 * @param {!string} canonicalURI - The canonical URI
 * @param {!string} canonicalQueryString - The canonical query string
 * @param {!string} canonicalHeaders - The canonical headers
 * @param {!string} signedHeaders - The signed headers
 * @param {!string} requestPayload - The payload of the request
 * @returns {Promise<string>} - The canonical request
 */
export async function buildCanonicalRequest(httpRequestMethod, canonicalURI, canonicalQueryString, canonicalHeaders,
                                            signedHeaders, requestPayload='') {
	return [
		httpRequestMethod,
		canonicalURI,
		canonicalQueryString,
		canonicalHeaders,
		'',
		signedHeaders,
		await (requestPayload ? hash(requestPayload) : emptyHash)
	].join('\n');
}

/**
 * Creates the string to sign
 * https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
 *
 * @param {!string} requestDate - The request date (`YYYMMDDThhmmssZ`)
 * @param {!string} credentialScope - the credential scope (formatted as `YYYYMMDD/region/service/aws4_request`)
 * @param {!string} hashedCanonicalRequest
 * @returns {string} - The string to sign
 */
export function buildStringToSign(requestDate, credentialScope, hashedCanonicalRequest) {
	return [
		algorithm,
		requestDate,
		credentialScope,
		hashedCanonicalRequest
	].join('\n');
}

/**
 * Pre-calculates the signing key
 * https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
 *
 * @param {!string} secretAccessKey - The secret access key for the AWS account
 * @param {!string} date - The date in YYYYMMDD format
 * @param {!string} region - The AWS region (e.g. `us-east-1`)
 * @param {!string} service - The AWS service (e.g. `iam`)
 * @returns {Promise<string>} - The pre-calculated signing key
 */
export async function preCalculateSigningKey(secretAccessKey, date, region, service) {
	return [
		date,
		region,
		service,
		'aws4_request'
	].reduce(
		async (acc, val) => hmac(await acc, val),
		`AWS4${secretAccessKey}`
	);
}

/**
 * Pre-calculates the signature
 * https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
 *
 * @param {!string} signingKey - The pre-calculated signing key
 * @param {!string} stringToSign - The string to sign
 * @returns {Promise<string>} - The pre-calculated signature
 */
export function preCalculatedSign(signingKey, stringToSign) {
	return hmac(signingKey, stringToSign, 'hex');
}

/**
 * Calculates the signature
 * https://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html
 *
 * @param {!string} secretAccessKey - The secret access key for the AWS account
 * @param {!string} date - The date in YYYYMMDD format
 * @param {!string} region - The AWS region (e.g. `us-east-1`)
 * @param {!string} service - The AWS service (e.g. `iam`)
 * @param {!string} stringToSign - The string to sign
 * @returns {Promise<string>} - The signature
 */
export async function sign(secretAccessKey, date, region, service, stringToSign) {
	const signingKey = await preCalculateSigningKey(secretAccessKey, date, region, service);

	return preCalculatedSign(signingKey, stringToSign);
}

/**
 * Creates the authorization string
 * https://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html
 *
 * @param {!string} accessKeyId
 * @param {!string} credentialScope
 * @param {!string} signedHeaders
 * @param {!string} signature
 * @returns {string}
 */
export function buildAuthorization(accessKeyId, credentialScope, signedHeaders, signature) {
	return algorithm + ' ' + [
		['Credential', `${accessKeyId}/${credentialScope}`],
		['SignedHeaders', signedHeaders],
		['Signature', signature]
	]
		.map(item => item.join('='))
		.join(', ');
}