cratestack_cose/keys/verify_key.rs
1//! Verification keys.
2
3use std::fmt;
4
5use cratestack_core::CratestackError;
6
7use super::hmac::{HmacSecret, is_mac};
8use crate::alg::CoseAlg;
9use crate::thumbprint::{self, KID_LEN};
10
11/// A key a message may be verified with, bound to exactly one algorithm.
12///
13/// Opaque: built from bytes ([`ed25519`](Self::ed25519),
14/// [`p256_sec1`](Self::p256_sec1), [`hmac`](Self::hmac),
15/// [`hmac_from_secret`](Self::hmac_from_secret)) and read back as
16/// bytes ([`ed25519_bytes`](Self::ed25519_bytes),
17/// [`p256_sec1_uncompressed`](Self::p256_sec1_uncompressed)), so no
18/// `ed25519-dalek` or `p256` type is part of this crate's public API and
19/// either can be upgraded without a breaking change.
20///
21/// Internally an enum over key *types*, not a bag of bytes, so the classic
22/// COSE/JOSE mistake is unrepresentable: an Ed25519 public key can never be
23/// used as an HMAC secret (an attacker who knows the public key could
24/// otherwise MAC anything with it), and a P-256 key can never verify an
25/// Ed25519 signature.
26///
27/// **One key, one algorithm.** An HMAC key carries the algorithm it was
28/// configured for, and [`supports`](Self::supports) matches it exactly:
29/// a secret deployed for HMAC 256/256 never accepts a 64-bit HMAC 256/64
30/// tag, which a sender could otherwise pick to cut a forger's work from
31/// 2²⁵⁶ to 2⁶⁴ online attempts. (The two share a `kid`: RFC 9679's
32/// symmetric thumbprint covers only the secret. A deployment that wants
33/// both lists the secret twice, once per algorithm.)
34///
35/// The RFC 9679 thumbprint is computed once, at construction.
36#[derive(Clone, PartialEq, Eq)]
37pub struct CoseVerifyKey {
38 pub(super) repr: Repr,
39 thumbprint: [u8; 32],
40}
41
42/// The key material. Visible to `verify.rs`, nothing else.
43#[derive(Clone, PartialEq, Eq)]
44pub(super) enum Repr {
45 Ed25519(ed25519_dalek::VerifyingKey),
46 P256(p256::ecdsa::VerifyingKey),
47 Hmac { alg: CoseAlg, secret: HmacSecret },
48}
49
50impl CoseVerifyKey {
51 /// An Ed25519 (`-19`) public key from its 32 bytes. Fails with
52 /// `CratestackError::Validation` for bytes that are not a point
53 /// encoding.
54 pub fn ed25519(public: &[u8; 32]) -> Result<Self, CratestackError> {
55 ed25519_dalek::VerifyingKey::from_bytes(public)
56 .map(Self::from_ed25519)
57 .map_err(|_| CratestackError::Validation("invalid Ed25519 public key".to_owned()))
58 }
59
60 /// A P-256 (`-9`) public key from its SEC1 encoding (compressed or
61 /// not). Fails with `CratestackError::Validation` for a point not on the
62 /// curve.
63 pub fn p256_sec1(sec1: &[u8]) -> Result<Self, CratestackError> {
64 p256::ecdsa::VerifyingKey::from_sec1_bytes(sec1)
65 .map(Self::from_p256)
66 .map_err(|_| CratestackError::Validation("invalid P-256 public key".to_owned()))
67 }
68
69 /// An HMAC secret for exactly `alg` ([`CoseAlg::Hmac256_64`] or
70 /// [`CoseAlg::Hmac256_256`]). Fails with `CratestackError::Validation`
71 /// for another algorithm or a secret below 32 bytes (see
72 /// [`HmacSecret`]).
73 pub fn hmac(alg: CoseAlg, secret: impl Into<Vec<u8>>) -> Result<Self, CratestackError> {
74 if !is_mac(alg) {
75 return Err(CratestackError::Validation(
76 "an HMAC key needs an HMAC algorithm".to_owned(),
77 ));
78 }
79 Self::hmac_from_secret(alg, HmacSecret::new(secret)?)
80 }
81
82 /// As [`hmac`](Self::hmac), from an already validated [`HmacSecret`]
83 /// (the counterpart of `HmacSigner::from_secret`), so a secret loaded
84 /// once can back a signer and a verification key without its bytes
85 /// passing through a plain `Vec` again. Fails with
86 /// `CratestackError::Validation` for a non-HMAC `alg`.
87 pub fn hmac_from_secret(alg: CoseAlg, secret: HmacSecret) -> Result<Self, CratestackError> {
88 if !is_mac(alg) {
89 return Err(CratestackError::Validation(
90 "an HMAC key needs an HMAC algorithm".to_owned(),
91 ));
92 }
93 Ok(Self::from_hmac_secret(alg, secret))
94 }
95
96 pub(crate) fn from_ed25519(key: ed25519_dalek::VerifyingKey) -> Self {
97 let thumbprint = thumbprint::okp_ed25519_thumbprint(key.as_bytes());
98 Self {
99 repr: Repr::Ed25519(key),
100 thumbprint,
101 }
102 }
103
104 pub(crate) fn from_p256(key: p256::ecdsa::VerifyingKey) -> Self {
105 let point = key.to_sec1_point(false);
106 let bytes = point.as_bytes();
107 let mut x = [0; 32];
108 let mut y = [0; 32];
109 // Uncompressed SEC1 is `0x04 ‖ x ‖ y`, 65 bytes, for P-256.
110 x.copy_from_slice(&bytes[1..33]);
111 y.copy_from_slice(&bytes[33..65]);
112 Self {
113 repr: Repr::P256(key),
114 thumbprint: thumbprint::ec2_p256_thumbprint(&x, &y),
115 }
116 }
117
118 /// `alg` must be a MAC algorithm; callers check.
119 pub(crate) fn from_hmac_secret(alg: CoseAlg, secret: HmacSecret) -> Self {
120 let thumbprint = thumbprint::symmetric_thumbprint(secret.expose());
121 Self {
122 repr: Repr::Hmac { alg, secret },
123 thumbprint,
124 }
125 }
126
127 /// The one algorithm this key verifies.
128 pub fn alg(&self) -> CoseAlg {
129 match &self.repr {
130 Repr::Ed25519(_) => CoseAlg::Ed25519,
131 Repr::P256(_) => CoseAlg::Esp256,
132 Repr::Hmac { alg, .. } => *alg,
133 }
134 }
135
136 /// Whether this key can verify messages of `alg`: exactly
137 /// [`alg`](Self::alg).
138 pub fn supports(&self, alg: CoseAlg) -> bool {
139 self.alg() == alg
140 }
141
142 /// The key's RFC 9679 thumbprint.
143 pub fn thumbprint(&self) -> [u8; 32] {
144 self.thumbprint
145 }
146
147 /// The key's `kid` (§3): the first 8 bytes of its thumbprint.
148 pub fn kid(&self) -> [u8; KID_LEN] {
149 thumbprint::kid_from_thumbprint(&self.thumbprint)
150 }
151
152 /// The 32-byte Ed25519 public key, or `None` for another key type.
153 pub fn ed25519_bytes(&self) -> Option<[u8; 32]> {
154 match &self.repr {
155 Repr::Ed25519(key) => Some(key.to_bytes()),
156 _ => None,
157 }
158 }
159
160 /// The uncompressed SEC1 P-256 point (`0x04 ‖ x ‖ y`, 65 bytes), or
161 /// `None` for another key type.
162 pub fn p256_sec1_uncompressed(&self) -> Option<[u8; 65]> {
163 match &self.repr {
164 Repr::P256(key) => key.to_sec1_point(false).as_bytes().try_into().ok(),
165 _ => None,
166 }
167 }
168}
169
170impl fmt::Debug for CoseVerifyKey {
171 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172 f.debug_struct("CoseVerifyKey")
173 .field("alg", &self.alg())
174 .field("kid", &self.kid())
175 .finish()
176 }
177}