|
// @see https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html |
|
var AWS = AWS || {}; // Create the namespace |
|
|
|
AWS.SignatureV4 = { |
|
/** |
|
* Sign the request using AWS Signature Version 4 |
|
* @param {Object} opts - Request options containing method, host, path, region, and service |
|
* @param {Object} credentials - AWS credentials containing accessKeyId and secretAccessKey |
|
*/ |
|
sign: function(opts, credentials) { |
|
const payload = ""; // Empty payload for GET request (signing is required for any method) |
|
const now = new Date(); |
|
const amzDate = Utilities.formatDate(now, "UTC", "yyyyMMdd'T'HHmmss'Z'"); // Full timestamp for x-amz-date |
|
const dateStamp = Utilities.formatDate(now, "UTC", "yyyyMMdd"); // Date in YYYYMMDD format for signing key |
|
|
|
// Step 1: Create the canonical request |
|
const canonicalUri = opts.path; // URI encoded path (e.g., /v1/my-api) |
|
const canonicalQuerystring = ""; // No query parameters for this request |
|
const canonicalHeaders = |
|
"host:" + opts.host + "\n" + "x-amz-date:" + amzDate + "\n"; // Required headers: Host and x-amz-date |
|
const signedHeaders = "host;x-amz-date"; // Headers that are part of the signature |
|
const payloadHash = sha256(payload); // SHA-256 hash of the request payload |
|
|
|
// The canonical request combines method, URI, query string, headers, signed headers, and payload hash |
|
const canonicalRequest = [ |
|
opts.method, // HTTP Verb: GET, POST, PUT, etc. |
|
canonicalUri, // Resource path, e.g., /v1/my-path/ |
|
canonicalQuerystring, // Query string (none in this case) |
|
canonicalHeaders, // Canonical headers |
|
signedHeaders, // Headers being signed |
|
payloadHash, // SHA256 hash of the payload (empty in case of GET) |
|
].join("\n"); |
|
|
|
// Step 2: Create the string to sign |
|
const algorithm = "AWS4-HMAC-SHA256"; // Hashing algorithm |
|
const credentialScope = |
|
dateStamp + "/" + opts.region + "/" + opts.service + "/aws4_request"; // Credential scope (date, region, service, AWS4 request) |
|
const canonicalRequestHash = sha256(canonicalRequest); // Hash of the canonical request |
|
|
|
// String to sign includes the algorithm, request time, scope, and hash of the canonical request |
|
const stringToSign = [ |
|
algorithm, |
|
amzDate, |
|
credentialScope, |
|
canonicalRequestHash, |
|
].join("\n"); |
|
|
|
// Step 3: Calculate the signature |
|
const signature = getSignatureKey( |
|
credentials.secretAccessKey, // Secret access key |
|
dateStamp, // Date stamp (YYYYMMDD) |
|
opts.region, // AWS region (e.g., ap-southeast-2) |
|
opts.service, // AWS service (e.g., execute-api) |
|
stringToSign // String to be signed |
|
); |
|
|
|
// Step 4: Add the signature to the request headers |
|
const authorizationHeader = [ |
|
`${algorithm} Credential=` + credentials.accessKeyId + "/" + credentialScope, // Credential string |
|
"SignedHeaders=" + signedHeaders, // Signed headers |
|
"Signature=" + signature, // The calculated signature |
|
].join(", "); |
|
|
|
// Adding necessary headers to the request options |
|
const headers = { |
|
"X-Amz-Date": amzDate, // x-amz-date header with the current timestamp |
|
Authorization: authorizationHeader, // Authorization header with the signature |
|
}; |
|
|
|
opts.headers = headers; // Update the request with signed headers |
|
} |
|
}; |
|
|
|
/** |
|
* Utility function to compute the SHA-256 hash of a given input |
|
* @param {string} input - The input string |
|
* @returns {string} - The hex-encoded hash of the input |
|
*/ |
|
function sha256(input) { |
|
const bytes = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, input); |
|
return bytesToHex(bytes); // Convert the result to hexadecimal |
|
} |
|
|
|
/** |
|
* Utility function to calculate HMAC-SHA256 |
|
* @param {string} input - The data to sign |
|
* @param {string|ByteArray} key - The secret key used to sign the data |
|
* @returns {ByteArray} - The HMAC-SHA256 signature as byte array |
|
*/ |
|
function hmac(input, key) { |
|
return Utilities.computeHmacSha256Signature(input, key); // Generate HMAC-SHA256 signature |
|
} |
|
|
|
/** |
|
* Function to calculate the signing key for AWS Signature V4 |
|
* @param {string} secretKey - The AWS secret access key |
|
* @param {string} dateStamp - The date in YYYYMMDD format |
|
* @param {string} regionName - AWS region (e.g., ap-southeast-2) |
|
* @param {string} serviceName - AWS service name (e.g., execute-api) |
|
* @param {string} stringToSign - The string to be signed |
|
* @returns {string} - The signature as hex-encoded string |
|
*/ |
|
function getSignatureKey(secretKey, dateStamp, regionName, serviceName, stringToSign) { |
|
const kDate = hmac(dateStamp, "AWS4" + secretKey); // HMAC of the date stamp |
|
const kRegion = hmac(Utilities.newBlob(regionName).getBytes(), kDate); // HMAC of the region |
|
const kService = hmac(Utilities.newBlob(serviceName).getBytes(), kRegion); // HMAC of the service |
|
const signingKey = hmac(Utilities.newBlob("aws4_request").getBytes(), kService); // HMAC of the "aws4_request" |
|
|
|
// Return the signature |
|
return bytesToHex(hmac(Utilities.newBlob(stringToSign).getBytes(), signingKey)); |
|
} |
|
|
|
/** |
|
* Utility function to convert a byte array to a hex-encoded string |
|
* @param {ByteArray} byteArray - The byte array to convert |
|
* @returns {string} - Hexadecimal representation of the byte array |
|
*/ |
|
function bytesToHex(byteArray) { |
|
return byteArray |
|
.map(function (byte) { |
|
return ("0" + (byte & 0xff).toString(16)).slice(-2); // Convert each byte to hex |
|
}) |
|
.join(""); |
|
} |