Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7cc1156
sigv4 implementation
PavelSafronov Dec 11, 2025
811d453
fix issues and add a test, and a simple way to run it
PavelSafronov Dec 12, 2025
a0ba1ec
added unit tests for the signing logic, added comments about how to c…
PavelSafronov Dec 15, 2025
449d677
Merge branch 'main' into NODE-5393
PavelSafronov Dec 15, 2025
a44f3b4
added test for undefined credentials
PavelSafronov Dec 15, 2025
221044d
pr feedback:
PavelSafronov Dec 17, 2025
72ab61d
removed extraneous integ test and moved its logic into an existing aw…
PavelSafronov Dec 17, 2025
037bcf8
minor fixes
PavelSafronov Dec 17, 2025
fe3c90b
use webcrypto for new code
PavelSafronov Dec 19, 2025
021f9de
use ByteUtils.toHex
PavelSafronov Dec 19, 2025
d7966a3
use ByteUtils.encodeUTF8Into
PavelSafronov Dec 19, 2025
3a2a0ee
pr feedback
PavelSafronov Jan 5, 2026
9178f66
Update src/cmap/auth/aws4.ts
PavelSafronov Jan 6, 2026
5a8380f
pr feedback
PavelSafronov Jan 6, 2026
a3c06e4
minor fix
PavelSafronov Jan 6, 2026
26fecf5
Merge branch 'main' into NODE-5393
PavelSafronov Jan 6, 2026
a625dc5
Merge branch 'main' into NODE-5393
PavelSafronov Jan 7, 2026
2e69f64
removing unnecessary bit of code
PavelSafronov Jan 7, 2026
31f49e7
Merge branch 'main' into NODE-5393
PavelSafronov Jan 7, 2026
59f3e26
update aws4 test
PavelSafronov Jan 7, 2026
41a18ab
Merge branch 'main' into NODE-5393
PavelSafronov Jan 7, 2026
178b90a
pr feedback
PavelSafronov Jan 8, 2026
4e88199
add aws4 as dev dependency and verify our code generates the same sig…
PavelSafronov Jan 8, 2026
a4d722a
make aws4 a dev dependency
PavelSafronov Jan 8, 2026
6fffef6
Merge branch 'main' into NODE-5393
PavelSafronov Jan 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 29 additions & 11 deletions src/aws4.ts → src/cmap/auth/aws4.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BSON } from './bson';
import { type AWSCredentials } from './deps';
import { BSON } from '../../bson';
import { type AWSCredentials } from '../../deps';

export type Options = {
Comment thread
PavelSafronov marked this conversation as resolved.
Outdated
path: '/';
Expand All @@ -14,7 +14,7 @@ export type Options = {
};
service: string;
region: string;
date?: Date;
date: Date;
};

export type SignedHeaders = {
Expand All @@ -24,13 +24,26 @@ export type SignedHeaders = {
};
};

/**
* Calculates the SHA-256 hash of a string.
*
* @param str - String to hash.
* @returns Hexadecimal representation of the hash.
*/
const getHash = async (str: string): Promise<string> => {
Comment thread
addaleax marked this conversation as resolved.
Outdated
const data = new Uint8Array(BSON.onDemand.ByteUtils.utf8ByteLength(str));
BSON.onDemand.ByteUtils.encodeUTF8Into(data, str, 0);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashHex = BSON.onDemand.ByteUtils.toHex(new Uint8Array(hashBuffer));
return hashHex;
};
Comment thread
addaleax marked this conversation as resolved.
Outdated

/**
* Calculates the HMAC-SHA256 of a string using the provided key.
* @param key - Key to use for HMAC calculation. Can be a string or Uint8Array.
* @param str - String to calculate HMAC for.
* @returns Uint8Array containing the HMAC-SHA256 digest.
*/
const getHmacBuffer = async (key: string | Uint8Array, str: string): Promise<Uint8Array> => {
Comment thread
baileympearson marked this conversation as resolved.
Outdated
let keyData: Uint8Array;
if (typeof key === 'string') {
Expand All @@ -53,12 +66,16 @@ const getHmacBuffer = async (key: string | Uint8Array, str: string): Promise<Uin
const digest = new Uint8Array(signature);
return digest;
};
const getHmacString = async (key: Uint8Array, str: string): Promise<string> => {
const hmacBuffer = await getHmacBuffer(key, str);
const hashHex = BSON.onDemand.ByteUtils.toHex(hmacBuffer);
return hashHex;
};

/**
* Converts header values according to AWS requirements,
* From https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv-create-signed-request.html#create-canonical-request
* For values, you must:
- trim any leading or trailing spaces.
- convert sequential spaces to a single space.
* @param value - Header value to convert.
* @returns - Converted header value.
*/
const convertHeaderValue = (value: string | number) => {
Comment thread
baileympearson marked this conversation as resolved.
return value.toString().trim().replace(/\s+/g, ' ');
};
Expand Down Expand Up @@ -91,8 +108,8 @@ export async function aws4Sign(

// 1: Create a canonical request

// Date – The date and time used to sign the request. If not provided, use the current date.
const date = options.date || new Date();
// Date – The date and time used to sign the request.
const date = options.date;
// RequestDateTime – The date and time used in the credential scope. This value is the current UTC time in ISO 8601 format (for example, 20130524T000000Z).
const requestDateTime = date.toISOString().replace(/[:-]|\.\d{3}/g, '');
// RequestDate – The date used in the credential scope. This value is the current UTC date in YYYYMMDD format (for example, 20130524).
Expand Down Expand Up @@ -164,7 +181,8 @@ export async function aws4Sign(
const signingKey = await getHmacBuffer(dateRegionServiceKey, 'aws4_request');

// 5. Calculate the signature
const signature = await getHmacString(signingKey, stringToSign);
const signatureBuffer = await getHmacBuffer(signingKey, stringToSign);
const signature = BSON.onDemand.ByteUtils.toHex(signatureBuffer);

// 6. Add the signature to the request
// Calculate the Authorization header
Expand Down
5 changes: 3 additions & 2 deletions src/cmap/auth/mongodb_aws.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { aws4Sign } from '../../aws4';
import type { Binary, BSONSerializeOptions } from '../../bson';
import * as BSON from '../../bson';
import {
Expand All @@ -13,6 +12,7 @@ import {
AWSSDKCredentialProvider,
type AWSTempCredentials
} from './aws_temporary_credentials';
import { aws4Sign } from './aws4';
import { MongoCredentials } from './mongo_credentials';
import { AuthMechanism } from './providers';

Expand Down Expand Up @@ -119,7 +119,8 @@ export class MongoDBAWS extends AuthProvider {
'X-MongoDB-GS2-CB-Flag': 'n'
},
path: '/',
body
body,
date: new Date()
},
awsCredentials
);
Expand Down
5 changes: 3 additions & 2 deletions test/integration/auth/mongodb_aws.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ import {
MongoMissingDependencyError,
MongoServerError
} from '../../../src';
import { aws4Sign } from '../../../src/aws4';
import { refreshKMSCredentials } from '../../../src/client-side-encryption/providers';
import { AWSSDKCredentialProvider } from '../../../src/cmap/auth/aws_temporary_credentials';
import { aws4Sign } from '../../../src/cmap/auth/aws4';
import { MongoDBAWS } from '../../../src/cmap/auth/mongodb_aws';
import { Connection } from '../../../src/cmap/connection';
import { setDifference } from '../../../src/utils';
Expand Down Expand Up @@ -268,7 +268,8 @@ describe('MONGODB-AWS', function () {
region: 'us-east-1',
service: 'sts',
headers: headers,
body
body,
date: new Date()
},
creds
);
Expand Down
2 changes: 1 addition & 1 deletion test/unit/aws4.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect } from 'chai';

import { aws4Sign, type Options } from '../../src/aws4';
import { aws4Sign, type Options } from '../../src/cmap/auth/aws4';

describe('Verify AWS4 signature generation', () => {
const date = new Date('2025-12-15T12:34:56Z');
Expand Down