| // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | |
| // SPDX-License-Identifier: Apache-2.0 | |
| use llrt_buffer::Buffer; | |
| use llrt_utils::{bytes::ObjectBytes, iterable_enum, result::ResultExt}; | |
| use rquickjs::{ | |
| class::Trace, function::Opt, prelude::This, Class, Ctx, IntoJs, JsLifetime, Result, Value, | |
| }; | |
| use super::encoded_bytes; | |
| use crate::provider::{CryptoError, CryptoProvider, HmacProvider, SimpleDigest}; | |
| use crate::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::provider::DefaultProvider as CryptoProvider>::Digest; | |
| type ProviderHmac = <crate::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) | |
| } | |
| } |