@@ -18,15 +18,15 @@ use itertools::Itertools;
1818
1919use crate :: password:: Password ;
2020
21- /// The default iteration count as suggested by
22- /// <https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html>
23- const DEFAULT_ITERATIONS : NonZeroU32 = NonZeroU32 :: new ( 600_000 ) . unwrap ( ) ;
24-
2521/// The default salt size, which isn't currently configurable.
2622const DEFAULT_SALT_SIZE : usize = 32 ;
2723
2824const SHA256_OUTPUT_LEN : usize = 32 ;
2925
26+ /// The default iteration count as suggested by
27+ /// <https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html>
28+ const DEFAULT_ITERATIONS : NonZeroU32 = NonZeroU32 :: new ( 600_000 ) . unwrap ( ) ;
29+
3030/// The options for hashing a password
3131#[ derive( Debug , PartialEq ) ]
3232pub struct HashOpts {
@@ -70,21 +70,22 @@ impl Display for HashError {
7070
7171/// Hashes a password using PBKDF2 with SHA256
7272/// and a random salt.
73- pub fn hash_password ( password : & Password ) -> Result < PasswordHash , HashError > {
73+ pub fn hash_password (
74+ password : & Password ,
75+ iterations : & Option < NonZeroU32 > ,
76+ ) -> Result < PasswordHash , HashError > {
7477 let mut salt = [ 0u8 ; DEFAULT_SALT_SIZE ] ;
7578 openssl:: rand:: rand_bytes ( & mut salt) . map_err ( HashError :: Openssl ) ?;
79+ let iterations = iterations. unwrap_or ( DEFAULT_ITERATIONS ) ;
7680
7781 let hash = hash_password_inner (
78- & HashOpts {
79- iterations : DEFAULT_ITERATIONS ,
80- salt,
81- } ,
82+ & HashOpts { iterations, salt } ,
8283 password. to_string ( ) . as_bytes ( ) ,
8384 ) ?;
8485
8586 Ok ( PasswordHash {
8687 salt,
87- iterations : DEFAULT_ITERATIONS ,
88+ iterations,
8889 hash,
8990 } )
9091}
@@ -115,8 +116,11 @@ pub fn hash_password_with_opts(
115116/// Hashes a password using PBKDF2 with SHA256,
116117/// and returns it in the SCRAM-SHA-256 format.
117118/// The format is SCRAM-SHA-256$<iterations>:<salt>$<stored_key>:<server_key>
118- pub fn scram256_hash ( password : & Password ) -> Result < String , HashError > {
119- let hashed_password = hash_password ( password) ?;
119+ pub fn scram256_hash (
120+ password : & Password ,
121+ iterations : & Option < NonZeroU32 > ,
122+ ) -> Result < String , HashError > {
123+ let hashed_password = hash_password ( password, iterations) ?;
120124 Ok ( scram256_hash_inner ( hashed_password) . to_string ( ) )
121125}
122126
@@ -207,14 +211,18 @@ fn generate_signature(key: &[u8], message: &str) -> Result<Vec<u8>, VerifyError>
207211// Generate a mock challenge based on the username and client nonce
208212// We do this so that we can present a deterministic challenge even for
209213// nonexistent users, to avoid user enumeration attacks.
210- pub fn mock_sasl_challenge ( username : & str , mock_nonce : & str ) -> HashOpts {
214+ pub fn mock_sasl_challenge (
215+ username : & str ,
216+ mock_nonce : & str ,
217+ iterations : & Option < NonZeroU32 > ,
218+ ) -> HashOpts {
211219 let mut buf = Vec :: with_capacity ( username. len ( ) + mock_nonce. len ( ) ) ;
212220 buf. extend_from_slice ( username. as_bytes ( ) ) ;
213221 buf. extend_from_slice ( mock_nonce. as_bytes ( ) ) ;
214222 let digest = openssl:: sha:: sha256 ( & buf) ;
215223
216224 HashOpts {
217- iterations : DEFAULT_ITERATIONS ,
225+ iterations : iterations . unwrap_or ( DEFAULT_ITERATIONS ) ,
218226 salt : digest,
219227 }
220228}
@@ -326,7 +334,11 @@ mod tests {
326334 #[ cfg_attr( miri, ignore) ] // unsupported operation: can't call foreign function `OPENSSL_init_ssl` on OS `linux`
327335 fn test_hash_password ( ) {
328336 let password = "password" . to_string ( ) ;
329- let hashed_password = hash_password ( & password. into ( ) ) . expect ( "Failed to hash password" ) ;
337+ let hashed_password = hash_password (
338+ & password. into ( ) ,
339+ & Some ( NonZeroU32 :: new ( 100 ) . expect ( "Trust me on this" ) ) ,
340+ )
341+ . expect ( "Failed to hash password" ) ;
330342 assert_eq ! ( hashed_password. iterations, DEFAULT_ITERATIONS ) ;
331343 assert_eq ! ( hashed_password. salt. len( ) , DEFAULT_SALT_SIZE ) ;
332344 assert_eq ! ( hashed_password. hash. len( ) , SHA256_OUTPUT_LEN ) ;
@@ -336,7 +348,7 @@ mod tests {
336348 #[ cfg_attr( miri, ignore) ] // unsupported operation: can't call foreign function `OPENSSL_init_ssl` on OS `linux`
337349 fn test_scram256_hash ( ) {
338350 let password = "password" . into ( ) ;
339- let scram_hash = scram256_hash ( & password) . expect ( "Failed to hash password" ) ;
351+ let scram_hash = scram256_hash ( & password, & None ) . expect ( "Failed to hash password" ) ;
340352
341353 let res = scram256_verify ( & password, & scram_hash) ;
342354 assert ! ( res. is_ok( ) ) ;
@@ -363,16 +375,16 @@ mod tests {
363375 fn test_mock_sasl_challenge ( ) {
364376 let username = "alice" ;
365377 let mock = "cnonce" ;
366- let opts1 = mock_sasl_challenge ( username, mock) ;
367- let opts2 = mock_sasl_challenge ( username, mock) ;
378+ let opts1 = mock_sasl_challenge ( username, mock, & None ) ;
379+ let opts2 = mock_sasl_challenge ( username, mock, & None ) ;
368380 assert_eq ! ( opts1, opts2) ;
369381 }
370382
371383 #[ mz_ore:: test]
372384 #[ cfg_attr( miri, ignore) ]
373385 fn test_sasl_verify_success ( ) {
374386 let password: Password = "password" . into ( ) ;
375- let hashed_password = scram256_hash ( & password) . expect ( "hash password" ) ;
387+ let hashed_password = scram256_hash ( & password, & None ) . expect ( "hash password" ) ;
376388 let auth_message = "n=user,r=clientnonce,s=somesalt" ; // arbitrary auth message
377389
378390 // Parse client_key and server_key from the SCRAM hash
@@ -426,7 +438,7 @@ mod tests {
426438 #[ cfg_attr( miri, ignore) ]
427439 fn test_sasl_verify_invalid_proof ( ) {
428440 let password: Password = "password" . into ( ) ;
429- let hashed_password = scram256_hash ( & password) . expect ( "hash password" ) ;
441+ let hashed_password = scram256_hash ( & password, & None ) . expect ( "hash password" ) ;
430442 let auth_message = "n=user,r=clientnonce,s=somesalt" ;
431443 // Provide an obviously invalid base64 proof (different size / random)
432444 let bad_proof = BASE64_STANDARD . encode ( [ 0u8 ; 32 ] ) ;
0 commit comments