1919var parsePayload = function ( payload ) {
2020 var dict = { } ;
2121 var parts = payload . split ( ',' ) ;
22-
2322 for ( var i = 0 ; i < parts . length ; i ++ ) {
2423 var valueParts = parts [ i ] . split ( '=' ) ;
2524 dict [ valueParts [ 0 ] ] = valueParts [ 1 ] ;
@@ -105,6 +104,23 @@ function HI(data, salt, iterations, cryptoMethod) {
105104 return saltedData ;
106105}
107106
107+ function compareDigest ( lhs , rhs ) {
108+ if ( lhs . length !== rhs . length ) {
109+ return false ;
110+ }
111+
112+ if ( typeof crypto . timingSafeEqual === 'function' ) {
113+ return crypto . timingSafeEqual ( lhs , rhs ) ;
114+ }
115+
116+ let result = 0 ;
117+ for ( let i = 0 ; i < lhs . length ; i ++ ) {
118+ result |= lhs [ i ] ^ rhs [ i ] ;
119+ }
120+
121+ return result === 0 ;
122+ }
123+
108124/**
109125 * Creates a new ScramSHA authentication mechanism
110126 * @class
@@ -179,9 +195,19 @@ class ScramSHA extends AuthProvider {
179195
180196 const payload = Buffer . isBuffer ( r . payload ) ? new Binary ( r . payload ) : r . payload ;
181197 const dict = parsePayload ( payload . value ( ) ) ;
198+
182199 const iterations = parseInt ( dict . i , 10 ) ;
200+ if ( iterations && iterations < 4096 ) {
201+ callback ( new MongoError ( `Server returned an invalid iteration count ${ iterations } ` ) , false ) ;
202+ return ;
203+ }
204+
183205 const salt = dict . s ;
184206 const rnonce = dict . r ;
207+ if ( rnonce . startsWith ( 'nonce' ) ) {
208+ callback ( new MongoError ( `Server returned an invalid nonce: ${ rnonce } ` ) , false ) ;
209+ return ;
210+ }
185211
186212 // Set up start of proof
187213 const withoutProof = `c=biws,r=${ rnonce } ` ;
@@ -192,25 +218,35 @@ class ScramSHA extends AuthProvider {
192218 cryptoMethod
193219 ) ;
194220
195- if ( iterations && iterations < 4096 ) {
196- const error = new MongoError ( `Server returned an invalid iteration count ${ iterations } ` ) ;
197- return callback ( error , false ) ;
198- }
199-
200221 const clientKey = HMAC ( cryptoMethod , saltedPassword , 'Client Key' ) ;
222+ const serverKey = HMAC ( cryptoMethod , saltedPassword , 'Server Key' ) ;
201223 const storedKey = H ( cryptoMethod , clientKey ) ;
202224 const authMessage = [ firstBare , payload . value ( ) . toString ( 'base64' ) , withoutProof ] . join ( ',' ) ;
203225
204226 const clientSignature = HMAC ( cryptoMethod , storedKey , authMessage ) ;
205227 const clientProof = `p=${ xor ( clientKey , clientSignature ) } ` ;
206228 const clientFinal = [ withoutProof , clientProof ] . join ( ',' ) ;
229+
230+ const serverSignature = HMAC ( cryptoMethod , serverKey , authMessage ) ;
231+
207232 const saslContinueCmd = {
208233 saslContinue : 1 ,
209234 conversationId : r . conversationId ,
210235 payload : new Binary ( Buffer . from ( clientFinal ) )
211236 } ;
212237
213238 sendAuthCommand ( connection , `${ db } .$cmd` , saslContinueCmd , ( err , r ) => {
239+ if ( r && typeof r . ok === 'number' && r . ok === 0 ) {
240+ callback ( err , r ) ;
241+ return ;
242+ }
243+
244+ const parsedResponse = parsePayload ( r . payload . value ( ) ) ;
245+ if ( ! compareDigest ( Buffer . from ( parsedResponse . v , 'base64' ) , serverSignature ) ) {
246+ callback ( new MongoError ( 'Server returned an invalid signature' ) ) ;
247+ return ;
248+ }
249+
214250 if ( ! r || r . done !== false ) {
215251 return callback ( err , r ) ;
216252 }
0 commit comments