Skip to main content

tako_rs_extractors/cookie_signed/
key.rs

1use cookie::Key;
2
3/// Key ring for rotation-aware cookie signing/verification.
4///
5/// `active` is used to sign new cookies; `previous` keys are tried for
6/// verification only, letting old cookies remain valid through a rotation.
7/// Each key carries a string `kid` so callers can log which key admitted a
8/// given cookie when [`CookieSigned::get_with_kid`](super::CookieSigned::get_with_kid) is used.
9#[derive(Clone)]
10pub struct KeyRing {
11  pub(crate) active_kid: String,
12  pub(crate) active: Key,
13  pub(crate) previous: Vec<(String, Key)>,
14}
15
16impl KeyRing {
17  /// Build a key ring with a single active key.
18  pub fn new(active_kid: impl Into<String>, active: Key) -> Self {
19    Self {
20      active_kid: active_kid.into(),
21      active,
22      previous: Vec::new(),
23    }
24  }
25
26  /// Add a previous key. Verification tries the active key first, then each
27  /// previous key in insertion order.
28  pub fn with_previous(mut self, kid: impl Into<String>, key: Key) -> Self {
29    self.previous.push((kid.into(), key));
30    self
31  }
32
33  /// Removes a previous key by `kid`. Cookies signed with that key will no
34  /// longer be accepted — call this when a key has been disclosed or
35  /// rotated past its retention window. Returns `true` if a key was removed.
36  pub fn revoke(&mut self, kid: &str) -> bool {
37    let before = self.previous.len();
38    self.previous.retain(|(k, _)| k != kid);
39    before != self.previous.len()
40  }
41
42  /// Returns the list of currently-trusted previous key ids in verification
43  /// order. Use this to confirm a revocation took effect or to plan a key
44  /// rotation.
45  pub fn previous_kids(&self) -> impl Iterator<Item = &str> {
46    self.previous.iter().map(|(k, _)| k.as_str())
47  }
48
49  /// Borrow the active key.
50  pub fn active(&self) -> &Key {
51    &self.active
52  }
53
54  /// The active key id.
55  pub fn active_kid(&self) -> &str {
56    &self.active_kid
57  }
58}
59
60#[cfg(test)]
61mod tests {
62  use super::*;
63
64  #[test]
65  fn keyring_revoke_removes_previous_key() {
66    let active = Key::generate();
67    let old = Key::generate();
68    let older = Key::generate();
69    let mut ring = KeyRing::new("v3", active)
70      .with_previous("v2", old)
71      .with_previous("v1", older);
72
73    assert_eq!(ring.previous_kids().collect::<Vec<_>>(), vec!["v2", "v1"]);
74
75    assert!(ring.revoke("v2"));
76    assert_eq!(ring.previous_kids().collect::<Vec<_>>(), vec!["v1"]);
77
78    // No-op for non-existent kid.
79    assert!(!ring.revoke("v99"));
80
81    assert!(ring.revoke("v1"));
82    assert_eq!(ring.previous_kids().count(), 0);
83  }
84}