one_time/
totp.rs

1use hmac::digest::{
2    Update, BlockInput, FixedOutput, Reset,
3    generic_array::ArrayLength,
4};
5use crate::error::Error;
6
7/// Calculate the TOTP code for the current system time.
8#[cfg(any(feature = "std", feature = "libc"))]
9pub fn totp_now<D>(key: &[u8]) -> Result<u64, Error>
10    where D: Update + BlockInput + FixedOutput + Reset + Default + Clone,
11          D::BlockSize: ArrayLength<u8>,
12{
13    totp::<D>(&TotpOptions::with_key_now(key))
14}
15
16/// Calculate the TOTP code for the given options.
17pub fn totp<D>(options: &TotpOptions) -> Result<u64, Error>
18    where D: Update + BlockInput + FixedOutput + Reset + Default + Clone,
19          D::BlockSize: ArrayLength<u8>,
20{
21    if options.step == 0 {
22        return Err(Error::InvalidStep);
23    }
24
25    if options.timestamp < options.epoch {
26        return Err(Error::InvalidTimeOrEpoch);
27    }
28
29    let counter = (options.timestamp - options.epoch) / options.step;
30    crate::hotp::<D>(options.key, counter, options.digits)
31}
32
33/// Options for generating TOTP codes.
34pub struct TotpOptions<'a> {
35    /// The secret, shared key to use to generate the code.
36    pub key: &'a [u8],
37    /// The number of digits that should be in the code.
38    ///
39    /// Defaults to `6`.
40    pub digits: u32,
41    /// The number of seconds in a generation window.
42    ///
43    /// Defaults to `30`.
44    pub step: u64,
45    /// The seconds from the Unix epoch to use as `T0`.
46    ///
47    /// Defaults to `0`.
48    pub epoch: u64,
49    /// The timestamp to generate the TOTP code at.
50    ///
51    /// Defaults to the current system time if `std` or `libc` are enabled.
52    pub timestamp: u64,
53}
54
55impl<'a> TotpOptions<'a> {
56    /// Create a `TotpOptions` with the given key at the current system time.
57    ///
58    /// `digits`, `step`, and `epoch` are all initialised to their default values.
59    #[cfg(any(feature = "std", feature = "libc"))]
60    pub fn with_key_now(key: &'a [u8]) -> Self {
61        Self {
62            key,
63            digits: 6,
64            step: 30,
65            epoch: 0,
66            timestamp: now().unwrap_or_default(),
67        }
68    }
69}
70
71#[cfg(all(feature = "std", not(feature = "libc")))]
72fn now() -> Option<u64> {
73    use std::time::{SystemTime, UNIX_EPOCH};
74
75    SystemTime::now().duration_since(UNIX_EPOCH)
76        .ok()
77        .map(|t| t.as_secs())
78}
79
80#[cfg(feature = "libc")]
81fn now() -> Option<u64> {
82    let time = unsafe {
83        libc::time(core::ptr::null_mut())
84    };
85    if time < 0 {
86        return None;
87    }
88
89    Some(time as u64)
90}