Skip to content

Commit 73b90d6

Browse files
authored
Merge pull request #8262 from BitGo/aloe/hmacWebCryptoStrategy
feat: hmac authentication strategy and response verification
2 parents 80c470e + fd4a300 commit 73b90d6

20 files changed

Lines changed: 2137 additions & 88 deletions

modules/sdk-api/src/api.ts

Lines changed: 66 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import querystring from 'querystring';
1111

1212
import { ApiResponseError, BitGoRequest } from '@bitgo/sdk-core';
1313

14-
import { AuthVersion, VerifyResponseOptions } from './types';
14+
import { AuthVersion, VerifyResponseInfo, VerifyResponseOptions } from './types';
1515
import { BitGoAPI } from './bitgoAPI';
1616

1717
const debug = Debug('bitgo:api');
@@ -214,44 +214,23 @@ export function setRequestQueryString(req: superagent.SuperAgentRequest): void {
214214
}
215215

216216
/**
217-
* Verify that the response received from the server is signed correctly.
218-
* Right now, it is very permissive with the timestamp variance.
217+
* Validate a completed verification response and throw a descriptive `ApiResponseError` if it
218+
* indicates the response is invalid or outside the acceptable time window.
219219
*/
220-
export function verifyResponse(
220+
function assertVerificationResponse(
221221
bitgo: BitGoAPI,
222222
token: string | undefined,
223-
method: VerifyResponseOptions['method'],
224223
req: superagent.SuperAgentRequest,
225224
response: superagent.Response,
226-
authVersion: AuthVersion
227-
): superagent.Response {
228-
// we can't verify the response if we're not authenticated
229-
if (!req.isV2Authenticated || !req.authenticationToken) {
230-
return response;
231-
}
232-
233-
const verificationResponse = bitgo.verifyResponse({
234-
url: req.url,
235-
hmac: response.header.hmac,
236-
statusCode: response.status,
237-
text: response.text,
238-
timestamp: response.header.timestamp,
239-
token: req.authenticationToken,
240-
method,
241-
authVersion,
242-
});
243-
225+
verificationResponse: VerifyResponseInfo
226+
): void {
244227
if (!verificationResponse.isValid) {
245-
// calculate the HMAC
246-
const receivedHmac = response.header.hmac;
247-
const expectedHmac = verificationResponse.expectedHmac;
248-
const signatureSubject = verificationResponse.signatureSubject;
249228
// Log only the first 10 characters of the token to ensure the full token isn't logged.
250229
const partialBitgoToken = token ? token.substring(0, 10) : '';
251230
const errorDetails = {
252-
expectedHmac,
253-
receivedHmac,
254-
hmacInput: signatureSubject,
231+
expectedHmac: verificationResponse.expectedHmac,
232+
receivedHmac: response.header.hmac,
233+
hmacInput: verificationResponse.signatureSubject,
255234
requestToken: req.authenticationToken,
256235
bitgoToken: partialBitgoToken,
257236
};
@@ -271,5 +250,62 @@ export function verifyResponse(
271250
errorDetails
272251
);
273252
}
253+
}
254+
255+
/**
256+
* Verify that the response received from the server is signed correctly.
257+
* Right now, it is very permissive with the timestamp variance.
258+
*/
259+
export function verifyResponse(
260+
bitgo: BitGoAPI,
261+
token: string | undefined,
262+
method: VerifyResponseOptions['method'],
263+
req: superagent.SuperAgentRequest,
264+
response: superagent.Response,
265+
authVersion: AuthVersion
266+
): superagent.Response {
267+
if (!req.isV2Authenticated || !req.authenticationToken) {
268+
return response;
269+
}
270+
271+
const verificationResponse = bitgo.verifyResponse({
272+
url: req.url,
273+
hmac: response.header.hmac,
274+
statusCode: response.status,
275+
text: response.text,
276+
timestamp: response.header.timestamp,
277+
token: req.authenticationToken,
278+
method,
279+
authVersion,
280+
});
281+
282+
assertVerificationResponse(bitgo, token, req, response, verificationResponse);
283+
return response;
284+
}
285+
286+
export async function verifyResponseAsync(
287+
bitgo: BitGoAPI,
288+
token: string | undefined,
289+
method: VerifyResponseOptions['method'],
290+
req: superagent.SuperAgentRequest,
291+
response: superagent.Response,
292+
authVersion: AuthVersion
293+
): Promise<superagent.Response> {
294+
if (!req.isV2Authenticated || !req.authenticationToken) {
295+
return response;
296+
}
297+
298+
const verificationResponse = await bitgo.verifyResponseAsync({
299+
url: req.url,
300+
hmac: response.header.hmac,
301+
statusCode: response.status,
302+
text: response.text,
303+
timestamp: response.header.timestamp,
304+
token: req.authenticationToken,
305+
method,
306+
authVersion,
307+
});
308+
309+
assertVerificationResponse(bitgo, token, req, response, verificationResponse);
274310
return response;
275311
}

0 commit comments

Comments
 (0)