Skip to main content

dnsy_core/
key.rs

1//! Key roles and key lifecycle states, shared by the signer (`dnsy-dnssec`) and the key store
2//! (`dnsy-keys`).
3
4use std::fmt;
5
6/// What a key signs.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub enum KeyRole {
9    /// Combined signing key: signs the DNSKEY `RRset` and the zone, carries the SEP flag.
10    Csk,
11    /// Key signing key: signs the DNSKEY `RRset`, carries the SEP flag.
12    Ksk,
13    /// Zone signing key: signs everything else.
14    Zsk,
15}
16
17impl KeyRole {
18    #[must_use]
19    pub fn as_str(self) -> &'static str {
20        match self {
21            KeyRole::Csk => "csk",
22            KeyRole::Ksk => "ksk",
23            KeyRole::Zsk => "zsk",
24        }
25    }
26
27    #[must_use]
28    pub fn parse(s: &str) -> Option<KeyRole> {
29        match s {
30            "csk" => Some(KeyRole::Csk),
31            "ksk" => Some(KeyRole::Ksk),
32            "zsk" => Some(KeyRole::Zsk),
33            _ => None,
34        }
35    }
36
37    /// True when the DNSKEY carries the SEP flag and a DS is published at the parent.
38    #[must_use]
39    pub fn is_sep(self) -> bool {
40        !matches!(self, KeyRole::Zsk)
41    }
42}
43
44impl fmt::Display for KeyRole {
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        f.write_str(self.as_str())
47    }
48}
49
50/// Lifecycle state of a key.
51#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
52pub enum KeyState {
53    /// Created, not yet in the DNSKEY `RRset`.
54    Generated,
55    /// In the DNSKEY `RRset` but not signing.
56    Published,
57    /// Signing.
58    Active,
59    /// No longer signing, still in the DNSKEY `RRset` for validators holding old signatures.
60    Retired,
61    /// Withdrawn; the private key is destroyed.
62    Removed,
63}
64
65impl KeyState {
66    #[must_use]
67    pub fn as_str(self) -> &'static str {
68        match self {
69            KeyState::Generated => "generated",
70            KeyState::Published => "published",
71            KeyState::Active => "active",
72            KeyState::Retired => "retired",
73            KeyState::Removed => "removed",
74        }
75    }
76
77    #[must_use]
78    pub fn parse(s: &str) -> Option<KeyState> {
79        match s {
80            "generated" => Some(KeyState::Generated),
81            "published" => Some(KeyState::Published),
82            "active" => Some(KeyState::Active),
83            "retired" => Some(KeyState::Retired),
84            "removed" => Some(KeyState::Removed),
85            _ => None,
86        }
87    }
88
89    /// True when the DNSKEY belongs in the zone's DNSKEY `RRset`.
90    #[must_use]
91    pub fn is_published(self) -> bool {
92        matches!(
93            self,
94            KeyState::Published | KeyState::Active | KeyState::Retired
95        )
96    }
97}
98
99impl fmt::Display for KeyState {
100    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101        f.write_str(self.as_str())
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn names_round_trip() {
111        for r in [KeyRole::Csk, KeyRole::Ksk, KeyRole::Zsk] {
112            assert_eq!(KeyRole::parse(r.as_str()), Some(r));
113            assert_eq!(r.to_string(), r.as_str());
114        }
115        for s in [
116            KeyState::Generated,
117            KeyState::Published,
118            KeyState::Active,
119            KeyState::Retired,
120            KeyState::Removed,
121        ] {
122            assert_eq!(KeyState::parse(s.as_str()), Some(s));
123            assert_eq!(s.to_string(), s.as_str());
124        }
125        assert_eq!(KeyRole::parse("x"), None);
126        assert_eq!(KeyState::parse("x"), None);
127        assert!(KeyRole::Ksk.is_sep() && KeyRole::Csk.is_sep() && !KeyRole::Zsk.is_sep());
128        assert!(KeyState::Retired.is_published() && !KeyState::Generated.is_published());
129        assert!(!KeyState::Removed.is_published());
130    }
131}