Skip to main content

openmls/key_packages/
lifetime.rs

1#[cfg(not(target_arch = "wasm32"))]
2use std::time::{SystemTime, UNIX_EPOCH};
3#[cfg(target_arch = "wasm32")]
4use web_time::{SystemTime, UNIX_EPOCH};
5
6use serde::{Deserialize, Serialize};
7use tls_codec::{TlsDeserialize, TlsDeserializeBytes, TlsSerialize, TlsSize};
8
9use crate::{group::LeafNodeLifetimePolicy, treesync::errors::LifetimeError};
10
11/// This value is used as the default lifetime if no default  lifetime is configured.
12/// The value is in seconds and amounts to 3 * 28 Days, i.e. about 3 months.
13const DEFAULT_KEY_PACKAGE_LIFETIME_SECONDS: u64 = 60 * 60 * 24 * 28 * 3;
14
15/// This value is used as the default amount of time (in seconds) the lifetime
16/// of a `KeyPackage` is extended into the past to allow for skewed clocks. The
17/// value is in seconds and amounts to 1h.
18const DEFAULT_KEY_PACKAGE_LIFETIME_MARGIN_SECONDS: u64 = 60 * 60;
19
20/// The maximum total lifetime range that is acceptable for a leaf node.
21/// The value is in seconds and amounts to 3 * 28 Days, i.e., about 3 months.
22const MAX_LEAF_NODE_LIFETIME_RANGE_SECONDS: u64 =
23    DEFAULT_KEY_PACKAGE_LIFETIME_MARGIN_SECONDS + DEFAULT_KEY_PACKAGE_LIFETIME_SECONDS;
24
25/// The lifetime represents the times between which clients will
26/// consider a KeyPackage valid. This time is represented as an absolute time,
27/// measured in seconds since the Unix epoch (1970-01-01T00:00:00Z).
28/// A client MUST NOT use the data in a KeyPackage for any processing before
29/// the not_before date, or after the not_after date.
30///
31/// Applications MUST define a maximum total lifetime that is acceptable for a
32/// KeyPackage, and reject any KeyPackage where the total lifetime is longer
33/// than this duration.This extension MUST always be present in a KeyPackage.
34///
35/// ```c
36/// // draft-ietf-mls-protocol-16
37/// struct {
38///     uint64 not_before;
39///     uint64 not_after;
40/// } Lifetime;
41/// ```
42#[derive(
43    PartialEq,
44    Eq,
45    Copy,
46    Clone,
47    Debug,
48    TlsSerialize,
49    TlsSize,
50    TlsDeserialize,
51    TlsDeserializeBytes,
52    Serialize,
53    Deserialize,
54)]
55pub struct Lifetime {
56    not_before: u64,
57    not_after: u64,
58}
59
60impl Lifetime {
61    /// Create a new lifetime with lifetime `t` (in seconds).
62    /// Note that the lifetime is extended 1h into the past to adapt to skewed
63    /// clocks, i.e. `not_before` is set to now - 1h.
64    pub fn new(t: u64) -> Self {
65        let lifetime_margin: u64 = DEFAULT_KEY_PACKAGE_LIFETIME_MARGIN_SECONDS;
66        let now = SystemTime::now()
67            .duration_since(UNIX_EPOCH)
68            .expect("SystemTime before UNIX EPOCH!")
69            .as_secs();
70        let not_before = now - lifetime_margin;
71        let not_after = now + t;
72        Self {
73            not_before,
74            not_after,
75        }
76    }
77
78    /// Initialize raw lifetime without skew and explicit dates.
79    pub fn init(not_before: u64, not_after: u64) -> Self {
80        Self {
81            not_before,
82            not_after,
83        }
84    }
85
86    /// Returns a [`LifetimeError`] if the lifetime is not valid.
87    pub fn validate(&self) -> Result<(), LifetimeError> {
88        self.validate_with_time(SystemTime::now())
89    }
90
91    /// Returns a [`LifetimeError`] if the lifetime is not valid under
92    /// `policy`.
93    pub(crate) fn validate_with_policy(
94        &self,
95        policy: LeafNodeLifetimePolicy,
96    ) -> Result<(), LifetimeError> {
97        match policy {
98            LeafNodeLifetimePolicy::Verify => self.validate(),
99            LeafNodeLifetimePolicy::Skip => Ok(()),
100            LeafNodeLifetimePolicy::VerifyAt(unix_seconds) => self.validate_at(unix_seconds),
101        }
102    }
103
104    /// Returns a [`LifetimeError`] if the lifetime is not valid at the given
105    /// time.
106    pub fn validate_with_time(&self, now: SystemTime) -> Result<(), LifetimeError> {
107        let duration_since_unix_epoch = now
108            .duration_since(UNIX_EPOCH)
109            .map_err(|_| LifetimeError::SystemTimeBeforeUnixEpoch)?
110            .as_secs();
111        self.validate_at(duration_since_unix_epoch)
112    }
113
114    /// Returns a [`LifetimeError`] if the lifetime is not valid at `now`, in
115    /// seconds since the Unix epoch.
116    fn validate_at(&self, now: u64) -> Result<(), LifetimeError> {
117        if self.not_after <= now {
118            Err(LifetimeError::Expired {
119                not_after: self.not_after,
120                now,
121            })
122        } else if self.not_before > now {
123            Err(LifetimeError::NotValidYet {
124                not_before: self.not_before,
125                now,
126            })
127        } else {
128            Ok(())
129        }
130    }
131
132    /// ValSem(openmls/annotations#32):
133    /// Applications MUST define a maximum total lifetime that is acceptable for a LeafNode,
134    /// and reject any LeafNode where the total lifetime is longer than this duration.
135    pub fn has_acceptable_range(&self) -> bool {
136        self.not_after.saturating_sub(self.not_before) <= MAX_LEAF_NODE_LIFETIME_RANGE_SECONDS
137    }
138
139    /// Returns the "not before" timestamp of the KeyPackage.
140    pub fn not_before(&self) -> u64 {
141        self.not_before
142    }
143
144    /// Returns the "not after" timestamp of the KeyPackage.
145    pub fn not_after(&self) -> u64 {
146        self.not_after
147    }
148}
149
150impl Default for Lifetime {
151    fn default() -> Self {
152        Lifetime::new(DEFAULT_KEY_PACKAGE_LIFETIME_SECONDS)
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use core::time::Duration;
159    #[cfg(not(target_arch = "wasm32"))]
160    use std::time::SystemTime;
161    #[cfg(target_arch = "wasm32")]
162    use web_time::SystemTime;
163
164    use tls_codec::{Deserialize, Serialize};
165
166    use super::Lifetime;
167
168    #[test]
169    fn lifetime() {
170        // A freshly created extensions must be valid.
171        let ext = Lifetime::default();
172        ext.validate().expect("Default Lifetime should be valid");
173
174        // An extension without lifetime is invalid (waiting for 1 second).
175        let ext = Lifetime::new(0);
176        let now_plus_1s = SystemTime::now() + Duration::from_secs(1);
177        let e = ext
178            .validate_with_time(now_plus_1s)
179            .expect_err("Lifetime should be expired");
180        assert!(matches!(e, super::LifetimeError::Expired { .. }));
181
182        let five_hours_before_now = SystemTime::now() - Duration::from_hours(5);
183        let e = ext
184            .validate_with_time(five_hours_before_now)
185            .expect_err("Lifetime should not be valid yet");
186        assert!(matches!(e, super::LifetimeError::NotValidYet { .. }));
187
188        // Test (de)serializing invalid extension
189        let serialized = ext
190            .tls_serialize_detached()
191            .expect("error encoding life time extension");
192        let ext_deserialized = Lifetime::tls_deserialize(&mut serialized.as_slice())
193            .expect("Error deserializing lifetime");
194        ext_deserialized
195            .validate_with_time(now_plus_1s)
196            .expect_err("Lifetime should be expired");
197    }
198}