1+ use std:: { hint:: black_box, io:: Read } ;
2+
13use axum:: { headers:: Authorization , TypedHeader } ;
24use http:: HeaderValue ;
5+ use sha2:: Digest ;
36
47pub struct ApiKey ( String ) ;
58
@@ -26,7 +29,50 @@ pub fn accept_auth(
2629 None => return true ,
2730 } ;
2831 match header {
29- Some ( TypedHeader ( Authorization ( ApiKey ( presented) ) ) ) => expected == & presented,
32+ Some ( TypedHeader ( Authorization ( ApiKey ( presented) ) ) ) => const_comp ( expected, & presented) ,
3033 None => false ,
3134 }
3235}
36+
37+ /// Function for comparing two strings in equal time. I.e. the similarity of the strings should
38+ /// have no bearing on the time it takes to compare them.
39+ ///
40+ /// A naive solution to compare two strings for equality would most likely return at the first byte
41+ /// that differs, which means that the more similar two strings are, the longer the execution time
42+ /// for comparing the strings. This would make such naive implementation susceptible to a
43+ /// [timing attack](https://en.wikipedia.org/wiki/Timing_attack), which should ideally be avoided.
44+ fn const_comp ( i0 : impl AsRef < [ u8 ] > , i1 : impl AsRef < [ u8 ] > ) -> bool {
45+ // Hash inputs so we always get equal length when comparing, so we do not risk leaking the
46+ // length of the expected API key via a timing attack.
47+ let h0 = sha2:: Sha384 :: digest ( i0) ;
48+ let h1 = sha2:: Sha384 :: digest ( i1) ;
49+ // The documentation for black box explicitly states that _"this function does not offer any
50+ // guarantees for cryptographic or security purposes"_. But the other two options are to
51+ // either
52+ // 1. Take no measures at all to prevent unwanted optimizations
53+ // 2. Try to hand roll a constant time comparison algorithm implemented in assembler
54+ //
55+ // The first option seems worse than taking no action at all, and the later is not really
56+ // feasible.
57+ black_box (
58+ h0. bytes ( )
59+ . zip ( h1. bytes ( ) )
60+ . fold ( 0 , |acc, ( x, y) | acc | ( x. unwrap ( ) ^ y. unwrap ( ) ) )
61+ == 0 ,
62+ )
63+ }
64+
65+ #[ cfg( test) ]
66+ mod tests {
67+ use crate :: auth:: const_comp;
68+
69+ #[ test]
70+ fn compare_on_equal ( ) {
71+ assert ! ( const_comp( "foo" , "foo" ) ) ;
72+ }
73+
74+ #[ test]
75+ fn compare_on_not_equal ( ) {
76+ assert ! ( !const_comp( "foo" , "bar" ) ) ;
77+ }
78+ }
0 commit comments