@@ -186,7 +186,7 @@ async function continueScramConversation(
186186 const r = await connection . command ( ns ( `${ db } .$cmd` ) , saslContinueCmd , undefined ) ;
187187 const parsedResponse = parsePayload ( r . payload ) ;
188188
189- if ( ! compareDigest ( ByteUtils . fromBase64 ( parsedResponse . v ) , serverSignature ) ) {
189+ if ( ! ( await compareDigest ( ByteUtils . fromBase64 ( parsedResponse . v ) , serverSignature ) ) ) {
190190 throw new MongoRuntimeError ( 'Server returned an invalid signature' ) ;
191191 }
192192
@@ -342,17 +342,25 @@ async function HI(data: string, salt: Uint8Array, iterations: number, cryptoMeth
342342 return saltedData ;
343343}
344344
345- function compareDigest ( lhs : Uint8Array , rhs : Uint8Array ) {
345+ async function compareDigest ( lhs : Uint8Array , rhs : Uint8Array ) {
346346 if ( lhs . length !== rhs . length ) {
347347 return false ;
348348 }
349349
350- let result = 0 ;
351- for ( let i = 0 ; i < lhs . length ; i ++ ) {
352- result |= lhs [ i ] ^ rhs [ i ] ;
353- }
350+ // Compare values using a time-constant algorithm to prevent against timing attacks
351+ // The approach is called "Double HMAC Verification". The basic idea is:
352+ // 1. Generate a random key
353+ // 2. HMAC the random key with both values
354+ // 3. Compare the HMACs using an equality check
355+
356+ const randomKey = crypto . getRandomValues ( new Uint8Array ( 32 ) ) ;
357+ const lhsHMAC = await HMAC ( 'sha256' , randomKey , lhs ) ;
358+ const rhsHMAC = await HMAC ( 'sha256' , randomKey , rhs ) ;
359+ const lhsHex = ByteUtils . toHex ( lhsHMAC ) ;
360+ const rhsHex = ByteUtils . toHex ( rhsHMAC ) ;
361+ const areEqual = lhsHex === rhsHex ;
354362
355- return result === 0 ;
363+ return areEqual ;
356364}
357365
358366export class ScramSHA1 extends ScramSHA {
0 commit comments