use std::{fmt, str::FromStr};
use hmac::{Hmac, Mac};
use miette::Diagnostic;
#[cfg(feature = "serde")]
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use sha1::Sha1;
#[cfg(feature = "sha2")]
use sha2::{Sha256, Sha512};
use thiserror::Error;
use crate::macros::errors;
pub type HmacSha1 = Hmac<Sha1>;
#[cfg(feature = "sha2")]
pub type HmacSha256 = Hmac<Sha256>;
#[cfg(feature = "sha2")]
pub type HmacSha512 = Hmac<Sha512>;
#[derive(Debug, Error, Diagnostic)]
#[error("unknown algorithm `{unknown}`")]
#[diagnostic(code(otp_std::algorithm), help("make sure the algorithm is supported"))]
pub struct Error {
pub unknown: String,
}
impl Error {
pub const fn new(unknown: String) -> Self {
Self { unknown }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Algorithm {
#[default]
Sha1,
#[cfg(feature = "sha2")]
Sha256,
#[cfg(feature = "sha2")]
Sha512,
}
impl Algorithm {
#[cfg(not(feature = "sha2"))]
pub const COUNT: usize = 1;
#[cfg(not(feature = "sha2"))]
pub const ARRAY: [Self; Self::COUNT] = [Self::Sha1];
#[cfg(feature = "sha2")]
pub const COUNT: usize = 3;
#[cfg(feature = "sha2")]
pub const ARRAY: [Self; Self::COUNT] = [Self::Sha1, Self::Sha256, Self::Sha512];
}
pub const SHA1: &str = "SHA1";
pub const SHA1_LENGTH: usize = 20;
#[cfg(feature = "sha2")]
pub const SHA256_LENGTH: usize = 32;
#[cfg(feature = "sha2")]
pub const SHA512_LENGTH: usize = 64;
#[cfg(feature = "sha2")]
pub const SHA256: &str = "SHA256";
#[cfg(feature = "sha2")]
pub const SHA512: &str = "SHA512";
#[cfg(feature = "serde")]
impl Serialize for Algorithm {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.static_str().serialize(serializer)
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for Algorithm {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let string = <&str>::deserialize(deserializer)?;
string.parse().map_err(de::Error::custom)
}
}
impl Algorithm {
pub const fn static_str(self) -> &'static str {
match self {
Self::Sha1 => SHA1,
#[cfg(feature = "sha2")]
Self::Sha256 => SHA256,
#[cfg(feature = "sha2")]
Self::Sha512 => SHA512,
}
}
pub const fn recommended_length(self) -> usize {
match self {
Self::Sha1 => SHA1_LENGTH,
#[cfg(feature = "sha2")]
Self::Sha256 => SHA256_LENGTH,
#[cfg(feature = "sha2")]
Self::Sha512 => SHA512_LENGTH,
}
}
pub fn hmac<K: AsRef<[u8]>, D: AsRef<[u8]>>(self, key: K, data: D) -> Vec<u8> {
match self {
Self::Sha1 => hmac_sha1(key, data),
#[cfg(feature = "sha2")]
Self::Sha256 => hmac_sha256(key, data),
#[cfg(feature = "sha2")]
Self::Sha512 => hmac_sha512(key, data),
}
}
}
errors! {
Type = Error,
Hack = $,
error => new(string => to_owned),
}
impl FromStr for Algorithm {
type Err = Error;
fn from_str(string: &str) -> Result<Self, Self::Err> {
match string {
SHA1 => Ok(Self::Sha1),
#[cfg(feature = "sha2")]
SHA256 => Ok(Self::Sha256),
#[cfg(feature = "sha2")]
SHA512 => Ok(Self::Sha512),
_ => Err(error!(string)),
}
}
}
impl fmt::Display for Algorithm {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
self.static_str().fmt(formatter)
}
}
pub fn hmac<M: Mac, D: AsRef<[u8]>>(mut mac: M, data: D) -> Vec<u8> {
mac.update(data.as_ref());
mac.finalize().into_bytes().to_vec()
}
pub const HMAC_ANY_KEY_LENGTH: &str = "hmac accepts any key length";
pub fn new_hmac_sha1<K: AsRef<[u8]>>(key: K) -> HmacSha1 {
HmacSha1::new_from_slice(key.as_ref()).expect(HMAC_ANY_KEY_LENGTH)
}
pub fn hmac_sha1<K: AsRef<[u8]>, D: AsRef<[u8]>>(key: K, data: D) -> Vec<u8> {
hmac(new_hmac_sha1(key), data)
}
#[cfg(feature = "sha2")]
pub fn new_hmac_sha256<K: AsRef<[u8]>>(key: K) -> HmacSha256 {
HmacSha256::new_from_slice(key.as_ref()).expect(HMAC_ANY_KEY_LENGTH)
}
#[cfg(feature = "sha2")]
pub fn hmac_sha256<K: AsRef<[u8]>, D: AsRef<[u8]>>(key: K, data: D) -> Vec<u8> {
hmac(new_hmac_sha256(key), data)
}
#[cfg(feature = "sha2")]
pub fn new_hmac_sha512<K: AsRef<[u8]>>(key: K) -> HmacSha512 {
HmacSha512::new_from_slice(key.as_ref()).expect(HMAC_ANY_KEY_LENGTH)
}
#[cfg(feature = "sha2")]
pub fn hmac_sha512<K: AsRef<[u8]>, D: AsRef<[u8]>>(key: K, data: D) -> Vec<u8> {
hmac(new_hmac_sha512(key), data)
}