use crate::buffer::Buffer;
use crate::iterable_enum;
use crate::utils::{bytes::ObjectBytes, result::ResultExt};
use rquickjs::{
class::Trace, function::Opt, prelude::This, Class, Ctx, IntoJs, JsLifetime, Result, Value,
};
use super::encoded_bytes;
use crate::crypto::provider::{CryptoError, CryptoProvider, HmacProvider, SimpleDigest};
use crate::crypto::CRYPTO_PROVIDER;
#[derive(Debug, Clone, Copy)]
pub enum HashAlgorithm {
Md5,
Sha1,
Sha256,
Sha384,
Sha512,
}
iterable_enum!(HashAlgorithm, Md5, Sha1, Sha256, Sha384, Sha512);
impl TryFrom<&str> for HashAlgorithm {
type Error = String;
fn try_from(s: &str) -> std::result::Result<Self, Self::Error> {
Ok(match s.to_ascii_uppercase().as_str() {
"MD5" => HashAlgorithm::Md5,
"MD-5" => HashAlgorithm::Md5,
"SHA1" => HashAlgorithm::Sha1,
"SHA-1" => HashAlgorithm::Sha1,
"SHA256" => HashAlgorithm::Sha256,
"SHA-256" => HashAlgorithm::Sha256,
"SHA384" => HashAlgorithm::Sha384,
"SHA-384" => HashAlgorithm::Sha384,
"SHA512" => HashAlgorithm::Sha512,
"SHA-512" => HashAlgorithm::Sha512,
_ => return Err(["'", s, "' not available"].concat()),
})
}
}
impl HashAlgorithm {
pub fn class_name(&self) -> &'static str {
match self {
HashAlgorithm::Md5 => "Md5",
HashAlgorithm::Sha1 => "Sha1",
HashAlgorithm::Sha256 => "Sha256",
HashAlgorithm::Sha384 => "Sha384",
HashAlgorithm::Sha512 => "Sha512",
}
}
pub fn as_str(&self) -> &'static str {
match self {
HashAlgorithm::Md5 => "MD5",
HashAlgorithm::Sha1 => "SHA-1",
HashAlgorithm::Sha256 => "SHA-256",
HashAlgorithm::Sha384 => "SHA-384",
HashAlgorithm::Sha512 => "SHA-512",
}
}
pub fn as_numeric_str(&self) -> &'static str {
match self {
HashAlgorithm::Md5 => "md5",
HashAlgorithm::Sha1 => "1",
HashAlgorithm::Sha256 => "256",
HashAlgorithm::Sha384 => "384",
HashAlgorithm::Sha512 => "512",
}
}
pub fn digest_len(&self) -> usize {
match self {
HashAlgorithm::Md5 => 16,
HashAlgorithm::Sha1 => 20,
HashAlgorithm::Sha256 => 32,
HashAlgorithm::Sha384 => 48,
HashAlgorithm::Sha512 => 64,
}
}
pub fn block_len(&self) -> usize {
match self {
HashAlgorithm::Md5 => 64,
HashAlgorithm::Sha1 => 64,
HashAlgorithm::Sha256 => 64,
HashAlgorithm::Sha384 => 128,
HashAlgorithm::Sha512 => 128,
}
}
pub(super) fn from_strict_str(s: &str) -> std::result::Result<Self, CryptoError> {
Ok(match s {
"SHA-1" => HashAlgorithm::Sha1,
"SHA-256" => HashAlgorithm::Sha256,
"SHA-384" => HashAlgorithm::Sha384,
"SHA-512" => HashAlgorithm::Sha512,
_ => return Err(CryptoError::UnsupportedAlgorithm),
})
}
}
type ProviderDigest = <crate::crypto::provider::DefaultProvider as CryptoProvider>::Digest;
type ProviderHmac = <crate::crypto::provider::DefaultProvider as CryptoProvider>::Hmac;
#[derive(Trace, JsLifetime)]
#[rquickjs::class]
pub struct Hash {
#[qjs(skip_trace)]
digest: Option<ProviderDigest>,
#[qjs(skip_trace)]
hmac: Option<ProviderHmac>,
}
impl Hash {
pub fn new(ctx: Ctx<'_>, algorithm: String) -> Result<Self> {
let algorithm = HashAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?;
Ok(Self {
digest: Some(CRYPTO_PROVIDER.digest(algorithm)),
hmac: None,
})
}
pub fn new_hmac<'js>(
ctx: Ctx<'js>,
algorithm: String,
secret: ObjectBytes<'js>,
) -> Result<Self> {
let algorithm = HashAlgorithm::try_from(algorithm.as_str()).or_throw(&ctx)?;
let key = secret.as_bytes(&ctx)?;
Ok(Self {
digest: None,
hmac: Some(CRYPTO_PROVIDER.hmac(algorithm, key)),
})
}
fn do_update(&mut self, data: &[u8]) {
if let Some(ref mut d) = self.digest {
d.update(data);
} else if let Some(ref mut h) = self.hmac {
h.update(data);
}
}
fn do_finalize(&mut self) -> Option<Vec<u8>> {
if let Some(d) = self.digest.take() {
Some(d.finalize())
} else {
self.hmac.take().map(|h| h.finalize())
}
}
}
#[rquickjs::methods]
impl Hash {
#[qjs(rename = "digest")]
fn hash_digest<'js>(&mut self, ctx: Ctx<'js>, encoding: Opt<String>) -> Result<Value<'js>> {
let result = self
.do_finalize()
.ok_or_else(|| rquickjs::Exception::throw_message(&ctx, "Digest already called"))?;
let Some(encoding) = encoding.0 else {
return Buffer(result).into_js(&ctx);
};
match encoded_bytes(&ctx, &result, &encoding)? {
Some(encoded) => Ok(encoded),
None => Buffer(result).into_js(&ctx),
}
}
#[qjs(rename = "update")]
fn hash_update<'js>(
this: This<Class<'js, Self>>,
ctx: Ctx<'js>,
bytes: ObjectBytes<'js>,
) -> Result<Class<'js, Self>> {
let bytes = bytes.as_bytes(&ctx)?;
this.0.borrow_mut().do_update(bytes);
Ok(this.0)
}
}
#[derive(Trace, JsLifetime)]
#[rquickjs::class]
pub struct Hmac {
#[qjs(skip_trace)]
hash: Hash,
}
impl Hmac {
pub fn new<'js>(ctx: Ctx<'js>, algorithm: String, key_value: ObjectBytes<'js>) -> Result<Self> {
Ok(Self {
hash: Hash::new_hmac(ctx, algorithm, key_value)?,
})
}
}
#[rquickjs::methods]
impl Hmac {
fn digest<'js>(&mut self, ctx: Ctx<'js>, encoding: Opt<String>) -> Result<Value<'js>> {
self.hash.hash_digest(ctx, encoding)
}
fn update<'js>(
this: This<Class<'js, Self>>,
ctx: Ctx<'js>,
bytes: ObjectBytes<'js>,
) -> Result<Class<'js, Self>> {
let bytes = bytes.as_bytes(&ctx)?;
this.0.borrow_mut().hash.do_update(bytes);
Ok(this.0)
}
}