otp_simple/
util.rs

1//! Additional utilities for working with TOTP and HOTP.
2
3use crate::{hotp, totp};
4use hmac::digest::{
5    block_buffer::Eager,
6    core_api::{BufferKindUser, CoreProxy, FixedOutputCore, UpdateCore},
7    crypto_common::BlockSizeUser,
8    typenum::{IsLess, Le, NonZero, U256},
9    HashMarker,
10};
11
12/// Checks the validity of a TOTP code with a specific amount of skew as specified in [RFC6238](https://www.rfc-editor.org/rfc/rfc6238#section-6).
13pub fn check_totp<D>(
14    time: u64,
15    step: u64,
16    secret: &[u8],
17    digits: u32,
18    skew: (u64, u64),
19    expected: u32,
20) -> bool
21where
22    D: CoreProxy,
23    D::Core: HashMarker
24        + UpdateCore
25        + FixedOutputCore
26        + BufferKindUser<BufferKind = Eager>
27        + Default
28        + Clone,
29    <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
30    Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
31{
32    iter_skew(time, step, skew)
33        .map(|e| totp::<D>(e, step, secret, digits))
34        .any(|code| code == expected)
35}
36
37/// Checks the validity of an HOTP code with a specific amount of skew as specified in [RFC4266](https://www.rfc-editor.org/rfc/rfc4226#section-7.4).
38pub fn check_hotp<D>(time: u64, secret: &[u8], digits: u32, skew: u64, expected: u32) -> bool
39where
40    D: CoreProxy,
41    D::Core: HashMarker
42        + UpdateCore
43        + FixedOutputCore
44        + BufferKindUser<BufferKind = Eager>
45        + Default
46        + Clone,
47    <D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
48    Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
49{
50    iter_skew(time, 1, (0, skew))
51        .map(|e| hotp::<D>(e, secret, digits))
52        .any(|code| code == expected)
53}
54
55fn iter_skew(time: u64, step: u64, skew: (u64, u64)) -> impl Iterator<Item = u64> {
56    let start = time - (step * skew.0);
57    let end = time + (step * skew.1);
58
59    (start..=end).step_by(step as usize)
60}
61
62#[cfg(test)]
63mod test {
64    use crate::util::iter_skew;
65
66    #[test]
67    fn test_iter_skew() {
68        let values = [
69            (
70                (10000, 2, (2, 4)),
71                vec![9996, 9998, 10000, 10002, 10004, 10006, 10008],
72            ),
73            ((100, 1, (0, 5)), vec![100, 101, 102, 103, 104, 105]),
74        ];
75
76        for ((time, step, skew), output) in values {
77            assert_eq!(iter_skew(time, step, skew).collect::<Vec<_>>(), output);
78        }
79    }
80}