p2panda_encryption/key_bundle/
lifetime.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use std::time::{Duration, SystemTime, UNIX_EPOCH};
4
5use serde::{Deserialize, Serialize};
6use thiserror::Error;
7
8/// Default lifetime which amounts to 3 * 28 Days, i.e. about 3 months.
9const DEFAULT_LIFETIME: u64 = 60 * 60 * 24 * 28 * 3;
10
11/// The lifetime is extended into the past to allow for skewed clocks. The value is in seconds and
12/// amounts to 1h.
13const DEFAULT_LIFETIME_MARGIN: u64 = 60 * 60;
14
15/// Determines the lifetime of a pre-key by defining a time range.
16///
17/// Implementations should reject received messages with expired pre-keys and disallow use.
18#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
19pub struct Lifetime {
20    not_before: u64,
21    not_after: u64,
22}
23
24impl Lifetime {
25    /// Create a new lifetime in seconds from now on.
26    ///
27    /// Note that the lifetime is extended 1h into the past to adapt to skewed clocks, i.e.
28    /// `not_before` is set to `now - 1h`.
29    pub fn new(t: u64) -> Self {
30        let lifetime_margin: u64 = DEFAULT_LIFETIME_MARGIN;
31        let now = SystemTime::now()
32            .duration_since(UNIX_EPOCH)
33            .expect("SystemTime before UNIX EPOCH!")
34            .as_secs();
35        let not_before = now - lifetime_margin;
36        let not_after = now + t;
37        Self {
38            not_before,
39            not_after,
40        }
41    }
42
43    /// Returns true if this lifetime is valid.
44    pub fn verify(&self) -> Result<(), LifetimeError> {
45        let is_valid = match SystemTime::now()
46            .duration_since(UNIX_EPOCH)
47            .map(|duration| duration.as_secs())
48        {
49            Ok(elapsed) => self.not_before < elapsed && elapsed < self.not_after,
50            Err(err) => return Err(LifetimeError::SystemTime(err)),
51        };
52
53        if is_valid {
54            Ok(())
55        } else {
56            Err(LifetimeError::InvalidLifetime)
57        }
58    }
59
60    /// Returns true if this lifetime with an additional window is valid.
61    ///
62    /// This method can be used to calculate if a lifetime _is about_ to expire, given a defined
63    /// time "window".
64    ///
65    /// ```plain
66    ///               [-------]
67    ///               |       |
68    /// Lifetime: [---|----]  |
69    ///               |       |
70    ///              now  + window
71    ///
72    ///         -- t -->
73    /// ```
74    pub fn verify_with_window(&self, window: Duration) -> Result<(), LifetimeError> {
75        let is_valid = match SystemTime::now()
76            .duration_since(UNIX_EPOCH)
77            .map(|duration| duration.as_secs())
78        {
79            Ok(elapsed) => {
80                let elapsed = elapsed + window.as_secs();
81                self.not_before < elapsed && elapsed < self.not_after
82            }
83            Err(err) => return Err(LifetimeError::SystemTime(err)),
84        };
85
86        if is_valid {
87            Ok(())
88        } else {
89            Err(LifetimeError::InvalidLifetime)
90        }
91    }
92}
93
94impl Default for Lifetime {
95    fn default() -> Self {
96        Lifetime::new(DEFAULT_LIFETIME)
97    }
98}
99
100impl Ord for Lifetime {
101    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
102        self.not_after.cmp(&other.not_after)
103    }
104}
105
106impl PartialOrd for Lifetime {
107    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
108        Some(self.cmp(other))
109    }
110}
111
112#[cfg(any(test, feature = "test_utils"))]
113impl Lifetime {
114    pub fn from_range(not_before: u64, not_after: u64) -> Self {
115        Self {
116            not_before,
117            not_after,
118        }
119    }
120}
121
122#[derive(Debug, Error)]
123pub enum LifetimeError {
124    #[error("lifetime of pre-key is not valid")]
125    InvalidLifetime,
126
127    #[error(transparent)]
128    SystemTime(std::time::SystemTimeError),
129}
130
131#[cfg(test)]
132mod tests {
133    use std::time::{Duration, SystemTime, UNIX_EPOCH};
134
135    use super::Lifetime;
136
137    #[test]
138    fn verify() {
139        let now = SystemTime::now()
140            .duration_since(UNIX_EPOCH)
141            .expect("SystemTime before UNIX EPOCH!")
142            .as_secs();
143
144        // Default lifetimes are correct.
145        let lifetime = Lifetime::default();
146        assert!(lifetime.verify().is_ok());
147
148        // Lifetimes of a minute are correct.
149        let lifetime = Lifetime::new(60);
150        assert!(lifetime.verify().is_ok());
151        assert!(
152            lifetime
153                .verify_with_window(Duration::from_secs(120))
154                .is_err()
155        );
156
157        // Test interface for lifetime is correct.
158        let lifetime = Lifetime::from_range(now - 60, now + 60);
159        assert!(lifetime.verify().is_ok());
160
161        // Invalid lifetimes throw an error.
162        let lifetime = Lifetime::from_range(now + 60, now + 60); // too early
163        assert!(lifetime.verify().is_err());
164
165        let lifetime = Lifetime::from_range(now - 120, now - 60); // too late
166        assert!(lifetime.verify().is_err());
167    }
168}