Skip to content

Commit 3075ec1

Browse files
authored
Merge pull request #7685 from BitGo/ANT-1352
feat: add forward tolerance for v3 validity check
2 parents ea85be9 + 1af1db9 commit 3075ec1

File tree

2 files changed

+64
-38
lines changed

2 files changed

+64
-38
lines changed

modules/sdk-hmac/src/hmac.ts

Lines changed: 30 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,11 @@ export function calculateHMACSubject<T extends string | Buffer = string>(
6969
/**
7070
* Calculate the HMAC for an HTTP request
7171
*/
72-
export function calculateRequestHMAC<T extends string | Buffer = string>({
73-
url: urlPath,
74-
text,
75-
timestamp,
76-
token,
77-
method,
78-
authVersion,
79-
}: CalculateRequestHmacOptions<T>): string {
80-
const signatureSubject = calculateHMACSubject({ urlPath, text, timestamp, method, authVersion });
72+
export function calculateRequestHMAC<T extends string | Buffer = string>(
73+
{ url: urlPath, text, timestamp, token, method, authVersion }: CalculateRequestHmacOptions<T>,
74+
useOriginalPath = false
75+
): string {
76+
const signatureSubject = calculateHMACSubject({ urlPath, text, timestamp, method, authVersion }, useOriginalPath);
8177

8278
// calculate the HMAC
8379
return calculateHMAC(token, signatureSubject);
@@ -86,15 +82,12 @@ export function calculateRequestHMAC<T extends string | Buffer = string>({
8682
/**
8783
* Calculate request headers with HMAC
8884
*/
89-
export function calculateRequestHeaders<T extends string | Buffer = string>({
90-
url,
91-
text,
92-
token,
93-
method,
94-
authVersion,
95-
}: CalculateRequestHeadersOptions<T>): RequestHeaders {
85+
export function calculateRequestHeaders<T extends string | Buffer = string>(
86+
{ url, text, token, method, authVersion }: CalculateRequestHeadersOptions<T>,
87+
useOriginalPath = false
88+
): RequestHeaders {
9689
const timestamp = Date.now();
97-
const hmac = calculateRequestHMAC({ url, text, timestamp, token, method, authVersion });
90+
const hmac = calculateRequestHMAC({ url, text, timestamp, token, method, authVersion }, useOriginalPath);
9891

9992
// calculate the SHA256 hash of the token
10093
const hashDigest = sjcl.hash.sha256.hash(token);
@@ -109,31 +102,31 @@ export function calculateRequestHeaders<T extends string | Buffer = string>({
109102
/**
110103
* Verify the HMAC for an HTTP response
111104
*/
112-
export function verifyResponse<T extends string | Buffer = string>({
113-
url: urlPath,
114-
statusCode,
115-
text,
116-
timestamp,
117-
token,
118-
hmac,
119-
method,
120-
authVersion,
121-
}: VerifyResponseOptions<T>): VerifyResponseInfo<T> {
122-
const signatureSubject = calculateHMACSubject({
123-
urlPath,
124-
text,
125-
timestamp,
126-
statusCode,
127-
method,
128-
authVersion,
129-
});
105+
export function verifyResponse<T extends string | Buffer = string>(
106+
{ url: urlPath, statusCode, text, timestamp, token, hmac, method, authVersion }: VerifyResponseOptions<T>,
107+
useOriginalPath = false
108+
): VerifyResponseInfo<T> {
109+
const signatureSubject = calculateHMACSubject(
110+
{
111+
urlPath,
112+
text,
113+
timestamp,
114+
statusCode,
115+
method,
116+
authVersion,
117+
},
118+
useOriginalPath
119+
);
130120

131121
// calculate the HMAC
132122
const expectedHmac = calculateHMAC(token, signatureSubject);
133123

134-
// determine if the response is still within the validity window (5 minute window)
124+
// determine if the response is still within the validity window (5-minute backwards window, 1-minute forward window)
135125
const now = Date.now();
136-
const isInResponseValidityWindow = timestamp >= now - 1000 * 60 * 5 && timestamp <= now;
126+
const backwardValidityWindow = 1000 * 60 * 5;
127+
const forwardValidityWindow = 1000 * 60;
128+
const isInResponseValidityWindow =
129+
timestamp >= now - backwardValidityWindow && timestamp <= now + forwardValidityWindow;
137130

138131
// verify the HMAC and timestamp
139132
return {

modules/sdk-hmac/test/hmac.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ describe('HMAC Utility Functions', () => {
249249
expect(result.isValid).to.be.false;
250250
});
251251

252-
it('should return invalid if timestamp is outside the validity window', () => {
252+
it('should return invalid if timestamp is outside the validity window (backwards check)', () => {
253253
const result = verifyResponse({
254254
url: '/api/test',
255255
statusCode: 200,
@@ -264,6 +264,39 @@ describe('HMAC Utility Functions', () => {
264264
expect(result.isInResponseValidityWindow).to.be.false;
265265
});
266266

267+
it('should return invalid if timestamp is outside the validity window (forwards check)', () => {
268+
const result = verifyResponse({
269+
url: '/api/test',
270+
statusCode: 200,
271+
text: 'response-body',
272+
timestamp: MOCK_TIMESTAMP + 1000 * 60 * 2, // 2 minutes in the future
273+
token: 'test-token',
274+
hmac: '8f6a2d183e4c4f2bd2023202486e1651292c84573a31b3829d394f1763a6ec6c',
275+
method: 'post',
276+
authVersion: 3,
277+
});
278+
279+
expect(result.isInResponseValidityWindow).to.be.false;
280+
});
281+
282+
it('should verify if timestamp is inside the forward validity window', () => {
283+
const result = verifyResponse({
284+
url: '/api/test',
285+
statusCode: 200,
286+
text: 'response-body',
287+
timestamp: MOCK_TIMESTAMP + 1000 * 30, // 30 seconds in the future
288+
token: 'test-token',
289+
hmac: '5e08c494691951ee45a6fc0e3fbfce3a76fcb5b9ee37e9bf9f2ac66690466dc7',
290+
method: 'post',
291+
authVersion: 3,
292+
});
293+
294+
expect(result).to.include({
295+
isValid: true,
296+
isInResponseValidityWindow: true,
297+
});
298+
});
299+
267300
it('should verify response with Buffer data', () => {
268301
const responseData = Buffer.from('binary-response-data');
269302

0 commit comments

Comments
 (0)