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}