cratestack_cose/keys/
hmac.rs1use std::fmt;
4
5use cratestack_core::CratestackError;
6use hmac::{Hmac, KeyInit, Mac};
7use sha2::Sha256;
8use subtle::ConstantTimeEq;
9use zeroize::Zeroizing;
10
11use super::traits::CoseSigner;
12use super::verify_key::CoseVerifyKey;
13use crate::alg::CoseAlg;
14use crate::thumbprint::{self, KID_LEN};
15
16pub const MIN_HMAC_SECRET_LEN: usize = 32;
24
25#[derive(Clone)]
41pub struct HmacSecret(Zeroizing<Vec<u8>>);
42
43impl PartialEq for HmacSecret {
44 fn eq(&self, other: &Self) -> bool {
45 bool::from(self.0.as_slice().ct_eq(other.0.as_slice()))
46 }
47}
48
49impl Eq for HmacSecret {}
50
51impl HmacSecret {
52 pub fn new(secret: impl Into<Vec<u8>>) -> Result<Self, CratestackError> {
56 let secret = Zeroizing::new(secret.into());
57 if secret.len() < MIN_HMAC_SECRET_LEN {
58 return Err(CratestackError::Validation(format!(
59 "HMAC secret must be at least {MIN_HMAC_SECRET_LEN} bytes"
60 )));
61 }
62 Ok(Self(secret))
63 }
64
65 pub(crate) fn expose(&self) -> &[u8] {
66 &self.0
67 }
68}
69
70impl fmt::Debug for HmacSecret {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 f.write_str("HmacSecret(<redacted>)")
73 }
74}
75
76pub(crate) fn hmac_tag(secret: &HmacSecret, alg: CoseAlg, chunks: &[&[u8]]) -> Vec<u8> {
80 let Ok(mut mac) = Hmac::<Sha256>::new_from_slice(secret.expose()) else {
82 return Vec::new();
83 };
84 for chunk in chunks {
85 mac.update(chunk);
86 }
87 let full = mac.finalize().into_bytes();
88 full[..alg.signature_len().min(full.len())].to_vec()
89}
90
91pub(crate) fn is_mac(alg: CoseAlg) -> bool {
93 matches!(alg, CoseAlg::Hmac256_64 | CoseAlg::Hmac256_256)
94}
95
96#[derive(Clone)]
99pub struct HmacSigner {
100 secret: HmacSecret,
101 alg: CoseAlg,
102 kid: [u8; KID_LEN],
103}
104
105impl HmacSigner {
106 pub fn new(alg: CoseAlg, secret: impl Into<Vec<u8>>) -> Result<Self, CratestackError> {
110 Self::from_secret(alg, HmacSecret::new(secret)?)
111 }
112
113 pub fn from_secret(alg: CoseAlg, secret: HmacSecret) -> Result<Self, CratestackError> {
115 if !is_mac(alg) {
116 return Err(CratestackError::Validation(
117 "HmacSigner needs an HMAC algorithm".to_owned(),
118 ));
119 }
120 let kid =
121 thumbprint::kid_from_thumbprint(&thumbprint::symmetric_thumbprint(secret.expose()));
122 Ok(Self { secret, alg, kid })
123 }
124
125 pub fn verify_key(&self) -> CoseVerifyKey {
129 CoseVerifyKey::from_hmac_secret(self.alg, self.secret.clone())
130 }
131}
132
133impl fmt::Debug for HmacSigner {
134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135 f.debug_struct("HmacSigner")
136 .field("alg", &self.alg)
137 .field("kid", &self.kid)
138 .finish_non_exhaustive()
139 }
140}
141
142#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
143#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
144impl CoseSigner for HmacSigner {
145 fn alg(&self) -> CoseAlg {
146 self.alg
147 }
148
149 fn kid(&self) -> &[u8] {
150 &self.kid
151 }
152
153 async fn sign(&self, to_be_signed: &[u8]) -> Result<Vec<u8>, CratestackError> {
154 Ok(hmac_tag(&self.secret, self.alg, &[to_be_signed]))
155 }
156
157 fn sign_chunks(&self, to_be_signed: &[&[u8]]) -> Option<Result<Vec<u8>, CratestackError>> {
160 Some(Ok(hmac_tag(&self.secret, self.alg, to_be_signed)))
161 }
162}