Skip to main content

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}