11use crate :: error:: { ConfigError , Error , Result } ;
22use crate :: token_parser:: { parse_token, Parts } ;
3- use crate :: HashConfig ;
3+ use crate :: SecureString ;
44use argon2:: {
55 password_hash:: { PasswordHash , PasswordVerifier } ,
66 Argon2 ,
@@ -13,6 +13,8 @@ use password_hash::PasswordHashString;
1313pub struct KeyValidator {
1414 hash : PasswordHashString ,
1515 has_checksum : bool ,
16+ /// Dummy password for timing attack protection (should be a generated API key)
17+ dummy_password : SecureString ,
1618}
1719
1820/// Represents the status of an API key after verification
@@ -31,14 +33,19 @@ impl KeyValidator {
3133 const MAX_HASH_LENGTH : usize = 512 ;
3234
3335 pub fn new (
34- hash_config : & HashConfig ,
36+ _hash_config : & crate :: HashConfig ,
3537 has_checksum : bool ,
38+ dummy_key : SecureString ,
39+ dummy_hash : String ,
3640 ) -> std:: result:: Result < KeyValidator , ConfigError > {
37- let dummy_hash = format ! ( "$argon2id$v=19$m={},t={},p={}$0bJKH8iokgID0PWXnrsXvw$oef42xfOKBQMkCpvoQTeVHLhsYf+EQWMc2u4Ebn1MUo" , hash_config. memory_cost( ) , hash_config. time_cost( ) , hash_config. parallelism( ) ) ;
3841 let hash =
3942 PasswordHashString :: new ( & dummy_hash) . map_err ( |_| ConfigError :: InvalidArgon2Hash ) ?;
4043
41- Ok ( KeyValidator { hash, has_checksum } )
44+ Ok ( KeyValidator {
45+ hash,
46+ has_checksum,
47+ dummy_password : dummy_key,
48+ } )
4249 }
4350
4451 fn verify_expiry ( & self , parts : Parts ) -> Result < KeyStatus > {
@@ -113,12 +120,12 @@ impl KeyValidator {
113120 // SECURITY: Perform dummy Argon2 verification to match timing of real verification
114121 // This prevents timing attacks that could distinguish between "invalid hash format"
115122 // and "valid hash but wrong password" errors
116- let dummy_password =
117- b"text-v1-test-okphUY-aqllb-qHoZDC9mVlm5sY9lvmm.AAAAAGk2Mvg.a54368d6331bf42dc18c" ;
118- parse_token ( dummy_password , self . has_checksum ) . ok ( ) ;
123+ use crate :: ExposeSecret ;
124+ let dummy_bytes = self . dummy_password . expose_secret ( ) . as_bytes ( ) ;
125+ parse_token ( dummy_bytes , self . has_checksum ) . ok ( ) ;
119126
120127 Argon2 :: default ( )
121- . verify_password ( dummy_password , & self . hash . password_hash ( ) )
128+ . verify_password ( dummy_bytes , & self . hash . password_hash ( ) )
122129 . ok ( ) ;
123130 }
124131}
@@ -129,13 +136,22 @@ mod tests {
129136 use crate :: ExposeSecret ;
130137 use crate :: { config:: HashConfig , hasher:: KeyHasher , SecureString } ;
131138
139+ fn dummy_key_and_hash ( ) -> ( SecureString , String ) {
140+ let key = SecureString :: from ( "sk-live-dummy123test" . to_string ( ) ) ;
141+ let hasher = KeyHasher :: new ( HashConfig :: default ( ) ) ;
142+ let hash = hasher. hash ( & key) . unwrap ( ) ;
143+ ( key, hash)
144+ }
145+
132146 #[ test]
133147 fn test_verification ( ) {
134148 let key = SecureString :: from ( "sk_live_testkey123" . to_string ( ) ) ;
135149 let hasher = KeyHasher :: new ( HashConfig :: default ( ) ) ;
136150 let hash = hasher. hash ( & key) . unwrap ( ) ;
137151
138- let validator = KeyValidator :: new ( & HashConfig :: default ( ) , true ) . unwrap ( ) ;
152+ let ( dummy_key, dummy_hash) = dummy_key_and_hash ( ) ;
153+ let validator =
154+ KeyValidator :: new ( & HashConfig :: default ( ) , true , dummy_key, dummy_hash) . unwrap ( ) ;
139155 assert_eq ! (
140156 validator
141157 . verify( key. expose_secret( ) , hash. as_ref( ) )
@@ -150,7 +166,9 @@ mod tests {
150166
151167 #[ test]
152168 fn test_invalid_hash_format ( ) {
153- let validator = KeyValidator :: new ( & HashConfig :: default ( ) , true ) . unwrap ( ) ;
169+ let ( dummy_key, dummy_hash) = dummy_key_and_hash ( ) ;
170+ let validator =
171+ KeyValidator :: new ( & HashConfig :: default ( ) , true , dummy_key, dummy_hash) . unwrap ( ) ;
154172 let result = validator. verify ( "any_key" , "invalid_hash" ) ;
155173 // After timing oracle fix: invalid hash format returns Ok(Invalid) instead of Err
156174 // to prevent timing-based user enumeration attacks
@@ -165,7 +183,9 @@ mod tests {
165183 let hasher = KeyHasher :: new ( HashConfig :: default ( ) ) ;
166184 let hash = hasher. hash ( & valid_key) . unwrap ( ) ;
167185
168- let validator = KeyValidator :: new ( & HashConfig :: default ( ) , true ) . unwrap ( ) ;
186+ let ( dummy_key, dummy_hash) = dummy_key_and_hash ( ) ;
187+ let validator =
188+ KeyValidator :: new ( & HashConfig :: default ( ) , true , dummy_key, dummy_hash) . unwrap ( ) ;
169189 let result = validator. verify ( & oversized_key, hash. as_ref ( ) ) ;
170190 assert ! ( result. is_err( ) ) ;
171191 assert ! ( matches!( result. unwrap_err( ) , Error :: InvalidFormat ) ) ;
@@ -175,7 +195,9 @@ mod tests {
175195 fn test_oversized_hash_rejection ( ) {
176196 let oversized_hash = "a" . repeat ( 513 ) ; // Exceeds MAX_HASH_LENGTH
177197
178- let validator = KeyValidator :: new ( & HashConfig :: default ( ) , true ) . unwrap ( ) ;
198+ let ( dummy_key, dummy_hash) = dummy_key_and_hash ( ) ;
199+ let validator =
200+ KeyValidator :: new ( & HashConfig :: default ( ) , true , dummy_key, dummy_hash) . unwrap ( ) ;
179201 let result = validator. verify ( "valid_key" , & oversized_hash) ;
180202 assert ! ( result. is_err( ) ) ;
181203 assert ! ( matches!( result. unwrap_err( ) , Error :: InvalidFormat ) ) ;
@@ -187,7 +209,9 @@ mod tests {
187209 let hasher = KeyHasher :: new ( HashConfig :: default ( ) ) ;
188210 let hash = hasher. hash ( & valid_key) . unwrap ( ) ;
189211
190- let validator = KeyValidator :: new ( & HashConfig :: default ( ) , true ) . unwrap ( ) ;
212+ let ( dummy_key, dummy_hash) = dummy_key_and_hash ( ) ;
213+ let validator =
214+ KeyValidator :: new ( & HashConfig :: default ( ) , true , dummy_key, dummy_hash) . unwrap ( ) ;
191215
192216 // Test at boundary (512 chars - should pass)
193217 let max_key = "a" . repeat ( 512 ) ;
@@ -207,7 +231,9 @@ mod tests {
207231 let hasher = KeyHasher :: new ( HashConfig :: default ( ) ) ;
208232 let valid_hash = hasher. hash ( & valid_key) . unwrap ( ) ;
209233
210- let validator = KeyValidator :: new ( & HashConfig :: default ( ) , true ) . unwrap ( ) ;
234+ let ( dummy_key, dummy_hash) = dummy_key_and_hash ( ) ;
235+ let validator =
236+ KeyValidator :: new ( & HashConfig :: default ( ) , true , dummy_key, dummy_hash) . unwrap ( ) ;
211237
212238 let result1 = validator. verify ( "wrong_key" , valid_hash. as_ref ( ) ) ;
213239 assert ! ( result1. is_ok( ) ) ;
0 commit comments