From 8d6b27a97cf7f256b35986a305b27180e933f459 Mon Sep 17 00:00:00 2001 From: Ronak Patel Date: Mon, 28 Oct 2019 22:22:51 -0400 Subject: [PATCH] feat(signature-v4): add support to override the set of unsignableHeaders (#420) Co-Authored-By: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> --- packages/signature-v4/src/SignatureV4.spec.ts | 21 ++++++++++++++ packages/signature-v4/src/SignatureV4.ts | 29 +++++++++++++++---- .../src/getCanonicalHeaders.spec.ts | 26 +++++++++++++++++ .../signature-v4/src/getCanonicalHeaders.ts | 10 +++++-- packages/types/src/signature.ts | 10 +++++++ 5 files changed, 88 insertions(+), 8 deletions(-) diff --git a/packages/signature-v4/src/SignatureV4.spec.ts b/packages/signature-v4/src/SignatureV4.spec.ts index 5364a41f9a84..3a834c16babe 100644 --- a/packages/signature-v4/src/SignatureV4.spec.ts +++ b/packages/signature-v4/src/SignatureV4.spec.ts @@ -452,6 +452,27 @@ describe("SignatureV4", () => { ); }); + it("should allow specifying custom signable headers to override custom and always unsignable ones", async () => { + const { headers } = await signer.sign( + { + ...minimalRequest, + headers: { + host: "foo.us-bar-1.amazonaws.com", + foo: "bar", + "user-agent": "baz" + } + }, + { + signingDate: new Date("2000-01-01T00:00:00.000Z"), + unsignableHeaders: new Set(["foo"]), + signableHeaders: new Set(["foo", "user-agent"]) + } + ); + expect(headers[AUTH_HEADER]).toMatch( + /^AWS4-HMAC-SHA256 Credential=foo\/20000101\/us-bar-1\/foo\/aws4_request, SignedHeaders=foo;host;user-agent;x-amz-content-sha256;x-amz-date, Signature=/ + ); + }); + it("should support signing with asynchronously resolved credentials", async () => { const credsProvider = () => Promise.resolve({ diff --git a/packages/signature-v4/src/SignatureV4.ts b/packages/signature-v4/src/SignatureV4.ts index 0421f9617be0..c585eb921f92 100644 --- a/packages/signature-v4/src/SignatureV4.ts +++ b/packages/signature-v4/src/SignatureV4.ts @@ -133,7 +133,11 @@ export class SignatureV4 this.credentialProvider() ]); - const { signingDate = new Date(), unsignableHeaders } = options; + const { + signingDate = new Date(), + unsignableHeaders, + signableHeaders + } = options; const { longDate, shortDate } = formatDate(signingDate); const ttl = getTtl(signingDate, expiration); @@ -158,7 +162,11 @@ export class SignatureV4 request.query[AMZ_DATE_QUERY_PARAM] = longDate; request.query[EXPIRES_QUERY_PARAM] = ttl.toString(10); - const canonicalHeaders = getCanonicalHeaders(request, unsignableHeaders); + const canonicalHeaders = getCanonicalHeaders( + request, + unsignableHeaders, + signableHeaders + ); request.query[SIGNED_HEADERS_QUERY_PARAM] = getCanonicalHeaderList( canonicalHeaders ); @@ -205,14 +213,18 @@ export class SignatureV4 credentials ) as Promise; } else { - const { unsignableHeaders } = options as RequestSigningArguments; + const { + unsignableHeaders, + signableHeaders + } = options as RequestSigningArguments; return this.signRequest( toSign as HttpRequest, signingDate, region, credentials, - unsignableHeaders + unsignableHeaders, + signableHeaders ) as Promise; } } @@ -237,7 +249,8 @@ export class SignatureV4 signingDate: DateInput, region: string, credentials: Credentials, - unsignableHeaders?: Set + unsignableHeaders?: Set, + signableHeaders?: Set ): Promise> { const request = prepareRequest(originalRequest); const { longDate, shortDate } = formatDate(signingDate); @@ -253,7 +266,11 @@ export class SignatureV4 request.headers[SHA256_HEADER] = payloadHash; } - const canonicalHeaders = getCanonicalHeaders(request, unsignableHeaders); + const canonicalHeaders = getCanonicalHeaders( + request, + unsignableHeaders, + signableHeaders + ); const signature = await this.getSignature( longDate, scope, diff --git a/packages/signature-v4/src/getCanonicalHeaders.spec.ts b/packages/signature-v4/src/getCanonicalHeaders.spec.ts index 5b3e63482e76..a694720c37d5 100644 --- a/packages/signature-v4/src/getCanonicalHeaders.spec.ts +++ b/packages/signature-v4/src/getCanonicalHeaders.spec.ts @@ -61,4 +61,30 @@ describe("getCanonicalHeaders", () => { host: "foo.us-east-1.amazonaws.com" }); }); + + it("should allow specifying custom signable headers that override unsignable ones", () => { + const request: HttpRequest = { + method: "POST", + protocol: "https:", + path: "/", + headers: { + host: "foo.us-east-1.amazonaws.com", + foo: "bar", + "user-agent": "foo-user" + }, + hostname: "foo.us-east-1.amazonaws.com" + }; + + expect( + getCanonicalHeaders( + request, + new Set(["foo"]), + new Set(["foo", "user-agent"]) + ) + ).toEqual({ + host: "foo.us-east-1.amazonaws.com", + foo: "bar", + "user-agent": "foo-user" + }); + }); }); diff --git a/packages/signature-v4/src/getCanonicalHeaders.ts b/packages/signature-v4/src/getCanonicalHeaders.ts index c729a5b20dc9..eb6776c74692 100644 --- a/packages/signature-v4/src/getCanonicalHeaders.ts +++ b/packages/signature-v4/src/getCanonicalHeaders.ts @@ -10,7 +10,8 @@ import { */ export function getCanonicalHeaders( { headers }: HttpRequest, - unsignableHeaders?: Set + unsignableHeaders?: Set, + signableHeaders?: Set ): HeaderBag { const canonical: HeaderBag = {}; for (let headerName of Object.keys(headers).sort()) { @@ -21,7 +22,12 @@ export function getCanonicalHeaders( PROXY_HEADER_PATTERN.test(canonicalHeaderName) || SEC_HEADER_PATTERN.test(canonicalHeaderName) ) { - continue; + if ( + !signableHeaders || + (signableHeaders && !signableHeaders.has(canonicalHeaderName)) + ) { + continue; + } } canonical[canonicalHeaderName] = headers[headerName] diff --git a/packages/types/src/signature.ts b/packages/types/src/signature.ts index cb7ee4324376..13fb706c4259 100644 --- a/packages/types/src/signature.ts +++ b/packages/types/src/signature.ts @@ -23,6 +23,16 @@ export interface RequestSigningArguments extends SigningArguments { * lower case and then checked for existence in the unsignableHeaders set. */ unsignableHeaders?: Set; + + /** + * A set of strings whose members represents headers that should be signed. + * Any values passed here will override those provided via unsignableHeaders, + * allowing them to be signed. + * + * All headers in the provided request will have their names converted to + * lower case before signing. + */ + signableHeaders?: Set; } export interface RequestPresigner {