11import { saslprep } from '@mongodb-js/saslprep' ;
2- import * as crypto from 'crypto' ;
32
43import {
54 allocateBuffer ,
@@ -164,22 +163,22 @@ async function continueScramConversation(
164163
165164 // Set up start of proof
166165 const withoutProof = `c=biws,r=${ rnonce } ` ;
167- const saltedPassword = HI ( processedPassword , fromBase64 ( salt ) , iterations , cryptoMethod ) ;
166+ const saltedPassword = await HI ( processedPassword , fromBase64 ( salt ) , iterations , cryptoMethod ) ;
168167
169- const clientKey = HMAC ( cryptoMethod , saltedPassword , 'Client Key' ) ;
170- const serverKey = HMAC ( cryptoMethod , saltedPassword , 'Server Key' ) ;
171- const storedKey = H ( cryptoMethod , clientKey ) ;
168+ const clientKey = await HMAC ( cryptoMethod , saltedPassword , 'Client Key' ) ;
169+ const serverKey = await HMAC ( cryptoMethod , saltedPassword , 'Server Key' ) ;
170+ const storedKey = await H ( cryptoMethod , clientKey ) ;
172171 const authMessage = [
173172 clientFirstMessageBare ( username , nonce ) ,
174173 payload . toString ( 'utf8' ) ,
175174 withoutProof
176175 ] . join ( ',' ) ;
177176
178- const clientSignature = HMAC ( cryptoMethod , storedKey , authMessage ) ;
177+ const clientSignature = await HMAC ( cryptoMethod , storedKey , authMessage ) ;
179178 const clientProof = `p=${ xor ( clientKey , clientSignature ) } ` ;
180179 const clientFinal = [ withoutProof , clientProof ] . join ( ',' ) ;
181180
182- const serverSignature = HMAC ( cryptoMethod , serverKey , authMessage ) ;
181+ const serverSignature = await HMAC ( cryptoMethod , serverKey , authMessage ) ;
183182 const saslContinueCmd = {
184183 saslContinue : 1 ,
185184 conversationId : response . conversationId ,
@@ -231,19 +230,28 @@ function passwordDigest(username: string, password: string) {
231230 throw new MongoInvalidArgumentError ( 'Password cannot be empty' ) ;
232231 }
233232
234- let md5 : crypto . Hash ;
233+ let nodeCrypto ;
235234 try {
236- md5 = crypto . createHash ( 'md5' ) ;
235+ // eslint-disable-next-line @typescript-eslint/no-require-imports
236+ nodeCrypto = require ( 'crypto' ) ;
237+ } catch ( e ) {
238+ throw new MongoRuntimeError ( 'Crypto support is required for SCRAM-SHA-1 authentication' , {
239+ cause : e
240+ } ) ;
241+ }
242+
243+ try {
244+ const md5 = nodeCrypto . createHash ( 'md5' ) ;
245+ md5 . update ( `${ username } :mongo:${ password } ` , 'utf8' ) ;
246+ return md5 . digest ( 'hex' ) ;
237247 } catch ( err ) {
238- if ( crypto . getFips ( ) ) {
248+ if ( nodeCrypto . getFips ( ) ) {
239249 // This error is (slightly) more helpful than what comes from OpenSSL directly, e.g.
240250 // 'Error: error:060800C8:digital envelope routines:EVP_DigestInit_ex:disabled for FIPS'
241251 throw new Error ( 'Auth mechanism SCRAM-SHA-1 is not supported in FIPS mode' ) ;
242252 }
243253 throw err ;
244254 }
245- md5 . update ( `${ username } :mongo:${ password } ` , 'utf8' ) ;
246- return md5 . digest ( 'hex' ) ;
247255}
248256
249257// XOR two buffers
@@ -258,12 +266,28 @@ function xor(a: Uint8Array, b: Uint8Array) {
258266 return ByteUtils . toBase64 ( fromNumberArray ( res ) ) ;
259267}
260268
261- function H ( method : CryptoMethod , text : Uint8Array ) : Uint8Array {
262- return crypto . createHash ( method ) . update ( text ) . digest ( ) ;
269+ async function H ( method : CryptoMethod , text : Uint8Array ) : Promise < Uint8Array > {
270+ const buffer = await crypto . subtle . digest ( method === 'sha256' ? 'SHA-256' : 'SHA-1' , text ) ;
271+ return new Uint8Array ( buffer ) ;
263272}
264273
265- function HMAC ( method : CryptoMethod , key : Uint8Array , text : Uint8Array | string ) : Uint8Array {
266- return crypto . createHmac ( method , key ) . update ( text ) . digest ( ) ;
274+ async function HMAC (
275+ method : CryptoMethod ,
276+ key : Uint8Array ,
277+ text : Uint8Array | string
278+ ) : Promise < Uint8Array > {
279+ const keyBuffer = ByteUtils . toLocalBufferType ( key ) ;
280+ const cryptoKey = await crypto . subtle . importKey (
281+ 'raw' ,
282+ keyBuffer ,
283+ { name : 'HMAC' , hash : { name : method === 'sha256' ? 'SHA-256' : 'SHA-1' } } ,
284+ false ,
285+ [ 'sign' , 'verify' ]
286+ ) ;
287+ const textData : Uint8Array = typeof text === 'string' ? new TextEncoder ( ) . encode ( text ) : text ;
288+ const textBuffer = ByteUtils . toLocalBufferType ( textData ) ;
289+ const signature = await crypto . subtle . sign ( 'HMAC' , cryptoKey , textBuffer ) ;
290+ return new Uint8Array ( signature ) ;
267291}
268292
269293interface HICache {
@@ -282,21 +306,32 @@ const hiLengthMap = {
282306 sha1 : 20
283307} ;
284308
285- function HI ( data : string , salt : Uint8Array , iterations : number , cryptoMethod : CryptoMethod ) {
309+ async function HI ( data : string , salt : Uint8Array , iterations : number , cryptoMethod : CryptoMethod ) {
286310 // omit the work if already generated
287311 const key = [ data , ByteUtils . toBase64 ( salt ) , iterations ] . join ( '_' ) ;
288312 if ( _hiCache [ key ] != null ) {
289313 return _hiCache [ key ] ;
290314 }
291315
292- // generate the salt
293- const saltedData = crypto . pbkdf2Sync (
294- data ,
295- salt ,
296- iterations ,
297- hiLengthMap [ cryptoMethod ] ,
298- cryptoMethod
316+ const keyMaterial = await crypto . subtle . importKey (
317+ 'raw' ,
318+ new TextEncoder ( ) . encode ( data ) ,
319+ { name : 'PBKDF2' } ,
320+ false ,
321+ [ 'deriveBits' ]
299322 ) ;
323+ const params = {
324+ name : 'PBKDF2' ,
325+ salt : salt ,
326+ iterations : iterations ,
327+ hash : { name : cryptoMethod === 'sha256' ? 'SHA-256' : 'SHA-1' }
328+ } ;
329+ const derivedBits = await crypto . subtle . deriveBits (
330+ params ,
331+ keyMaterial ,
332+ hiLengthMap [ cryptoMethod ] * 8
333+ ) ;
334+ const saltedData = new Uint8Array ( derivedBits ) ;
300335
301336 // cache a copy to speed up the next lookup, but prevent unbounded cache growth
302337 if ( _hiCacheCount >= 200 ) {
@@ -313,10 +348,6 @@ function compareDigest(lhs: Uint8Array, rhs: Uint8Array) {
313348 return false ;
314349 }
315350
316- if ( typeof crypto . timingSafeEqual === 'function' ) {
317- return crypto . timingSafeEqual ( lhs , rhs ) ;
318- }
319-
320351 let result = 0 ;
321352 for ( let i = 0 ; i < lhs . length ; i ++ ) {
322353 result |= lhs [ i ] ^ rhs [ i ] ;
0 commit comments