161 lines
8.7 KiB
JavaScript
161 lines
8.7 KiB
JavaScript
"use strict";
|
||
Object.defineProperty(exports, "__esModule", { value: true });
|
||
exports.aws4Sign = aws4Sign;
|
||
const bson_1 = require("../../bson");
|
||
/**
|
||
* Calculates the SHA-256 hash of a string.
|
||
*
|
||
* @param str - String to hash.
|
||
* @returns Hexadecimal representation of the hash.
|
||
*/
|
||
const getHexSha256 = async (str) => {
|
||
const data = stringToBuffer(str);
|
||
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
||
const hashHex = bson_1.ByteUtils.toHex(new Uint8Array(hashBuffer));
|
||
return hashHex;
|
||
};
|
||
/**
|
||
* Calculates the HMAC-SHA256 of a string using the provided key.
|
||
* @param key - Key to use for HMAC calculation. Can be a string or Uint8Array.
|
||
* @param str - String to calculate HMAC for.
|
||
* @returns Uint8Array containing the HMAC-SHA256 digest.
|
||
*/
|
||
const getHmacSha256 = async (key, str) => {
|
||
let keyData;
|
||
if (typeof key === 'string') {
|
||
keyData = stringToBuffer(key);
|
||
}
|
||
else {
|
||
keyData = key;
|
||
}
|
||
const importedKey = await crypto.subtle.importKey('raw', keyData, { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign']);
|
||
const strData = stringToBuffer(str);
|
||
const signature = await crypto.subtle.sign('HMAC', importedKey, strData);
|
||
const digest = new Uint8Array(signature);
|
||
return digest;
|
||
};
|
||
/**
|
||
* Converts header values according to AWS requirements,
|
||
* From https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv-create-signed-request.html#create-canonical-request
|
||
* For values, you must:
|
||
- trim any leading or trailing spaces.
|
||
- convert sequential spaces to a single space.
|
||
* @param value - Header value to convert.
|
||
* @returns - Converted header value.
|
||
*/
|
||
const convertHeaderValue = (value) => {
|
||
return value.toString().trim().replace(/\s+/g, ' ');
|
||
};
|
||
/**
|
||
* Returns a Uint8Array representation of a string, encoded in UTF-8.
|
||
* @param str - String to convert.
|
||
* @returns Uint8Array containing the UTF-8 encoded string.
|
||
*/
|
||
function stringToBuffer(str) {
|
||
const data = new Uint8Array(bson_1.ByteUtils.utf8ByteLength(str));
|
||
bson_1.ByteUtils.encodeUTF8Into(data, str, 0);
|
||
return data;
|
||
}
|
||
/**
|
||
* This method implements AWS Signature 4 logic for a very specific request format.
|
||
* The signing logic is described here: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv-create-signed-request.html
|
||
*/
|
||
async function aws4Sign(options, credentials) {
|
||
/**
|
||
* From the spec: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv-create-signed-request.html
|
||
*
|
||
* Summary of signing steps
|
||
* 1. Create a canonical request
|
||
* Arrange the contents of your request (host, action, headers, etc.) into a standard canonical format. The canonical request is one of the inputs used to create the string to sign.
|
||
* 2. Create a hash of the canonical request
|
||
* Hash the canonical request using the same algorithm that you used to create the hash of the payload. The hash of the canonical request is a string of lowercase hexadecimal characters.
|
||
* 3. Create a string to sign
|
||
* Create a string to sign with the canonical request and extra information such as the algorithm, request date, credential scope, and the hash of the canonical request.
|
||
* 4. Derive a signing key
|
||
* Use the secret access key to derive the key used to sign the request.
|
||
* 5. Calculate the signature
|
||
* Perform a keyed hash operation on the string to sign using the derived signing key as the hash key.
|
||
* 6. Add the signature to the request
|
||
* Add the calculated signature to an HTTP header or to the query string of the request.
|
||
*/
|
||
// 1: Create a canonical request
|
||
// Date – The date and time used to sign the request.
|
||
const date = options.date;
|
||
// RequestDateTime – The date and time used in the credential scope. This value is the current UTC time in ISO 8601 format (for example, 20130524T000000Z).
|
||
const requestDateTime = date.toISOString().replace(/[:-]|\.\d{3}/g, '');
|
||
// RequestDate – The date used in the credential scope. This value is the current UTC date in YYYYMMDD format (for example, 20130524).
|
||
const requestDate = requestDateTime.substring(0, 8);
|
||
// Method – The HTTP request method. For us, this is always 'POST'.
|
||
const method = options.method;
|
||
// CanonicalUri – The URI-encoded version of the absolute path component URI, starting with the / that follows the domain name and up to the end of the string
|
||
// For our requests, this is always '/'
|
||
const canonicalUri = options.path;
|
||
// CanonicalQueryString – The URI-encoded query string parameters. For our requests, there are no query string parameters, so this is always an empty string.
|
||
const canonicalQuerystring = '';
|
||
// CanonicalHeaders – A list of request headers with their values. Individual header name and value pairs are separated by the newline character ("\n").
|
||
// All of our known/expected headers are included here, there are no extra headers.
|
||
const headers = new Headers({
|
||
'content-length': convertHeaderValue(options.headers['Content-Length']),
|
||
'content-type': convertHeaderValue(options.headers['Content-Type']),
|
||
host: convertHeaderValue(options.host),
|
||
'x-amz-date': convertHeaderValue(requestDateTime),
|
||
'x-mongodb-gs2-cb-flag': convertHeaderValue(options.headers['X-MongoDB-GS2-CB-Flag']),
|
||
'x-mongodb-server-nonce': convertHeaderValue(options.headers['X-MongoDB-Server-Nonce'])
|
||
});
|
||
// If session token is provided, include it in the headers
|
||
if ('sessionToken' in credentials && credentials.sessionToken) {
|
||
headers.append('x-amz-security-token', convertHeaderValue(credentials.sessionToken));
|
||
}
|
||
// Canonical headers are lowercased and sorted.
|
||
const canonicalHeaders = Array.from(headers.entries())
|
||
.map(([key, value]) => `${key.toLowerCase()}:${value}`)
|
||
.sort()
|
||
.join('\n');
|
||
const canonicalHeaderNames = Array.from(headers.keys()).map(header => header.toLowerCase());
|
||
// SignedHeaders – An alphabetically sorted, semicolon-separated list of lowercase request header names.
|
||
const signedHeaders = canonicalHeaderNames.sort().join(';');
|
||
// HashedPayload – A string created using the payload in the body of the HTTP request as input to a hash function. This string uses lowercase hexadecimal characters.
|
||
const hashedPayload = await getHexSha256(options.body);
|
||
// CanonicalRequest – A string that includes the above elements, separated by newline characters.
|
||
const canonicalRequest = [
|
||
method,
|
||
canonicalUri,
|
||
canonicalQuerystring,
|
||
canonicalHeaders + '\n',
|
||
signedHeaders,
|
||
hashedPayload
|
||
].join('\n');
|
||
// 2. Create a hash of the canonical request
|
||
// HashedCanonicalRequest – A string created by using the canonical request as input to a hash function.
|
||
const hashedCanonicalRequest = await getHexSha256(canonicalRequest);
|
||
// 3. Create a string to sign
|
||
// Algorithm – The algorithm used to create the hash of the canonical request. For SigV4, use AWS4-HMAC-SHA256.
|
||
const algorithm = 'AWS4-HMAC-SHA256';
|
||
// CredentialScope – The credential scope, which restricts the resulting signature to the specified Region and service.
|
||
// Has the following format: YYYYMMDD/region/service/aws4_request.
|
||
const credentialScope = `${requestDate}/${options.region}/${options.service}/aws4_request`;
|
||
// StringToSign – A string that includes the above elements, separated by newline characters.
|
||
const stringToSign = [algorithm, requestDateTime, credentialScope, hashedCanonicalRequest].join('\n');
|
||
// 4. Derive a signing key
|
||
// To derive a signing key for SigV4, perform a succession of keyed hash operations (HMAC) on the request date, Region, and service, with your AWS secret access key as the key for the initial hashing operation.
|
||
const dateKey = await getHmacSha256('AWS4' + credentials.secretAccessKey, requestDate);
|
||
const dateRegionKey = await getHmacSha256(dateKey, options.region);
|
||
const dateRegionServiceKey = await getHmacSha256(dateRegionKey, options.service);
|
||
const signingKey = await getHmacSha256(dateRegionServiceKey, 'aws4_request');
|
||
// 5. Calculate the signature
|
||
const signatureBuffer = await getHmacSha256(signingKey, stringToSign);
|
||
const signature = bson_1.ByteUtils.toHex(signatureBuffer);
|
||
// 6. Add the signature to the request
|
||
// Calculate the Authorization header
|
||
const authorizationHeader = [
|
||
'AWS4-HMAC-SHA256 Credential=' + credentials.accessKeyId + '/' + credentialScope,
|
||
'SignedHeaders=' + signedHeaders,
|
||
'Signature=' + signature
|
||
].join(', ');
|
||
// Return the calculated headers
|
||
return {
|
||
Authorization: authorizationHeader,
|
||
'X-Amz-Date': requestDateTime
|
||
};
|
||
}
|
||
//# sourceMappingURL=aws4.js.map
|