@@ -33,7 +33,6 @@ impl KeyValidator {
3333 const MAX_HASH_LENGTH : usize = 512 ;
3434
3535 pub fn new (
36- _hash_config : & crate :: HashConfig ,
3736 has_checksum : bool ,
3837 dummy_key : SecureString ,
3938 dummy_hash : String ,
@@ -48,27 +47,34 @@ impl KeyValidator {
4847 } )
4948 }
5049
51- fn verify_expiry ( & self , parts : Parts ) -> Result < KeyStatus > {
50+ fn verify_expiry ( & self , parts : Parts , expiry_grace_period : std :: time :: Duration ) -> Result < KeyStatus > {
5251 if let Some ( expiry) = parts. expiry_b64 {
5352 let decoded = URL_SAFE_NO_PAD
5453 . decode ( expiry)
5554 . or ( Err ( Error :: InvalidFormat ) ) ?;
56- let expiry = i64:: from_be_bytes ( decoded. try_into ( ) . or ( Err ( Error :: InvalidFormat ) ) ?) ;
57-
58- // TODO(ARCHITECTURE): time libs are platform dependent.
59- // We should set an `infra` layer and abstract
60- // out these libs.
61- if chrono:: Utc :: now ( ) . timestamp ( ) <= expiry {
62- Ok ( KeyStatus :: Valid )
63- } else {
64- Ok ( KeyStatus :: Invalid )
55+ let expiry_timestamp = i64:: from_be_bytes ( decoded. try_into ( ) . or ( Err ( Error :: InvalidFormat ) ) ?) ;
56+
57+ let current_time = chrono:: Utc :: now ( ) . timestamp ( ) ;
58+ let grace_seconds = expiry_grace_period. as_secs ( ) as i64 ;
59+
60+ // Key is invalid if it expired more than grace_period ago
61+ // This ensures once a key expires beyond the grace period, it stays expired
62+ // even if the clock goes backwards
63+ if expiry_timestamp + grace_seconds < current_time {
64+ return Ok ( KeyStatus :: Invalid ) ;
6565 }
66+ Ok ( KeyStatus :: Valid )
6667 } else {
6768 Ok ( KeyStatus :: Valid )
6869 }
6970 }
7071
71- pub fn verify ( & self , provided_key : & str , stored_hash : & str ) -> Result < KeyStatus > {
72+ pub fn verify (
73+ & self ,
74+ provided_key : & str ,
75+ stored_hash : & str ,
76+ expiry_grace_period : std:: time:: Duration ,
77+ ) -> Result < KeyStatus > {
7278 // Input length validation to prevent DoS attacks
7379 if provided_key. len ( ) > Self :: MAX_KEY_LENGTH {
7480 self . dummy_load ( ) ;
@@ -109,7 +115,7 @@ impl KeyValidator {
109115 // SECURITY: Force evaluation of expiry check BEFORE the match to ensure
110116 // constant-time execution. This prevents the compiler from short-circuiting
111117 // the expiry check when argon_result is Invalid, which would create a timing oracle.
112- let expiry_result = self . verify_expiry ( token_parts) ?;
118+ let expiry_result = self . verify_expiry ( token_parts, expiry_grace_period ) ?;
113119
114120 match ( argon_result, expiry_result) {
115121 ( KeyStatus :: Invalid , _) | ( _, KeyStatus :: Invalid ) => Ok ( KeyStatus :: Invalid ) ,
@@ -151,15 +157,15 @@ mod tests {
151157
152158 let ( dummy_key, dummy_hash) = dummy_key_and_hash ( ) ;
153159 let validator =
154- KeyValidator :: new ( & HashConfig :: default ( ) , true , dummy_key, dummy_hash) . unwrap ( ) ;
160+ KeyValidator :: new ( true , dummy_key, dummy_hash) . unwrap ( ) ;
155161 assert_eq ! (
156162 validator
157- . verify( key. expose_secret( ) , hash. as_ref( ) )
163+ . verify( key. expose_secret( ) , hash. as_ref( ) , std :: time :: Duration :: ZERO )
158164 . unwrap( ) ,
159165 KeyStatus :: Valid
160166 ) ;
161167 assert_eq ! (
162- validator. verify( "wrong_key" , hash. as_ref( ) ) . unwrap( ) ,
168+ validator. verify( "wrong_key" , hash. as_ref( ) , std :: time :: Duration :: ZERO ) . unwrap( ) ,
163169 KeyStatus :: Invalid
164170 ) ;
165171 }
@@ -168,8 +174,8 @@ mod tests {
168174 fn test_invalid_hash_format ( ) {
169175 let ( dummy_key, dummy_hash) = dummy_key_and_hash ( ) ;
170176 let validator =
171- KeyValidator :: new ( & HashConfig :: default ( ) , true , dummy_key, dummy_hash) . unwrap ( ) ;
172- let result = validator. verify ( "any_key" , "invalid_hash" ) ;
177+ KeyValidator :: new ( true , dummy_key, dummy_hash) . unwrap ( ) ;
178+ let result = validator. verify ( "any_key" , "invalid_hash" , std :: time :: Duration :: ZERO ) ;
173179 // After timing oracle fix: invalid hash format returns Ok(Invalid) instead of Err
174180 // to prevent timing-based user enumeration attacks
175181 assert ! ( result. is_ok( ) ) ;
@@ -185,8 +191,8 @@ mod tests {
185191
186192 let ( dummy_key, dummy_hash) = dummy_key_and_hash ( ) ;
187193 let validator =
188- KeyValidator :: new ( & HashConfig :: default ( ) , true , dummy_key, dummy_hash) . unwrap ( ) ;
189- let result = validator. verify ( & oversized_key, hash. as_ref ( ) ) ;
194+ KeyValidator :: new ( true , dummy_key, dummy_hash) . unwrap ( ) ;
195+ let result = validator. verify ( & oversized_key, hash. as_ref ( ) , std :: time :: Duration :: ZERO ) ;
190196 assert ! ( result. is_err( ) ) ;
191197 assert ! ( matches!( result. unwrap_err( ) , Error :: InvalidFormat ) ) ;
192198 }
@@ -197,8 +203,8 @@ mod tests {
197203
198204 let ( dummy_key, dummy_hash) = dummy_key_and_hash ( ) ;
199205 let validator =
200- KeyValidator :: new ( & HashConfig :: default ( ) , true , dummy_key, dummy_hash) . unwrap ( ) ;
201- let result = validator. verify ( "valid_key" , & oversized_hash) ;
206+ KeyValidator :: new ( true , dummy_key, dummy_hash) . unwrap ( ) ;
207+ let result = validator. verify ( "valid_key" , & oversized_hash, std :: time :: Duration :: ZERO ) ;
202208 assert ! ( result. is_err( ) ) ;
203209 assert ! ( matches!( result. unwrap_err( ) , Error :: InvalidFormat ) ) ;
204210 }
@@ -211,16 +217,16 @@ mod tests {
211217
212218 let ( dummy_key, dummy_hash) = dummy_key_and_hash ( ) ;
213219 let validator =
214- KeyValidator :: new ( & HashConfig :: default ( ) , true , dummy_key, dummy_hash) . unwrap ( ) ;
220+ KeyValidator :: new ( true , dummy_key, dummy_hash) . unwrap ( ) ;
215221
216222 // Test at boundary (512 chars - should pass)
217223 let max_key = "a" . repeat ( 512 ) ;
218- let result = validator. verify ( & max_key, hash. as_ref ( ) ) ;
224+ let result = validator. verify ( & max_key, hash. as_ref ( ) , std :: time :: Duration :: ZERO ) ;
219225 assert ! ( result. is_ok( ) ) ; // Should not error on length check
220226
221227 // Test just over boundary (513 chars - should fail)
222228 let over_max_key = "a" . repeat ( 513 ) ;
223- let result = validator. verify ( & over_max_key, hash. as_ref ( ) ) ;
229+ let result = validator. verify ( & over_max_key, hash. as_ref ( ) , std :: time :: Duration :: ZERO ) ;
224230 assert ! ( result. is_err( ) ) ;
225231 assert ! ( matches!( result. unwrap_err( ) , Error :: InvalidFormat ) ) ;
226232 }
@@ -233,17 +239,17 @@ mod tests {
233239
234240 let ( dummy_key, dummy_hash) = dummy_key_and_hash ( ) ;
235241 let validator =
236- KeyValidator :: new ( & HashConfig :: default ( ) , true , dummy_key, dummy_hash) . unwrap ( ) ;
242+ KeyValidator :: new ( true , dummy_key, dummy_hash) . unwrap ( ) ;
237243
238- let result1 = validator. verify ( "wrong_key" , valid_hash. as_ref ( ) ) ;
244+ let result1 = validator. verify ( "wrong_key" , valid_hash. as_ref ( ) , std :: time :: Duration :: ZERO ) ;
239245 assert ! ( result1. is_ok( ) ) ;
240246 assert_eq ! ( result1. unwrap( ) , KeyStatus :: Invalid ) ;
241247
242- let result2 = validator. verify ( valid_key. expose_secret ( ) , "invalid_hash_format" ) ;
248+ let result2 = validator. verify ( valid_key. expose_secret ( ) , "invalid_hash_format" , std :: time :: Duration :: ZERO ) ;
243249 assert ! ( result2. is_ok( ) ) ;
244250 assert_eq ! ( result2. unwrap( ) , KeyStatus :: Invalid ) ;
245251
246- let result3 = validator. verify ( valid_key. expose_secret ( ) , "not even close to valid" ) ;
252+ let result3 = validator. verify ( valid_key. expose_secret ( ) , "not even close to valid" , std :: time :: Duration :: ZERO ) ;
247253 assert ! ( result3. is_ok( ) ) ;
248254 assert_eq ! ( result3. unwrap( ) , KeyStatus :: Invalid ) ;
249255 }
0 commit comments