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}