@@ -230,46 +230,86 @@ export function verifyResponse(
230230 return response ;
231231 }
232232
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- } ) ;
233+ // --- Build version-specific params, call bitgo.verifyResponse(), collect error context ---
234+ let verificationResult : {
235+ isValid : boolean ;
236+ expectedHmac : string ;
237+ isInResponseValidityWindow : boolean ;
238+ verificationTime : number ;
239+ } ;
240+ let hmacErrorDetails : Record < string , unknown > ;
241+ let responseTimestamp : string | number ;
242+
243+ if ( authVersion === 4 ) {
244+ const hmac = response . header [ 'x-signature' ] ;
245+ const timestamp = response . header [ 'x-request-timestamp' ] ;
246+ const authRequestId = response . header [ 'x-auth-request-id' ] ;
247+
248+ if ( ! hmac || ! timestamp ) {
249+ // Server didn't sign the response (could be an error before auth middleware)
250+ return response ;
251+ }
252+
253+ // Hash the raw response body bytes.
254+ // Convert response.text to a Buffer (UTF-8) so we're hashing the actual bytes,
255+ // not relying on Node's implicit string encoding in crypto.update().
256+ const rawResponseBuffer = Buffer . from ( response . text || '' ) ;
257+ const bodyHashHex = bitgo . calculateBodyHash ( rawResponseBuffer ) ;
258+
259+ const result = bitgo . verifyResponse ( {
260+ hmac,
261+ timestampSec : Number ( timestamp ) ,
262+ method : req . v4Method || method ,
263+ pathWithQuery : req . v4PathWithQuery || new URL ( req . url ) . pathname + new URL ( req . url ) . search ,
264+ bodyHashHex,
265+ authRequestId : authRequestId || req . v4AuthRequestId || '' ,
266+ statusCode : response . status ,
267+ rawToken : req . authenticationToken ,
268+ } ) ;
243269
244- if ( ! verificationResponse . isValid ) {
245- // calculate the HMAC
246- const receivedHmac = response . header . hmac ;
247- const expectedHmac = verificationResponse . expectedHmac ;
248- const signatureSubject = verificationResponse . signatureSubject ;
249- // Log only the first 10 characters of the token to ensure the full token isn't logged.
270+ verificationResult = result ;
271+ responseTimestamp = timestamp ;
272+ hmacErrorDetails = { expectedHmac : result . expectedHmac , receivedHmac : hmac , preimage : result . preimage } ;
273+ } else {
274+ const result = bitgo . verifyResponse ( {
275+ url : req . url ,
276+ hmac : response . header . hmac ,
277+ statusCode : response . status ,
278+ text : response . text ,
279+ timestamp : response . header . timestamp ,
280+ token : req . authenticationToken ,
281+ method,
282+ authVersion,
283+ } ) ;
284+
285+ verificationResult = result ;
286+ responseTimestamp = response . header . timestamp ;
250287 const partialBitgoToken = token ? token . substring ( 0 , 10 ) : '' ;
251- const errorDetails = {
252- expectedHmac,
253- receivedHmac,
254- hmacInput : signatureSubject ,
288+ hmacErrorDetails = {
289+ expectedHmac : result . expectedHmac ,
290+ receivedHmac : response . header . hmac ,
291+ hmacInput : result . signatureSubject ,
255292 requestToken : req . authenticationToken ,
256293 bitgoToken : partialBitgoToken ,
257294 } ;
258- debug ( 'Invalid response HMAC: %O' , errorDetails ) ;
259- throw new ApiResponseError ( 'invalid response HMAC, possible man-in-the-middle-attack' , 511 , errorDetails ) ;
260295 }
261296
262- if ( bitgo . getAuthVersion ( ) === 3 && ! verificationResponse . isInResponseValidityWindow ) {
263- const errorDetails = {
264- timestamp : response . header . timestamp ,
265- verificationTime : verificationResponse . verificationTime ,
266- } ;
297+ // --- Common validation for all auth versions ---
298+ if ( ! verificationResult . isValid ) {
299+ debug ( 'Invalid response HMAC: %O' , hmacErrorDetails ) ;
300+ throw new ApiResponseError ( 'invalid response HMAC, possible man-in-the-middle-attack' , 511 , hmacErrorDetails ) ;
301+ }
302+
303+ // v3 and v4 enforce the response validity window; v2 does not
304+ if ( authVersion >= 3 && ! verificationResult . isInResponseValidityWindow ) {
305+ const errorDetails = { timestamp : responseTimestamp , verificationTime : verificationResult . verificationTime } ;
267306 debug ( 'Server response outside response validity time window: %O' , errorDetails ) ;
268307 throw new ApiResponseError (
269308 'server response outside response validity time window, possible man-in-the-middle-attack' ,
270309 511 ,
271310 errorDetails
272311 ) ;
273312 }
313+
274314 return response ;
275315}
0 commit comments