use std::{convert::TryFrom, fmt};
use aliri_base64::{Base64Url, Base64UrlRef};
use ring::rand::SecureRandom;
use serde::{Deserialize, Serialize};
use crate::{error, jws};
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[must_use]
pub struct Hmac {
#[serde(rename = "k")]
secret: Base64Url,
}
impl fmt::Debug for Hmac {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("Hmac { secret }")
}
}
impl Hmac {
pub fn new(secret: impl Into<Base64Url>) -> Self {
let secret = secret.into();
Self { secret }
}
pub fn generate(alg: SigningAlgorithm) -> Result<Self, error::Unexpected> {
Self::generate_with_rng(alg, &*super::CRATE_RNG)
}
pub fn generate_with_rng(
alg: SigningAlgorithm,
rng: &dyn SecureRandom,
) -> Result<Self, error::Unexpected> {
let bytes = alg.recommended_key_size();
let mut secret = Base64Url::from_raw(vec![0; bytes]);
rng.fill(secret.as_mut_slice())
.map_err(|_| error::unexpected("random number generator failure"))?;
Ok(Self { secret })
}
#[cfg_attr(not(test), allow(dead_code))]
pub(crate) fn secret(&self) -> &Base64UrlRef {
&self.secret
}
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
#[allow(clippy::upper_case_acronyms)]
#[non_exhaustive]
pub enum SigningAlgorithm {
HS256,
HS384,
HS512,
}
impl SigningAlgorithm {
#[must_use]
fn recommended_key_size(self) -> usize {
match self {
Self::HS256 => 256 / 8,
Self::HS384 => 384 / 8,
Self::HS512 => 512 / 8,
}
}
#[must_use]
pub fn signature_size(self) -> usize {
match self {
Self::HS256 => 256 / 8,
Self::HS384 => 384 / 8,
Self::HS512 => 512 / 8,
}
}
fn into_ring_algorithm(self) -> ring::hmac::Algorithm {
match self {
SigningAlgorithm::HS256 => ring::hmac::HMAC_SHA256,
SigningAlgorithm::HS384 => ring::hmac::HMAC_SHA384,
SigningAlgorithm::HS512 => ring::hmac::HMAC_SHA512,
}
}
}
impl From<SigningAlgorithm> for jws::Algorithm {
fn from(alg: SigningAlgorithm) -> Self {
Self::Hmac(alg)
}
}
impl TryFrom<jws::Algorithm> for SigningAlgorithm {
type Error = error::IncompatibleAlgorithm;
fn try_from(alg: jws::Algorithm) -> Result<Self, Self::Error> {
match alg {
jws::Algorithm::Hmac(alg) => Ok(alg),
#[allow(unreachable_patterns)]
_ => Err(error::incompatible_algorithm(alg)),
}
}
}
impl jws::Signer for Hmac {
type Algorithm = SigningAlgorithm;
type Error = std::convert::Infallible;
fn can_sign(&self, _alg: Self::Algorithm) -> bool {
true
}
fn sign(&self, alg: Self::Algorithm, data: &[u8]) -> Result<Vec<u8>, Self::Error> {
let key = ring::hmac::Key::new(alg.into_ring_algorithm(), self.secret.as_slice());
let digest = ring::hmac::sign(&key, data);
Ok(digest.as_ref().to_owned())
}
}
impl jws::Verifier for Hmac {
type Algorithm = SigningAlgorithm;
type Error = error::SignatureMismatch;
fn can_verify(&self, _alg: Self::Algorithm) -> bool {
true
}
fn verify(
&self,
alg: Self::Algorithm,
data: &[u8],
signature: &[u8],
) -> Result<(), Self::Error> {
let key = ring::hmac::Key::new(alg.into_ring_algorithm(), self.secret.as_slice());
ring::hmac::verify(&key, data, signature).map_err(|_| error::signature_mismatch())
}
}
impl fmt::Display for SigningAlgorithm {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
Self::HS256 => "HS256",
Self::HS384 => "HS384",
Self::HS512 => "HS512",
};
f.write_str(s)
}
}