Skip to content

Commit fe3c90b

Browse files
committed
use webcrypto for new code
1 parent 037bcf8 commit fe3c90b

4 files changed

Lines changed: 44 additions & 22 deletions

File tree

src/aws4.ts

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import * as crypto from 'node:crypto';
2-
31
import { type AWSCredentials } from './deps';
42

53
export type Options = {
@@ -25,14 +23,35 @@ export type SignedHeaders = {
2523
};
2624
};
2725

28-
const getHash = (str: string): string => {
29-
return crypto.createHash('sha256').update(str, 'utf8').digest('hex');
26+
const crypto = globalThis.crypto;
27+
28+
const getHash = async (str: string): Promise<string> => {
29+
const encoder = new TextEncoder();
30+
const data = encoder.encode(str);
31+
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
32+
const hashArray = Array.from(new Uint8Array(hashBuffer));
33+
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
34+
return hashHex;
3035
};
31-
const getHmacBuffer = (key: string | Uint8Array, str: string): Uint8Array => {
32-
return crypto.createHmac('sha256', key).update(str, 'utf8').digest();
36+
const getHmacBuffer = async (key: string | Uint8Array, str: string): Promise<Uint8Array> => {
37+
const encoder = new TextEncoder();
38+
const keyData = typeof key === 'string' ? encoder.encode(key) : key;
39+
const importedKey = await crypto.subtle.importKey(
40+
'raw',
41+
keyData,
42+
{ name: 'HMAC', hash: { name: 'SHA-256' } },
43+
false,
44+
['sign']
45+
);
46+
const signature = await crypto.subtle.sign('HMAC', importedKey, encoder.encode(str));
47+
const digest = new Uint8Array(signature);
48+
return digest;
3349
};
34-
const getHmacString = (key: Uint8Array, str: string): string => {
35-
return crypto.createHmac('sha256', key).update(str, 'utf8').digest('hex');
50+
const getHmacString = async (key: Uint8Array, str: string): Promise<string> => {
51+
const hmacBuffer = await getHmacBuffer(key, str);
52+
const hashArray = Array.from(hmacBuffer);
53+
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
54+
return hashHex;
3655
};
3756

3857
const convertHeaderValue = (value: string | number) => {
@@ -43,7 +62,10 @@ const convertHeaderValue = (value: string | number) => {
4362
* This method implements AWS Signature 4 logic for a very specific request format.
4463
* The signing logic is described here: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv-create-signed-request.html
4564
*/
46-
export function aws4Sign(options: Options, credentials: AWSCredentials): SignedHeaders {
65+
export async function aws4Sign(
66+
options: Options,
67+
credentials: AWSCredentials
68+
): Promise<SignedHeaders> {
4769
/**
4870
* From the spec: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv-create-signed-request.html
4971
*
@@ -102,7 +124,7 @@ export function aws4Sign(options: Options, credentials: AWSCredentials): SignedH
102124
const signedHeaders = canonicalHeaderNames.sort().join(';');
103125

104126
// 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.
105-
const hashedPayload = getHash(options.body);
127+
const hashedPayload = await getHash(options.body);
106128

107129
// CanonicalRequest – A string that includes the above elements, separated by newline characters.
108130
const canonicalRequest = [
@@ -116,7 +138,7 @@ export function aws4Sign(options: Options, credentials: AWSCredentials): SignedH
116138

117139
// 2. Create a hash of the canonical request
118140
// HashedCanonicalRequest – A string created by using the canonical request as input to a hash function.
119-
const hashedCanonicalRequest = getHash(canonicalRequest);
141+
const hashedCanonicalRequest = await getHash(canonicalRequest);
120142

121143
// 3. Create a string to sign
122144
// Algorithm – The algorithm used to create the hash of the canonical request. For SigV4, use AWS4-HMAC-SHA256.
@@ -131,13 +153,13 @@ export function aws4Sign(options: Options, credentials: AWSCredentials): SignedH
131153

132154
// 4. Derive a signing key
133155
// 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.
134-
const dateKey = getHmacBuffer('AWS4' + credentials.secretAccessKey, requestDate);
135-
const dateRegionKey = getHmacBuffer(dateKey, options.region);
136-
const dateRegionServiceKey = getHmacBuffer(dateRegionKey, options.service);
137-
const signingKey = getHmacBuffer(dateRegionServiceKey, 'aws4_request');
156+
const dateKey = await getHmacBuffer('AWS4' + credentials.secretAccessKey, requestDate);
157+
const dateRegionKey = await getHmacBuffer(dateKey, options.region);
158+
const dateRegionServiceKey = await getHmacBuffer(dateRegionKey, options.service);
159+
const signingKey = await getHmacBuffer(dateRegionServiceKey, 'aws4_request');
138160

139161
// 5. Calculate the signature
140-
const signature = getHmacString(signingKey, stringToSign);
162+
const signature = await getHmacString(signingKey, stringToSign);
141163

142164
// 6. Add the signature to the request
143165
// Calculate the Authorization header

src/cmap/auth/mongodb_aws.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export class MongoDBAWS extends AuthProvider {
106106
}
107107

108108
const body = 'Action=GetCallerIdentity&Version=2011-06-15';
109-
const signed = aws4Sign(
109+
const signed = await aws4Sign(
110110
{
111111
method: 'POST',
112112
host,

test/integration/auth/mongodb_aws.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ describe('MONGODB-AWS', function () {
260260
'X-MongoDB-Server-Nonce': 'fakenonce',
261261
'X-MongoDB-GS2-CB-Flag': 'n'
262262
};
263-
const signed = aws4Sign(
263+
const signed = await aws4Sign(
264264
{
265265
method: 'POST',
266266
host,

test/unit/aws4.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ describe('Verify AWS4 signature generation', () => {
3131
date
3232
};
3333

34-
it('should generate correct credentials for permanent credentials', () => {
35-
const signed = aws4Sign(request, awsCredentials);
34+
it('should generate correct credentials for permanent credentials', async () => {
35+
const signed = await aws4Sign(request, awsCredentials);
3636

3737
expect(signed.headers['X-Amz-Date']).to.exist;
3838
expect(signed.headers['X-Amz-Date']).to.equal('20251215T123456Z');
@@ -51,8 +51,8 @@ describe('Verify AWS4 signature generation', () => {
5151
// expect(oldSigned.headers['Authorization']).to.equal(signed.headers['Authorization']);
5252
});
5353

54-
it('should generate correct credentials for session credentials', () => {
55-
const signed = aws4Sign(request, awsSessionCredentials);
54+
it('should generate correct credentials for session credentials', async () => {
55+
const signed = await aws4Sign(request, awsSessionCredentials);
5656

5757
expect(signed.headers['X-Amz-Date']).to.exist;
5858
expect(signed.headers['X-Amz-Date']).to.equal('20251215T123456Z');

0 commit comments

Comments
 (0)