Skip to main content

cratestack_cose/
alg.rs

1//! The closed set of algorithms and message structures (ADR 0006 §3, as
2//! amended while scoping P0).
3
4/// A COSE algorithm this crate signs or verifies with.
5///
6/// **Deviation from the ADR 0006 §1 sketch**, which types `alg` as
7/// `coset::iana::Algorithm`: that is an open registry of about a hundred
8/// values, so every verifier would need its own allowlist and every
9/// resolver would be asked about algorithms it can never honour. Here the
10/// allowlist *is* the type. [`CoseAlg::from_id`] is the only way a wire
11/// value becomes an algorithm, and it knows four ids. The deprecated
12/// polymorphic ids -8 (EdDSA) and -7 (ES256), which RFC 9864 replaces with
13/// the fully specified -19 and -9, are rejected like any other unknown id:
14/// nothing has shipped with them, so there is nothing to stay compatible
15/// with.
16///
17/// `#[non_exhaustive]` because Q5 reserves a hybrid post-quantum `alg`
18/// value: adding it is a new variant, not a wire break.
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
20#[non_exhaustive]
21pub enum CoseAlg {
22    /// `-19`, Ed25519 (RFC 9864). The default for COSE_Sign1 (Q2).
23    Ed25519,
24    /// `-9`, ESP256: ECDSA over P-256 with SHA-256 (RFC 9864). Opt-in, for
25    /// keys held in a KMS, an HSM or as non-extractable WebCrypto keys (Q2).
26    Esp256,
27    /// `4`, HMAC 256/64: HMAC-SHA-256 truncated to its first 8 bytes. Safe
28    /// only because a forgery needs online attempts, which are rate
29    /// limited (§3).
30    Hmac256_64,
31    /// `5`, HMAC 256/256.
32    Hmac256_256,
33}
34
35impl CoseAlg {
36    /// Every accepted algorithm, for tests and for exhaustive tables. A
37    /// slice, not an array, so that Q5's reserved hybrid algorithm can be
38    /// added without changing this constant's type.
39    pub const ALL: &'static [CoseAlg] = &[
40        CoseAlg::Ed25519,
41        CoseAlg::Esp256,
42        CoseAlg::Hmac256_64,
43        CoseAlg::Hmac256_256,
44    ];
45
46    /// The IANA "COSE Algorithms" value written into protected label 1.
47    pub const fn id(self) -> i64 {
48        match self {
49            CoseAlg::Ed25519 => -19,
50            CoseAlg::Esp256 => -9,
51            CoseAlg::Hmac256_64 => 4,
52            CoseAlg::Hmac256_256 => 5,
53        }
54    }
55
56    /// The algorithm for a wire value, or `None` for anything outside the
57    /// allowlist, including -8 and -7.
58    pub const fn from_id(id: i64) -> Option<CoseAlg> {
59        match id {
60            -19 => Some(CoseAlg::Ed25519),
61            -9 => Some(CoseAlg::Esp256),
62            4 => Some(CoseAlg::Hmac256_64),
63            5 => Some(CoseAlg::Hmac256_256),
64            _ => None,
65        }
66    }
67
68    /// The message structure this algorithm belongs to. A tag-17 message
69    /// carrying a signature algorithm, or a tag-18 message carrying a MAC
70    /// algorithm, is rejected before any key is looked up.
71    pub const fn mode(self) -> CoseMode {
72        match self {
73            CoseAlg::Ed25519 | CoseAlg::Esp256 => CoseMode::Sign1,
74            CoseAlg::Hmac256_64 | CoseAlg::Hmac256_256 => CoseMode::Mac0,
75        }
76    }
77
78    /// The exact length of the signature or tag, in bytes. Q5: nothing in
79    /// this crate assumes 64; every size is read from here. Both signature
80    /// algorithms happen to be 64 (Ed25519, and ESP256's fixed-size `r‖s`).
81    pub const fn signature_len(self) -> usize {
82        match self {
83            CoseAlg::Ed25519 | CoseAlg::Esp256 => 64,
84            CoseAlg::Hmac256_64 => 8,
85            CoseAlg::Hmac256_256 => 32,
86        }
87    }
88}
89
90/// The unary message structure: COSE_Sign1 or COSE_Mac0.
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
92pub enum CoseMode {
93    /// COSE_Sign1, CBOR tag 18. Asymmetric: the verifier cannot forge.
94    Sign1,
95    /// COSE_Mac0, CBOR tag 17. Symmetric: service to service inside one
96    /// trust domain only (§3, "Why not Mac0 for devices?").
97    Mac0,
98}
99
100impl CoseMode {
101    /// The CBOR tag every message of this mode carries. Messages are
102    /// always tagged (§3), so the structure is never inferred from context.
103    pub const fn tag(self) -> u64 {
104        match self {
105            CoseMode::Sign1 => 18,
106            CoseMode::Mac0 => 17,
107        }
108    }
109
110    /// The `Content-Type` of a unary body in this mode (§2, RFC 9052 §2).
111    pub const fn media_type(self) -> &'static str {
112        match self {
113            CoseMode::Sign1 => "application/cose; cose-type=\"cose-sign1\"",
114            CoseMode::Mac0 => "application/cose; cose-type=\"cose-mac0\"",
115        }
116    }
117
118    /// The context string that opens the `Sig_structure` / `MAC_structure`
119    /// (RFC 9052 §4.4, §6.3).
120    pub(crate) const fn context(self) -> &'static str {
121        match self {
122            CoseMode::Sign1 => "Signature1",
123            CoseMode::Mac0 => "MAC0",
124        }
125    }
126}