UEA-Prodem
This commit is contained in:
+125
@@ -0,0 +1,125 @@
|
||||
import { fromHex, fromUtf8, toHex } from "@smithy/core/serde";
|
||||
export class HeaderFormatter {
|
||||
format(headers) {
|
||||
const chunks = [];
|
||||
for (const headerName of Object.keys(headers)) {
|
||||
const bytes = fromUtf8(headerName);
|
||||
chunks.push(Uint8Array.from([bytes.byteLength]), bytes, this.formatHeaderValue(headers[headerName]));
|
||||
}
|
||||
const out = new Uint8Array(chunks.reduce((carry, bytes) => carry + bytes.byteLength, 0));
|
||||
let position = 0;
|
||||
for (const chunk of chunks) {
|
||||
out.set(chunk, position);
|
||||
position += chunk.byteLength;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
formatHeaderValue(header) {
|
||||
switch (header.type) {
|
||||
case "boolean":
|
||||
return Uint8Array.from([header.value ? 0 : 1]);
|
||||
case "byte":
|
||||
return Uint8Array.from([2, header.value]);
|
||||
case "short":
|
||||
const shortView = new DataView(new ArrayBuffer(3));
|
||||
shortView.setUint8(0, 3);
|
||||
shortView.setInt16(1, header.value, false);
|
||||
return new Uint8Array(shortView.buffer);
|
||||
case "integer":
|
||||
const intView = new DataView(new ArrayBuffer(5));
|
||||
intView.setUint8(0, 4);
|
||||
intView.setInt32(1, header.value, false);
|
||||
return new Uint8Array(intView.buffer);
|
||||
case "long":
|
||||
const longBytes = new Uint8Array(9);
|
||||
longBytes[0] = 5;
|
||||
longBytes.set(header.value.bytes, 1);
|
||||
return longBytes;
|
||||
case "binary":
|
||||
const binView = new DataView(new ArrayBuffer(3 + header.value.byteLength));
|
||||
binView.setUint8(0, 6);
|
||||
binView.setUint16(1, header.value.byteLength, false);
|
||||
const binBytes = new Uint8Array(binView.buffer);
|
||||
binBytes.set(header.value, 3);
|
||||
return binBytes;
|
||||
case "string":
|
||||
const utf8Bytes = fromUtf8(header.value);
|
||||
const strView = new DataView(new ArrayBuffer(3 + utf8Bytes.byteLength));
|
||||
strView.setUint8(0, 7);
|
||||
strView.setUint16(1, utf8Bytes.byteLength, false);
|
||||
const strBytes = new Uint8Array(strView.buffer);
|
||||
strBytes.set(utf8Bytes, 3);
|
||||
return strBytes;
|
||||
case "timestamp":
|
||||
const tsBytes = new Uint8Array(9);
|
||||
tsBytes[0] = 8;
|
||||
tsBytes.set(Int64.fromNumber(header.value.valueOf()).bytes, 1);
|
||||
return tsBytes;
|
||||
case "uuid":
|
||||
if (!UUID_PATTERN.test(header.value)) {
|
||||
throw new Error(`Invalid UUID received: ${header.value}`);
|
||||
}
|
||||
const uuidBytes = new Uint8Array(17);
|
||||
uuidBytes[0] = 9;
|
||||
uuidBytes.set(fromHex(header.value.replace(/\-/g, "")), 1);
|
||||
return uuidBytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
var HEADER_VALUE_TYPE;
|
||||
(function (HEADER_VALUE_TYPE) {
|
||||
HEADER_VALUE_TYPE[HEADER_VALUE_TYPE["boolTrue"] = 0] = "boolTrue";
|
||||
HEADER_VALUE_TYPE[HEADER_VALUE_TYPE["boolFalse"] = 1] = "boolFalse";
|
||||
HEADER_VALUE_TYPE[HEADER_VALUE_TYPE["byte"] = 2] = "byte";
|
||||
HEADER_VALUE_TYPE[HEADER_VALUE_TYPE["short"] = 3] = "short";
|
||||
HEADER_VALUE_TYPE[HEADER_VALUE_TYPE["integer"] = 4] = "integer";
|
||||
HEADER_VALUE_TYPE[HEADER_VALUE_TYPE["long"] = 5] = "long";
|
||||
HEADER_VALUE_TYPE[HEADER_VALUE_TYPE["byteArray"] = 6] = "byteArray";
|
||||
HEADER_VALUE_TYPE[HEADER_VALUE_TYPE["string"] = 7] = "string";
|
||||
HEADER_VALUE_TYPE[HEADER_VALUE_TYPE["timestamp"] = 8] = "timestamp";
|
||||
HEADER_VALUE_TYPE[HEADER_VALUE_TYPE["uuid"] = 9] = "uuid";
|
||||
})(HEADER_VALUE_TYPE || (HEADER_VALUE_TYPE = {}));
|
||||
const UUID_PATTERN = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
|
||||
export class Int64 {
|
||||
bytes;
|
||||
constructor(bytes) {
|
||||
this.bytes = bytes;
|
||||
if (bytes.byteLength !== 8) {
|
||||
throw new Error("Int64 buffers must be exactly 8 bytes");
|
||||
}
|
||||
}
|
||||
static fromNumber(number) {
|
||||
if (number > 9_223_372_036_854_775_807 || number < -9_223_372_036_854_775_808) {
|
||||
throw new Error(`${number} is too large (or, if negative, too small) to represent as an Int64`);
|
||||
}
|
||||
const bytes = new Uint8Array(8);
|
||||
for (let i = 7, remaining = Math.abs(Math.round(number)); i > -1 && remaining > 0; i--, remaining /= 256) {
|
||||
bytes[i] = remaining;
|
||||
}
|
||||
if (number < 0) {
|
||||
negate(bytes);
|
||||
}
|
||||
return new Int64(bytes);
|
||||
}
|
||||
valueOf() {
|
||||
const bytes = this.bytes.slice(0);
|
||||
const negative = bytes[0] & 0b10000000;
|
||||
if (negative) {
|
||||
negate(bytes);
|
||||
}
|
||||
return parseInt(toHex(bytes), 16) * (negative ? -1 : 1);
|
||||
}
|
||||
toString() {
|
||||
return String(this.valueOf());
|
||||
}
|
||||
}
|
||||
function negate(bytes) {
|
||||
for (let i = 0; i < 8; i++) {
|
||||
bytes[i] ^= 0xff;
|
||||
}
|
||||
for (let i = 7; i > -1; i--) {
|
||||
bytes[i]++;
|
||||
if (bytes[i] !== 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
import { toHex, toUint8Array } from "@smithy/core/serde";
|
||||
import { HeaderFormatter } from "./HeaderFormatter";
|
||||
import { SignatureV4Base } from "./SignatureV4Base";
|
||||
import { ALGORITHM_IDENTIFIER, ALGORITHM_QUERY_PARAM, AMZ_DATE_HEADER, AMZ_DATE_QUERY_PARAM, AUTH_HEADER, CREDENTIAL_QUERY_PARAM, EVENT_ALGORITHM_IDENTIFIER, EXPIRES_QUERY_PARAM, MAX_PRESIGNED_TTL, SHA256_HEADER, SIGNATURE_QUERY_PARAM, SIGNED_HEADERS_QUERY_PARAM, TOKEN_HEADER, TOKEN_QUERY_PARAM, } from "./constants";
|
||||
import { createScope, getSigningKey } from "./credentialDerivation";
|
||||
import { getCanonicalHeaders } from "./getCanonicalHeaders";
|
||||
import { getPayloadHash } from "./getPayloadHash";
|
||||
import { hasHeader } from "./headerUtil";
|
||||
import { moveHeadersToQuery } from "./moveHeadersToQuery";
|
||||
import { prepareRequest } from "./prepareRequest";
|
||||
export class SignatureV4 extends SignatureV4Base {
|
||||
headerFormatter = new HeaderFormatter();
|
||||
constructor({ applyChecksum, credentials, region, service, sha256, uriEscapePath = true, }) {
|
||||
super({
|
||||
applyChecksum,
|
||||
credentials,
|
||||
region,
|
||||
service,
|
||||
sha256,
|
||||
uriEscapePath,
|
||||
});
|
||||
}
|
||||
async presign(originalRequest, options = {}) {
|
||||
const { signingDate = new Date(), expiresIn = 3600, unsignableHeaders, unhoistableHeaders, signableHeaders, hoistableHeaders, signingRegion, signingService, } = options;
|
||||
const credentials = await this.credentialProvider();
|
||||
this.validateResolvedCredentials(credentials);
|
||||
const region = signingRegion ?? (await this.regionProvider());
|
||||
const { longDate, shortDate } = this.formatDate(signingDate);
|
||||
if (expiresIn > MAX_PRESIGNED_TTL) {
|
||||
return Promise.reject("Signature version 4 presigned URLs" + " must have an expiration date less than one week in" + " the future");
|
||||
}
|
||||
const scope = createScope(shortDate, region, signingService ?? this.service);
|
||||
const request = moveHeadersToQuery(prepareRequest(originalRequest), { unhoistableHeaders, hoistableHeaders });
|
||||
if (credentials.sessionToken) {
|
||||
request.query[TOKEN_QUERY_PARAM] = credentials.sessionToken;
|
||||
}
|
||||
request.query[ALGORITHM_QUERY_PARAM] = ALGORITHM_IDENTIFIER;
|
||||
request.query[CREDENTIAL_QUERY_PARAM] = `${credentials.accessKeyId}/${scope}`;
|
||||
request.query[AMZ_DATE_QUERY_PARAM] = longDate;
|
||||
request.query[EXPIRES_QUERY_PARAM] = expiresIn.toString(10);
|
||||
const canonicalHeaders = getCanonicalHeaders(request, unsignableHeaders, signableHeaders);
|
||||
request.query[SIGNED_HEADERS_QUERY_PARAM] = this.getCanonicalHeaderList(canonicalHeaders);
|
||||
request.query[SIGNATURE_QUERY_PARAM] = await this.getSignature(longDate, scope, this.getSigningKey(credentials, region, shortDate, signingService), this.createCanonicalRequest(request, canonicalHeaders, await getPayloadHash(originalRequest, this.sha256)));
|
||||
return request;
|
||||
}
|
||||
async sign(toSign, options) {
|
||||
if (typeof toSign === "string") {
|
||||
return this.signString(toSign, options);
|
||||
}
|
||||
else if (toSign.headers && toSign.payload) {
|
||||
return this.signEvent(toSign, options);
|
||||
}
|
||||
else if (toSign.message) {
|
||||
return this.signMessage(toSign, options);
|
||||
}
|
||||
else {
|
||||
return this.signRequest(toSign, options);
|
||||
}
|
||||
}
|
||||
async signEvent({ headers, payload }, { signingDate = new Date(), priorSignature, signingRegion, signingService, eventStreamCredentials, }) {
|
||||
const region = signingRegion ?? (await this.regionProvider());
|
||||
const { shortDate, longDate } = this.formatDate(signingDate);
|
||||
const scope = createScope(shortDate, region, signingService ?? this.service);
|
||||
const hashedPayload = await getPayloadHash({ headers: {}, body: payload }, this.sha256);
|
||||
const hash = new this.sha256();
|
||||
hash.update(headers);
|
||||
const hashedHeaders = toHex(await hash.digest());
|
||||
const stringToSign = [
|
||||
EVENT_ALGORITHM_IDENTIFIER,
|
||||
longDate,
|
||||
scope,
|
||||
priorSignature,
|
||||
hashedHeaders,
|
||||
hashedPayload,
|
||||
].join("\n");
|
||||
return this.signString(stringToSign, {
|
||||
signingDate,
|
||||
signingRegion: region,
|
||||
signingService,
|
||||
eventStreamCredentials,
|
||||
});
|
||||
}
|
||||
async signMessage(signableMessage, { signingDate = new Date(), signingRegion, signingService, eventStreamCredentials }) {
|
||||
const promise = this.signEvent({
|
||||
headers: this.headerFormatter.format(signableMessage.message.headers),
|
||||
payload: signableMessage.message.body,
|
||||
}, {
|
||||
signingDate,
|
||||
signingRegion,
|
||||
signingService,
|
||||
priorSignature: signableMessage.priorSignature,
|
||||
eventStreamCredentials,
|
||||
});
|
||||
return promise.then((signature) => {
|
||||
return { message: signableMessage.message, signature };
|
||||
});
|
||||
}
|
||||
async signString(stringToSign, { signingDate = new Date(), signingRegion, signingService, eventStreamCredentials, } = {}) {
|
||||
const credentials = eventStreamCredentials ?? (await this.credentialProvider());
|
||||
this.validateResolvedCredentials(credentials);
|
||||
const region = signingRegion ?? (await this.regionProvider());
|
||||
const { shortDate } = this.formatDate(signingDate);
|
||||
const hash = new this.sha256(await this.getSigningKey(credentials, region, shortDate, signingService));
|
||||
hash.update(toUint8Array(stringToSign));
|
||||
return toHex(await hash.digest());
|
||||
}
|
||||
async signRequest(requestToSign, { signingDate = new Date(), signableHeaders, unsignableHeaders, signingRegion, signingService, } = {}) {
|
||||
const credentials = await this.credentialProvider();
|
||||
this.validateResolvedCredentials(credentials);
|
||||
const region = signingRegion ?? (await this.regionProvider());
|
||||
const request = prepareRequest(requestToSign);
|
||||
const { longDate, shortDate } = this.formatDate(signingDate);
|
||||
const scope = createScope(shortDate, region, signingService ?? this.service);
|
||||
request.headers[AMZ_DATE_HEADER] = longDate;
|
||||
if (credentials.sessionToken) {
|
||||
request.headers[TOKEN_HEADER] = credentials.sessionToken;
|
||||
}
|
||||
const payloadHash = await getPayloadHash(request, this.sha256);
|
||||
if (!hasHeader(SHA256_HEADER, request.headers) && this.applyChecksum) {
|
||||
request.headers[SHA256_HEADER] = payloadHash;
|
||||
}
|
||||
const canonicalHeaders = getCanonicalHeaders(request, unsignableHeaders, signableHeaders);
|
||||
const signature = await this.getSignature(longDate, scope, this.getSigningKey(credentials, region, shortDate, signingService), this.createCanonicalRequest(request, canonicalHeaders, payloadHash));
|
||||
request.headers[AUTH_HEADER] =
|
||||
`${ALGORITHM_IDENTIFIER} ` +
|
||||
`Credential=${credentials.accessKeyId}/${scope}, ` +
|
||||
`SignedHeaders=${this.getCanonicalHeaderList(canonicalHeaders)}, ` +
|
||||
`Signature=${signature}`;
|
||||
return request;
|
||||
}
|
||||
async getSignature(longDate, credentialScope, keyPromise, canonicalRequest) {
|
||||
const stringToSign = await this.createStringToSign(longDate, credentialScope, canonicalRequest, ALGORITHM_IDENTIFIER);
|
||||
const hash = new this.sha256(await keyPromise);
|
||||
hash.update(toUint8Array(stringToSign));
|
||||
return toHex(await hash.digest());
|
||||
}
|
||||
getSigningKey(credentials, region, shortDate, service) {
|
||||
return getSigningKey(this.sha256, credentials, shortDate, region, service || this.service);
|
||||
}
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
import { normalizeProvider } from "@smithy/core/client";
|
||||
import { escapeUri } from "@smithy/core/protocols";
|
||||
import { toHex, toUint8Array } from "@smithy/core/serde";
|
||||
import { getCanonicalQuery } from "./getCanonicalQuery";
|
||||
import { iso8601 } from "./utilDate";
|
||||
export class SignatureV4Base {
|
||||
service;
|
||||
regionProvider;
|
||||
credentialProvider;
|
||||
sha256;
|
||||
uriEscapePath;
|
||||
applyChecksum;
|
||||
constructor({ applyChecksum, credentials, region, service, sha256, uriEscapePath = true, }) {
|
||||
this.service = service;
|
||||
this.sha256 = sha256;
|
||||
this.uriEscapePath = uriEscapePath;
|
||||
this.applyChecksum = typeof applyChecksum === "boolean" ? applyChecksum : true;
|
||||
this.regionProvider = normalizeProvider(region);
|
||||
this.credentialProvider = normalizeProvider(credentials);
|
||||
}
|
||||
createCanonicalRequest(request, canonicalHeaders, payloadHash) {
|
||||
const sortedHeaders = Object.keys(canonicalHeaders).sort();
|
||||
return `${request.method}
|
||||
${this.getCanonicalPath(request)}
|
||||
${getCanonicalQuery(request)}
|
||||
${sortedHeaders.map((name) => `${name}:${canonicalHeaders[name]}`).join("\n")}
|
||||
|
||||
${sortedHeaders.join(";")}
|
||||
${payloadHash}`;
|
||||
}
|
||||
async createStringToSign(longDate, credentialScope, canonicalRequest, algorithmIdentifier) {
|
||||
const hash = new this.sha256();
|
||||
hash.update(toUint8Array(canonicalRequest));
|
||||
const hashedRequest = await hash.digest();
|
||||
return `${algorithmIdentifier}
|
||||
${longDate}
|
||||
${credentialScope}
|
||||
${toHex(hashedRequest)}`;
|
||||
}
|
||||
getCanonicalPath({ path }) {
|
||||
if (this.uriEscapePath) {
|
||||
const normalizedPathSegments = [];
|
||||
for (const pathSegment of path.split("/")) {
|
||||
if (pathSegment?.length === 0)
|
||||
continue;
|
||||
if (pathSegment === ".")
|
||||
continue;
|
||||
if (pathSegment === "..") {
|
||||
normalizedPathSegments.pop();
|
||||
}
|
||||
else {
|
||||
normalizedPathSegments.push(pathSegment);
|
||||
}
|
||||
}
|
||||
const normalizedPath = `${path?.startsWith("/") ? "/" : ""}${normalizedPathSegments.join("/")}${normalizedPathSegments.length > 0 && path?.endsWith("/") ? "/" : ""}`;
|
||||
const doubleEncoded = escapeUri(normalizedPath);
|
||||
return doubleEncoded.replace(/%2F/g, "/");
|
||||
}
|
||||
return path;
|
||||
}
|
||||
validateResolvedCredentials(credentials) {
|
||||
if (typeof credentials !== "object" ||
|
||||
typeof credentials.accessKeyId !== "string" ||
|
||||
typeof credentials.secretAccessKey !== "string") {
|
||||
throw new Error("Resolved credential object is not valid");
|
||||
}
|
||||
}
|
||||
formatDate(now) {
|
||||
const longDate = iso8601(now).replace(/[\-:]/g, "");
|
||||
return {
|
||||
longDate,
|
||||
shortDate: longDate.slice(0, 8),
|
||||
};
|
||||
}
|
||||
getCanonicalHeaderList(headers) {
|
||||
return Object.keys(headers).sort().join(";");
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
export const ALGORITHM_QUERY_PARAM = "X-Amz-Algorithm";
|
||||
export const CREDENTIAL_QUERY_PARAM = "X-Amz-Credential";
|
||||
export const AMZ_DATE_QUERY_PARAM = "X-Amz-Date";
|
||||
export const SIGNED_HEADERS_QUERY_PARAM = "X-Amz-SignedHeaders";
|
||||
export const EXPIRES_QUERY_PARAM = "X-Amz-Expires";
|
||||
export const SIGNATURE_QUERY_PARAM = "X-Amz-Signature";
|
||||
export const TOKEN_QUERY_PARAM = "X-Amz-Security-Token";
|
||||
export const REGION_SET_PARAM = "X-Amz-Region-Set";
|
||||
export const AUTH_HEADER = "authorization";
|
||||
export const AMZ_DATE_HEADER = AMZ_DATE_QUERY_PARAM.toLowerCase();
|
||||
export const DATE_HEADER = "date";
|
||||
export const GENERATED_HEADERS = [AUTH_HEADER, AMZ_DATE_HEADER, DATE_HEADER];
|
||||
export const SIGNATURE_HEADER = SIGNATURE_QUERY_PARAM.toLowerCase();
|
||||
export const SHA256_HEADER = "x-amz-content-sha256";
|
||||
export const TOKEN_HEADER = TOKEN_QUERY_PARAM.toLowerCase();
|
||||
export const HOST_HEADER = "host";
|
||||
export const ALWAYS_UNSIGNABLE_HEADERS = {
|
||||
authorization: true,
|
||||
"cache-control": true,
|
||||
connection: true,
|
||||
expect: true,
|
||||
from: true,
|
||||
"keep-alive": true,
|
||||
"max-forwards": true,
|
||||
pragma: true,
|
||||
referer: true,
|
||||
te: true,
|
||||
trailer: true,
|
||||
"transfer-encoding": true,
|
||||
upgrade: true,
|
||||
"user-agent": true,
|
||||
"x-amzn-trace-id": true,
|
||||
};
|
||||
export const PROXY_HEADER_PATTERN = /^proxy-/;
|
||||
export const SEC_HEADER_PATTERN = /^sec-/;
|
||||
export const UNSIGNABLE_PATTERNS = [/^proxy-/i, /^sec-/i];
|
||||
export const ALGORITHM_IDENTIFIER = "AWS4-HMAC-SHA256";
|
||||
export const ALGORITHM_IDENTIFIER_V4A = "AWS4-ECDSA-P256-SHA256";
|
||||
export const EVENT_ALGORITHM_IDENTIFIER = "AWS4-HMAC-SHA256-PAYLOAD";
|
||||
export const UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD";
|
||||
export const MAX_CACHE_SIZE = 50;
|
||||
export const KEY_TYPE_IDENTIFIER = "aws4_request";
|
||||
export const MAX_PRESIGNED_TTL = 60 * 60 * 24 * 7;
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
import { toHex, toUint8Array } from "@smithy/core/serde";
|
||||
import { KEY_TYPE_IDENTIFIER, MAX_CACHE_SIZE } from "./constants";
|
||||
const signingKeyCache = {};
|
||||
const cacheQueue = [];
|
||||
export const createScope = (shortDate, region, service) => `${shortDate}/${region}/${service}/${KEY_TYPE_IDENTIFIER}`;
|
||||
export const getSigningKey = async (sha256Constructor, credentials, shortDate, region, service) => {
|
||||
const credsHash = await hmac(sha256Constructor, credentials.secretAccessKey, credentials.accessKeyId);
|
||||
const cacheKey = `${shortDate}:${region}:${service}:${toHex(credsHash)}:${credentials.sessionToken}`;
|
||||
if (cacheKey in signingKeyCache) {
|
||||
return signingKeyCache[cacheKey];
|
||||
}
|
||||
cacheQueue.push(cacheKey);
|
||||
while (cacheQueue.length > MAX_CACHE_SIZE) {
|
||||
delete signingKeyCache[cacheQueue.shift()];
|
||||
}
|
||||
let key = `AWS4${credentials.secretAccessKey}`;
|
||||
for (const signable of [shortDate, region, service, KEY_TYPE_IDENTIFIER]) {
|
||||
key = await hmac(sha256Constructor, key, signable);
|
||||
}
|
||||
return (signingKeyCache[cacheKey] = key);
|
||||
};
|
||||
export const clearCredentialCache = () => {
|
||||
cacheQueue.length = 0;
|
||||
Object.keys(signingKeyCache).forEach((cacheKey) => {
|
||||
delete signingKeyCache[cacheKey];
|
||||
});
|
||||
};
|
||||
const hmac = (ctor, secret, data) => {
|
||||
const hash = new ctor(secret);
|
||||
hash.update(toUint8Array(data));
|
||||
return hash.digest();
|
||||
};
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import { ALWAYS_UNSIGNABLE_HEADERS, PROXY_HEADER_PATTERN, SEC_HEADER_PATTERN } from "./constants";
|
||||
export const getCanonicalHeaders = ({ headers }, unsignableHeaders, signableHeaders) => {
|
||||
const canonical = {};
|
||||
for (const headerName of Object.keys(headers).sort()) {
|
||||
if (headers[headerName] == undefined) {
|
||||
continue;
|
||||
}
|
||||
const canonicalHeaderName = headerName.toLowerCase();
|
||||
if (canonicalHeaderName in ALWAYS_UNSIGNABLE_HEADERS ||
|
||||
unsignableHeaders?.has(canonicalHeaderName) ||
|
||||
PROXY_HEADER_PATTERN.test(canonicalHeaderName) ||
|
||||
SEC_HEADER_PATTERN.test(canonicalHeaderName)) {
|
||||
if (!signableHeaders || (signableHeaders && !signableHeaders.has(canonicalHeaderName))) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
canonical[canonicalHeaderName] = headers[headerName].trim().replace(/\s+/g, " ");
|
||||
}
|
||||
return canonical;
|
||||
};
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import { escapeUri } from "@smithy/core/protocols";
|
||||
import { SIGNATURE_HEADER } from "./constants";
|
||||
export const getCanonicalQuery = ({ query = {} }) => {
|
||||
const keys = [];
|
||||
const serialized = {};
|
||||
for (const key of Object.keys(query)) {
|
||||
if (key.toLowerCase() === SIGNATURE_HEADER) {
|
||||
continue;
|
||||
}
|
||||
const encodedKey = escapeUri(key);
|
||||
keys.push(encodedKey);
|
||||
const value = query[key];
|
||||
if (typeof value === "string") {
|
||||
serialized[encodedKey] = `${encodedKey}=${escapeUri(value)}`;
|
||||
}
|
||||
else if (Array.isArray(value)) {
|
||||
serialized[encodedKey] = value
|
||||
.slice(0)
|
||||
.reduce((encoded, value) => encoded.concat([`${encodedKey}=${escapeUri(value)}`]), [])
|
||||
.sort()
|
||||
.join("&");
|
||||
}
|
||||
}
|
||||
return keys
|
||||
.sort()
|
||||
.map((key) => serialized[key])
|
||||
.filter((serialized) => serialized)
|
||||
.join("&");
|
||||
};
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
import { isArrayBuffer, toHex, toUint8Array } from "@smithy/core/serde";
|
||||
import { SHA256_HEADER, UNSIGNED_PAYLOAD } from "./constants";
|
||||
export const getPayloadHash = async ({ headers, body }, hashConstructor) => {
|
||||
for (const headerName of Object.keys(headers)) {
|
||||
if (headerName.toLowerCase() === SHA256_HEADER) {
|
||||
return headers[headerName];
|
||||
}
|
||||
}
|
||||
if (body == undefined) {
|
||||
return "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
||||
}
|
||||
else if (typeof body === "string" || ArrayBuffer.isView(body) || isArrayBuffer(body)) {
|
||||
const hashCtor = new hashConstructor();
|
||||
hashCtor.update(toUint8Array(body));
|
||||
return toHex(await hashCtor.digest());
|
||||
}
|
||||
return UNSIGNED_PAYLOAD;
|
||||
};
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
export const hasHeader = (soughtHeader, headers) => {
|
||||
soughtHeader = soughtHeader.toLowerCase();
|
||||
for (const headerName of Object.keys(headers)) {
|
||||
if (soughtHeader === headerName.toLowerCase()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
export const getHeaderValue = (soughtHeader, headers) => {
|
||||
soughtHeader = soughtHeader.toLowerCase();
|
||||
for (const headerName of Object.keys(headers)) {
|
||||
if (soughtHeader === headerName.toLowerCase()) {
|
||||
return headers[headerName];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
export const deleteHeader = (soughtHeader, headers) => {
|
||||
soughtHeader = soughtHeader.toLowerCase();
|
||||
for (const headerName of Object.keys(headers)) {
|
||||
if (soughtHeader === headerName.toLowerCase()) {
|
||||
delete headers[headerName];
|
||||
}
|
||||
}
|
||||
};
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
export * from "./SignatureV4";
|
||||
export * from "./constants";
|
||||
export { getCanonicalHeaders } from "./getCanonicalHeaders";
|
||||
export { getCanonicalQuery } from "./getCanonicalQuery";
|
||||
export { getPayloadHash } from "./getPayloadHash";
|
||||
export { moveHeadersToQuery } from "./moveHeadersToQuery";
|
||||
export { prepareRequest } from "./prepareRequest";
|
||||
export * from "./credentialDerivation";
|
||||
export { SignatureV4Base } from "./SignatureV4Base";
|
||||
export { hasHeader } from "./headerUtil";
|
||||
export * from "./signature-v4a-container";
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { HttpRequest } from "@smithy/core/protocols";
|
||||
export const moveHeadersToQuery = (request, options = {}) => {
|
||||
const { headers, query = {} } = HttpRequest.clone(request);
|
||||
for (const name of Object.keys(headers)) {
|
||||
const lname = name.toLowerCase();
|
||||
if ((lname.slice(0, 6) === "x-amz-" && !options.unhoistableHeaders?.has(lname)) ||
|
||||
options.hoistableHeaders?.has(lname)) {
|
||||
query[name] = headers[name];
|
||||
delete headers[name];
|
||||
}
|
||||
}
|
||||
return {
|
||||
...request,
|
||||
headers,
|
||||
query,
|
||||
};
|
||||
};
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { HttpRequest } from "@smithy/core/protocols";
|
||||
import { GENERATED_HEADERS } from "./constants";
|
||||
export const prepareRequest = (request) => {
|
||||
request = HttpRequest.clone(request);
|
||||
for (const headerName of Object.keys(request.headers)) {
|
||||
if (GENERATED_HEADERS.indexOf(headerName.toLowerCase()) > -1) {
|
||||
delete request.headers[headerName];
|
||||
}
|
||||
}
|
||||
return request;
|
||||
};
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
export const signatureV4aContainer = {
|
||||
SignatureV4a: null,
|
||||
};
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
export const iso8601 = (time) => toDate(time)
|
||||
.toISOString()
|
||||
.replace(/\.\d{3}Z$/, "Z");
|
||||
export const toDate = (time) => {
|
||||
if (typeof time === "number") {
|
||||
return new Date(time * 1000);
|
||||
}
|
||||
if (typeof time === "string") {
|
||||
if (Number(time)) {
|
||||
return new Date(Number(time) * 1000);
|
||||
}
|
||||
return new Date(time);
|
||||
}
|
||||
return time;
|
||||
};
|
||||
Reference in New Issue
Block a user