use core::fmt;
use secrecy::{ExposeSecret, SecretSlice};
use subtle::ConstantTimeEq as _;
use super::{
backend::{
interface::{self, hmac::Key as _},
Backend,
},
Error, Result,
};
use crate::{
crypto::backend::interface::Backend as _,
jwa,
jwk::{
self,
symmetric::{FromOctetSequenceError, OctetSequence},
IntoJsonWebKey,
},
jws::{self, Signer},
};
type BackendHmacKey = <Backend as interface::Backend>::HmacKey;
pub trait Variant: crate::sealed::Sealed {
const ALGORITHM: jwa::Hmac;
const OUTPUT_SIZE_BYTES: usize;
}
#[repr(transparent)]
pub struct Signature {
inner: <BackendHmacKey as interface::hmac::Key>::Signature,
}
impl AsRef<[u8]> for Signature {
fn as_ref(&self) -> &[u8] {
self.inner.as_ref()
}
}
impl fmt::Debug for Signature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(AsRef::<[u8]>::as_ref(&self.inner), f)
}
}
pub struct Key<H: Variant> {
inner: BackendHmacKey,
raw_key: SecretSlice<u8>,
_variant: core::marker::PhantomData<H>,
}
impl<H: Variant> Key<H> {
pub fn generate() -> Result<Self> {
let mut key = alloc::vec![0u8; H::OUTPUT_SIZE_BYTES].into_boxed_slice();
Backend::fill_random(&mut key)?;
let key = SecretSlice::from(key);
Self::new_from_key(key)
}
fn new_from_key(key: SecretSlice<u8>) -> Result<Self> {
let inner = BackendHmacKey::new(H::ALGORITHM, key.expose_secret())?;
Ok(Self {
inner,
raw_key: key,
_variant: core::marker::PhantomData,
})
}
}
impl<H: Variant> fmt::Debug for Key<H> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Key")
.field("key", &self.raw_key)
.field("algorithm", &H::ALGORITHM)
.finish()
}
}
impl<H: Variant> From<Key<H>> for jwk::JsonWebKeyType {
fn from(key: Key<H>) -> Self {
jwk::JsonWebKeyType::Symmetric(jwk::SymmetricJsonWebKey::OctetSequence(OctetSequence::new(
key.raw_key,
)))
}
}
impl<H: Variant> crate::sealed::Sealed for Key<H> {}
impl<H: Variant> IntoJsonWebKey for Key<H> {
type Algorithm = ();
type Error = core::convert::Infallible;
fn into_jwk(
self,
alg: Option<impl Into<Self::Algorithm>>,
) -> Result<crate::JsonWebKey, Self::Error> {
let key_ty = jwk::JsonWebKeyType::from(self);
let alg = alg.map(|_| {
jwa::JsonWebAlgorithm::Signing(jwa::JsonWebSigningAlgorithm::Hmac(H::ALGORITHM))
});
Ok(jwk::JsonWebKey::new_with_algorithm(key_ty, alg))
}
}
impl<H: Variant> jws::Verifier for Key<H> {
fn verify(&mut self, msg: &[u8], signature: &[u8]) -> Result<(), jws::VerifyError> {
let signed = self.inner.sign(msg)?;
let valid = bool::from(signature.ct_eq(signed.as_ref()));
if valid {
Ok(())
} else {
Err(jws::VerifyError::InvalidSignature)
}
}
}
impl<H: Variant> Signer<Signature> for Key<H> {
fn sign(&mut self, msg: &[u8]) -> Result<Signature, Error> {
let signature = self.inner.sign(msg)?;
Ok(Signature { inner: signature })
}
fn algorithm(&self) -> jwa::JsonWebSigningAlgorithm {
jwa::JsonWebSigningAlgorithm::Hmac(H::ALGORITHM)
}
}
impl<H: Variant> jwk::FromKey<OctetSequence> for Key<H> {
type Error = FromOctetSequenceError;
fn from_key(
key: OctetSequence,
alg: jwa::JsonWebAlgorithm,
) -> core::result::Result<Self, Self::Error> {
match alg {
jwa::JsonWebAlgorithm::Signing(jwa::JsonWebSigningAlgorithm::Hmac(alg)) => {
if alg != H::ALGORITHM {
return Err(FromOctetSequenceError::InvalidSigningAlgorithm(
jws::InvalidSigningAlgorithmError,
));
}
if key.len() < H::OUTPUT_SIZE_BYTES {
return Err(FromOctetSequenceError::InvalidLength);
}
Ok(Self::new_from_key(key.into_bytes())?)
}
_ => Err(FromOctetSequenceError::InvalidSigningAlgorithm(
jws::InvalidSigningAlgorithmError,
)),
}
}
}
impl<H: Variant> jwk::FromKey<&OctetSequence> for Key<H> {
type Error = FromOctetSequenceError;
fn from_key(key: &OctetSequence, alg: jwa::JsonWebAlgorithm) -> Result<Self, Self::Error> {
match alg {
jwa::JsonWebAlgorithm::Signing(jwa::JsonWebSigningAlgorithm::Hmac(alg)) => {
if alg != H::ALGORITHM {
return Err(FromOctetSequenceError::InvalidSigningAlgorithm(
jws::InvalidSigningAlgorithmError,
));
}
if key.len() < H::OUTPUT_SIZE_BYTES {
return Err(FromOctetSequenceError::InvalidLength);
}
Ok(Self::new_from_key(key.bytes().clone())?)
}
_ => Err(FromOctetSequenceError::InvalidSigningAlgorithm(
jws::InvalidSigningAlgorithmError,
)),
}
}
}
macro_rules! impl_variant {
(#[$doc:meta] $variant:ident = $size:expr) => {
#[$doc]
#[derive(Debug)]
pub enum $variant {}
impl Variant for $variant {
const ALGORITHM: jwa::Hmac = jwa::Hmac::$variant;
const OUTPUT_SIZE_BYTES: usize = $size;
}
impl crate::sealed::Sealed for $variant {}
};
}
impl_variant!(
Hs256 = 256 / 8
);
impl_variant!(
Hs384 = 384 / 8
);
impl_variant!(
Hs512 = 512 / 8
);