19
19
var parsePayload = function ( payload ) {
20
20
var dict = { } ;
21
21
var parts = payload . split ( ',' ) ;
22
-
23
22
for ( var i = 0 ; i < parts . length ; i ++ ) {
24
23
var valueParts = parts [ i ] . split ( '=' ) ;
25
24
dict [ valueParts [ 0 ] ] = valueParts [ 1 ] ;
@@ -105,6 +104,23 @@ function HI(data, salt, iterations, cryptoMethod) {
105
104
return saltedData ;
106
105
}
107
106
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
+
108
124
/**
109
125
* Creates a new ScramSHA authentication mechanism
110
126
* @class
@@ -179,9 +195,19 @@ class ScramSHA extends AuthProvider {
179
195
180
196
const payload = Buffer . isBuffer ( r . payload ) ? new Binary ( r . payload ) : r . payload ;
181
197
const dict = parsePayload ( payload . value ( ) ) ;
198
+
182
199
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
+
183
205
const salt = dict . s ;
184
206
const rnonce = dict . r ;
207
+ if ( rnonce . startsWith ( 'nonce' ) ) {
208
+ callback ( new MongoError ( `Server returned an invalid nonce: ${ rnonce } ` ) , false ) ;
209
+ return ;
210
+ }
185
211
186
212
// Set up start of proof
187
213
const withoutProof = `c=biws,r=${ rnonce } ` ;
@@ -192,25 +218,35 @@ class ScramSHA extends AuthProvider {
192
218
cryptoMethod
193
219
) ;
194
220
195
- if ( iterations && iterations < 4096 ) {
196
- const error = new MongoError ( `Server returned an invalid iteration count ${ iterations } ` ) ;
197
- return callback ( error , false ) ;
198
- }
199
-
200
221
const clientKey = HMAC ( cryptoMethod , saltedPassword , 'Client Key' ) ;
222
+ const serverKey = HMAC ( cryptoMethod , saltedPassword , 'Server Key' ) ;
201
223
const storedKey = H ( cryptoMethod , clientKey ) ;
202
224
const authMessage = [ firstBare , payload . value ( ) . toString ( 'base64' ) , withoutProof ] . join ( ',' ) ;
203
225
204
226
const clientSignature = HMAC ( cryptoMethod , storedKey , authMessage ) ;
205
227
const clientProof = `p=${ xor ( clientKey , clientSignature ) } ` ;
206
228
const clientFinal = [ withoutProof , clientProof ] . join ( ',' ) ;
229
+
230
+ const serverSignature = HMAC ( cryptoMethod , serverKey , authMessage ) ;
231
+
207
232
const saslContinueCmd = {
208
233
saslContinue : 1 ,
209
234
conversationId : r . conversationId ,
210
235
payload : new Binary ( Buffer . from ( clientFinal ) )
211
236
} ;
212
237
213
238
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
+
214
250
if ( ! r || r . done !== false ) {
215
251
return callback ( err , r ) ;
216
252
}
0 commit comments