Skip to main content

macula_rust/
profile.rs

1//! The post-quantum crypto profile a node runs, the counterpart of macula's
2//! `macula_crypto_profile` and macula-go's `profile`. A realm runs one profile
3//! and every node in it is configured with that one: there is no default, no
4//! negotiation and no classical fallback.
5
6use std::fmt;
7
8/// A crypto profile, by the name a node is configured with.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub enum Profile {
11    /// The CNSA 2.0 profile: ML-DSA-87 signatures, with no classical half.
12    PqPure,
13    /// The hybrid profile, the fleet's: ML-DSA-87 alone in TLS, and every
14    /// other signature the LAMPS composite id-MLDSA87-RSA4096-PSS-SHA512,
15    /// valid only if both halves verify.
16    PqHybrid,
17}
18
19/// A configured value that names no profile: empty, or not exactly one of
20/// `pq_pure` and `pq_hybrid`.
21#[derive(Debug, Clone, PartialEq, Eq)]
22pub enum ProfileError {
23    /// No profile is configured.
24    Missing,
25    /// The value is not exactly one known profile.
26    Unknown(String),
27}
28
29impl fmt::Display for ProfileError {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        match self {
32            ProfileError::Missing => f.write_str("no crypto profile is configured"),
33            ProfileError::Unknown(value) => write!(f, "not a known crypto profile: {value:?}"),
34        }
35    }
36}
37
38impl std::error::Error for ProfileError {}
39
40impl Profile {
41    /// The profile `value` names, exactly.
42    pub fn parse(value: &str) -> Result<Profile, ProfileError> {
43        match value {
44            "" => Err(ProfileError::Missing),
45            "pq_pure" => Ok(Profile::PqPure),
46            "pq_hybrid" => Ok(Profile::PqHybrid),
47            other => Err(ProfileError::Unknown(other.to_owned())),
48        }
49    }
50
51    /// The name a node is configured with, and that node_ids are derived
52    /// over.
53    pub fn name(self) -> &'static str {
54        match self {
55            Profile::PqPure => "pq_pure",
56            Profile::PqHybrid => "pq_hybrid",
57        }
58    }
59
60    /// Whether identity, CONNECT and status signatures pair ML-DSA-87 with
61    /// RSA-PSS-4096.
62    pub fn hybrid(self) -> bool {
63        self == Profile::PqHybrid
64    }
65
66    /// The signature algorithm's name, as signed structures carry it.
67    pub fn sig_alg(self) -> &'static str {
68        match self {
69            Profile::PqPure => "ML-DSA-87",
70            Profile::PqHybrid => "ML-DSA-87-PS384",
71        }
72    }
73}
74
75impl fmt::Display for Profile {
76    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77        f.write_str(self.name())
78    }
79}