macula_rust/seal/
keyring.rs1use std::fmt;
12use std::sync::{Arc, Mutex};
13
14use crate::profile::Profile;
15
16use super::{hex, PrivateKey, SealError, KEY_ID_SIZE};
17
18pub const KEY_LIFETIME_MS: i64 = 24 * 60 * 60 * 1000;
20pub const RETIRED_KEY_KEPT_MS: i64 = 30 * 60 * 1000;
22
23pub type Clock = Arc<dyn Fn() -> i64 + Send + Sync>;
25
26pub struct Keyring {
29 profile: Profile,
30 now: Clock,
31 held: Mutex<Held>,
32}
33
34struct Held {
35 current: Arc<PrivateKey>,
36 current_id: [u8; KEY_ID_SIZE],
37 since: i64,
38 retired: Vec<Retired>,
39}
40
41struct Retired {
42 key: Arc<PrivateKey>,
43 id: [u8; KEY_ID_SIZE],
44 until: i64,
45}
46
47impl fmt::Debug for Keyring {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 let held = self.lock();
50 write!(
51 f,
52 "Keyring({}, current key id {}, {} replaced kept)",
53 self.profile.name(),
54 hex(&held.current_id),
55 held.retired.len()
56 )
57 }
58}
59
60impl Keyring {
61 pub fn new(profile: Profile, now: Clock) -> Result<Keyring, SealError> {
64 let key = PrivateKey::generate(profile)?;
65 let since = now();
66 Ok(Keyring {
67 profile,
68 now,
69 held: Mutex::new(Held {
70 current_id: key.public_key().key_id(),
71 current: Arc::new(key),
72 since,
73 retired: Vec::new(),
74 }),
75 })
76 }
77
78 pub fn system(profile: Profile) -> Result<Keyring, SealError> {
80 Keyring::new(profile, Arc::new(unix_ms))
81 }
82
83 pub fn profile(&self) -> Profile {
85 self.profile
86 }
87
88 pub fn current(&self) -> Arc<PrivateKey> {
92 let mut held = self.lock();
93 self.rotate(&mut held, (self.now)());
94 held.current.clone()
95 }
96
97 pub fn current_id(&self) -> [u8; KEY_ID_SIZE] {
99 let mut held = self.lock();
100 self.rotate(&mut held, (self.now)());
101 held.current_id
102 }
103
104 pub fn find(&self, id: &[u8; KEY_ID_SIZE]) -> Option<Arc<PrivateKey>> {
106 let mut held = self.lock();
107 let now = (self.now)();
108 self.rotate(&mut held, now);
109 if &held.current_id == id {
110 return Some(held.current.clone());
111 }
112 held.retired
113 .iter()
114 .find(|old| &old.id == id && now < old.until)
115 .map(|old| old.key.clone())
116 }
117
118 fn lock(&self) -> std::sync::MutexGuard<'_, Held> {
119 self.held.lock().unwrap_or_else(|p| p.into_inner())
120 }
121
122 fn rotate(&self, held: &mut Held, now: i64) {
126 held.retired.retain(|old| now < old.until);
127 if now - held.since < KEY_LIFETIME_MS {
128 return;
129 }
130 let Ok(next) = PrivateKey::generate(self.profile) else {
134 return;
135 };
136 let next_id = next.public_key().key_id();
137 let replaced = std::mem::replace(&mut held.current, Arc::new(next));
138 held.retired.push(Retired {
139 key: replaced,
140 id: held.current_id,
141 until: now + RETIRED_KEY_KEPT_MS,
142 });
143 held.current_id = next_id;
144 held.since = now;
145 }
146}
147
148fn unix_ms() -> i64 {
149 std::time::SystemTime::now()
150 .duration_since(std::time::UNIX_EPOCH)
151 .map_or(0, |d| d.as_millis() as i64)
152}
153
154#[cfg(test)]
155mod tests {
156 use std::sync::atomic::{AtomicI64, Ordering};
157
158 use super::*;
159 use crate::seal::carried_key_size;
160
161 fn clock(at: &Arc<AtomicI64>) -> Clock {
162 let at = at.clone();
163 Arc::new(move || at.load(Ordering::SeqCst))
164 }
165
166 #[test]
169 fn a_keyring_rotates_and_forgets() {
170 let at = Arc::new(AtomicI64::new(1_790_000_000_000));
171 let ring = Keyring::new(Profile::PqPure, clock(&at)).unwrap();
172 let first = ring.current().public_key().key_id();
173 assert_eq!(ring.current().public_key().key_id(), first);
174
175 at.fetch_add(KEY_LIFETIME_MS - 1000, Ordering::SeqCst);
176 assert_eq!(
177 ring.current().public_key().key_id(),
178 first,
179 "a key rotated before its lifetime"
180 );
181 at.fetch_add(1000, Ordering::SeqCst);
182 let second = ring.current().public_key().key_id();
183 assert_ne!(second, first, "a key outlived its lifetime");
184 assert_eq!(ring.current_id(), second);
185 assert!(
186 ring.find(&first).is_some(),
187 "a replaced key stopped at once"
188 );
189
190 at.fetch_add(RETIRED_KEY_KEPT_MS - 1000, Ordering::SeqCst);
191 assert!(ring.find(&first).is_some(), "a replaced key went early");
192 at.fetch_add(1000, Ordering::SeqCst);
193 assert!(
194 ring.find(&first).is_none(),
195 "a replaced key outlived 30 minutes"
196 );
197 assert_eq!(
198 ring.find(&second).map(|k| k.public_key().key_id()),
199 Some(second)
200 );
201 assert!(ring.find(&[1, 0, 0, 0, 0, 0, 0, 0]).is_none());
202 }
203
204 #[test]
205 fn a_keyring_is_of_its_profile_and_shows_no_key() {
206 for profile in [Profile::PqPure, Profile::PqHybrid] {
207 let ring = Keyring::system(profile).unwrap();
208 assert_eq!(ring.profile(), profile);
209 assert_eq!(
210 ring.current().public_key().carried().len(),
211 carried_key_size(profile)
212 );
213 let shown = format!("{ring:?}");
214 assert!(shown.contains(&hex(&ring.current_id())), "{shown}");
215 assert!(shown.len() < 200, "{shown}");
216 }
217 }
218}