Skip to content

Commit 8785ad4

Browse files
authored
fix(signature-v4): add secrets to signing key cache key (#1776)
* fix(signature-v4): add secrets to signing key cache key
1 parent 388b180 commit 8785ad4

File tree

2 files changed

+26
-28
lines changed

2 files changed

+26
-28
lines changed

packages/signature-v4/src/credentialDerivation.spec.ts

+14-10
Original file line numberDiff line numberDiff line change
@@ -46,27 +46,31 @@ describe("getSigningKey", () => {
4646
});
4747

4848
describe("caching", () => {
49-
it("should return the same promise when called with the same date, region, service, and credentials", () => {
50-
const promise1 = getSigningKey(Sha256, credentials, shortDate, region, service);
51-
const promise2 = getSigningKey(Sha256, credentials, shortDate, region, service);
52-
expect(promise1).toBe(promise2);
49+
it("should return the same signing key when called with the same date, region, service, and credentials", async () => {
50+
const mockSha256Constructor = jest.fn().mockImplementation((args) => {
51+
return new Sha256(args);
52+
});
53+
const key1 = await getSigningKey(mockSha256Constructor, credentials, shortDate, region, service);
54+
const key2 = await getSigningKey(mockSha256Constructor, credentials, shortDate, region, service);
55+
expect(key1).toBe(key2);
56+
expect(mockSha256Constructor).toHaveBeenCalledTimes(6);
5357
});
5458

55-
it("should cache a maximum of 50 entries", () => {
56-
const keyPromises: Array<Promise<Uint8Array>> = new Array(50);
59+
it("should cache a maximum of 50 entries", async () => {
60+
const keys: Array<Uint8Array> = new Array(50);
5761
// fill the cache
5862
for (let i = 0; i < 50; i++) {
59-
keyPromises[i] = getSigningKey(Sha256, credentials, shortDate, `us-foo-${i.toString(10)}`, service);
63+
keys[i] = await getSigningKey(Sha256, credentials, shortDate, `us-foo-${i.toString(10)}`, service);
6064
}
6165

6266
// evict the oldest member from the cache
63-
getSigningKey(Sha256, credentials, shortDate, `us-foo-50`, service);
67+
await getSigningKey(Sha256, credentials, shortDate, `us-foo-50`, service);
6468

6569
// the second oldest member should still be in cache
66-
expect(keyPromises[1]).toBe(getSigningKey(Sha256, credentials, shortDate, `us-foo-1`, service));
70+
await expect(getSigningKey(Sha256, credentials, shortDate, `us-foo-1`, service)).resolves.toStrictEqual(keys[1]);
6771

6872
// the oldest member should not be in the cache
69-
expect(keyPromises[0]).not.toBe(getSigningKey(Sha256, credentials, shortDate, `us-foo-0`, service));
73+
await expect(getSigningKey(Sha256, credentials, shortDate, `us-foo-0`, service)).resolves.not.toBe(keys[0]);
7074
});
7175
});
7276
});

packages/signature-v4/src/credentialDerivation.ts

+12-18
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { Credentials, HashConstructor, SourceData } from "@aws-sdk/types";
2+
import { toHex } from "@aws-sdk/util-hex-encoding";
23

34
import { KEY_TYPE_IDENTIFIER, MAX_CACHE_SIZE } from "./constants";
45

5-
const signingKeyCache: { [key: string]: Promise<Uint8Array> } = {};
6+
const signingKeyCache: { [key: string]: Uint8Array } = {};
67
const cacheQueue: Array<string> = [];
78

89
/**
@@ -28,14 +29,15 @@ export function createScope(shortDate: string, region: string, service: string):
2829
* @param service The service to which the signed request is being
2930
* sent.
3031
*/
31-
export function getSigningKey(
32+
export const getSigningKey = async (
3233
sha256Constructor: HashConstructor,
3334
credentials: Credentials,
3435
shortDate: string,
3536
region: string,
3637
service: string
37-
): Promise<Uint8Array> {
38-
const cacheKey = `${shortDate}:${region}:${service}:` + `${credentials.accessKeyId}:${credentials.sessionToken}`;
38+
): Promise<Uint8Array> => {
39+
const credsHash = await hmac(sha256Constructor, credentials.secretAccessKey, credentials.accessKeyId);
40+
const cacheKey = `${shortDate}:${region}:${service}:${toHex(credsHash)}:${credentials.sessionToken}`;
3941
if (cacheKey in signingKeyCache) {
4042
return signingKeyCache[cacheKey];
4143
}
@@ -45,20 +47,12 @@ export function getSigningKey(
4547
delete signingKeyCache[cacheQueue.shift() as string];
4648
}
4749

48-
return (signingKeyCache[cacheKey] = new Promise((resolve, reject) => {
49-
let keyPromise: Promise<SourceData> = Promise.resolve(`AWS4${credentials.secretAccessKey}`);
50-
51-
for (const signable of [shortDate, region, service, KEY_TYPE_IDENTIFIER]) {
52-
keyPromise = keyPromise.then<Uint8Array>((intermediateKey) => hmac(sha256Constructor, intermediateKey, signable));
53-
keyPromise.catch(() => {});
54-
}
55-
56-
(keyPromise as Promise<Uint8Array>).then(resolve, (reason) => {
57-
delete signingKeyCache[cacheKey];
58-
reject(reason);
59-
});
60-
}));
61-
}
50+
let key: SourceData = `AWS4${credentials.secretAccessKey}`;
51+
for (const signable of [shortDate, region, service, KEY_TYPE_IDENTIFIER]) {
52+
key = await hmac(sha256Constructor, key, signable);
53+
}
54+
return (signingKeyCache[cacheKey] = key as Uint8Array);
55+
};
6256

6357
/**
6458
* @internal

0 commit comments

Comments
 (0)