1
//! Constant-time utilities.
2
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
3

            
4
/// Try to find an item in a slice without leaking where and whether the
5
/// item was found.
6
///
7
/// If there is any item `x` in the `array` for which `matches(x)`
8
/// is true, this function will return a reference to one such
9
/// item.  (We don't specify which.)
10
///
11
/// Otherwise, this function returns none.
12
///
13
/// We evaluate `matches` on every item of the array, and try not to
14
/// leak by timing which element (if any) matched.
15
///
16
/// Note that this doesn't necessarily do a constant-time comparison,
17
/// and that it is not constant-time for found/not-found case.
18
18
pub(crate) fn lookup<T, F>(array: &[T], matches: F) -> Option<&T>
19
18
where
20
18
    F: Fn(&T) -> Choice,
21
18
{
22
18
    // ConditionallySelectable isn't implemented for usize, so we need
23
18
    // to use u64.
24
18
    let mut idx: u64 = 0;
25
18
    let mut found: Choice = 0.into();
26

            
27
30
    for (i, x) in array.iter().enumerate() {
28
30
        let equal = matches(x);
29
30
        idx.conditional_assign(&(i as u64), equal);
30
30
        found.conditional_assign(&equal, equal);
31
30
    }
32

            
33
18
    if found.into() {
34
16
        Some(&array[idx as usize])
35
    } else {
36
2
        None
37
    }
38
18
}
39

            
40
/// Convert a boolean into a Choice.
41
///
42
/// This isn't necessarily a good idea or constant-time.
43
62
pub(crate) fn bool_to_choice(v: bool) -> Choice {
44
62
    Choice::from(u8::from(v))
45
62
}
46

            
47
/// Return true if two slices are equal.  Performs its operation in constant
48
/// time, but returns a bool instead of a subtle::Choice.
49
631
pub(crate) fn bytes_eq(a: &[u8], b: &[u8]) -> bool {
50
631
    let choice = a.ct_eq(b);
51
631
    choice.unwrap_u8() == 1
52
631
}
53

            
54
#[cfg(test)]
55
mod test {
56
    #![allow(clippy::unwrap_used)]
57
    #[test]
58
    fn test_bytes_eq() {
59
        use super::bytes_eq;
60
        assert!(bytes_eq(&b"123"[..], &b"1234"[..3]));
61
        assert!(!bytes_eq(&b"123"[..], &b"1234"[..]));
62
        assert!(bytes_eq(&b"45"[..], &b"45"[..]));
63
        assert!(!bytes_eq(&b"hi"[..], &b"45"[..]));
64
        assert!(bytes_eq(&b""[..], &b""[..]));
65
    }
66

            
67
    #[test]
68
    fn test_lookup() {
69
        use super::lookup;
70
        use subtle::ConstantTimeEq;
71
        let items = vec![
72
            "One".to_string(),
73
            "word".to_string(),
74
            "of".to_string(),
75
            "every".to_string(),
76
            "length".to_string(),
77
        ];
78
        let of_word = lookup(&items[..], |i| i.len().ct_eq(&2));
79
        let every_word = lookup(&items[..], |i| i.len().ct_eq(&5));
80
        let no_word = lookup(&items[..], |i| i.len().ct_eq(&99));
81
        assert_eq!(of_word.unwrap(), "of");
82
        assert_eq!(every_word.unwrap(), "every");
83
        assert_eq!(no_word, None);
84
    }
85
}