diff options
author | Luca Casonato <hello@lcas.dev> | 2024-07-05 10:10:22 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-07-05 10:10:22 +0200 |
commit | 08e5606c3400d3a993c0ce6748901c56fc3db35b (patch) | |
tree | 032d3a09c6d22763ceb703e7908ca159d3d7a809 /ext/node/ops | |
parent | b290fd01f3f5d32f9d010fc719ced0240759c049 (diff) |
fix(ext/node): rewrite digest handling (#24392)
Previously we had many different code paths all
handling digests in different places, all with
wildly different digest support. This commit
rewrites this to use a single digest handling
mechanism for all digest operations.
It adds various aliases for digest algorithms,
like node does. For example
`sha1WithRSAEncryption` is an alias for `sha1`.
It also adds support for `md5-sha1` digests in
various places.
Diffstat (limited to 'ext/node/ops')
-rw-r--r-- | ext/node/ops/crypto/digest.rs | 344 | ||||
-rw-r--r-- | ext/node/ops/crypto/md5_sha1.rs | 99 | ||||
-rw-r--r-- | ext/node/ops/crypto/mod.rs | 163 |
3 files changed, 345 insertions, 261 deletions
diff --git a/ext/node/ops/crypto/digest.rs b/ext/node/ops/crypto/digest.rs index e3a91e338..0a21a395a 100644 --- a/ext/node/ops/crypto/digest.rs +++ b/ext/node/ops/crypto/digest.rs @@ -56,30 +56,110 @@ impl Hasher { } } -pub enum Hash { - Blake2b512(Box<blake2::Blake2b512>), - Blake2s256(Box<blake2::Blake2s256>), - - Md4(Box<md4::Md4>), - Md5(Box<md5::Md5>), - - Ripemd160(Box<ripemd::Ripemd160>), - - Sha1(Box<sha1::Sha1>), - - Sha224(Box<sha2::Sha224>), - Sha256(Box<sha2::Sha256>), - Sha384(Box<sha2::Sha384>), - Sha512(Box<sha2::Sha512>), - Sha512_224(Box<sha2::Sha512_224>), - Sha512_256(Box<sha2::Sha512_256>), +macro_rules! match_fixed_digest { + ($algorithm_name:expr, fn <$type:ident>() $body:block, _ => $other:block) => { + match $algorithm_name { + "blake2b512" => { + type $type = ::blake2::Blake2b512; + $body + } + "blake2s256" => { + type $type = ::blake2::Blake2s256; + $body + } + _ => match_fixed_digest_with_eager_block_buffer!($algorithm_name, fn <$type>() $body, _ => $other) + } + }; +} +pub(crate) use match_fixed_digest; + +macro_rules! match_fixed_digest_with_eager_block_buffer { + ($algorithm_name:expr, fn <$type:ident>() $body:block, _ => $other:block) => { + match $algorithm_name { + "rsa-sm3" | "sm3" | "sm3withrsaencryption" => { + type $type = ::sm3::Sm3; + $body + } + "md5-sha1" => { + type $type = crate::ops::crypto::md5_sha1::Md5Sha1; + $body + } + _ => match_fixed_digest_with_oid!($algorithm_name, fn <$type>() $body, _ => $other) + } + }; +} +pub(crate) use match_fixed_digest_with_eager_block_buffer; + +macro_rules! match_fixed_digest_with_oid { + ($algorithm_name:expr, fn <$type:ident>() $body:block, _ => $other:block) => { + match $algorithm_name { + "rsa-md5" | "md5" | "md5withrsaencryption" | "ssl3-md5" => { + type $type = ::md5::Md5; + $body + } + "rsa-ripemd160" | "ripemd" | "ripemd160" | "ripemd160withrsa" + | "rmd160" => { + type $type = ::ripemd::Ripemd160; + $body + } + "rsa-sha1" + | "rsa-sha1-2" + | "sha1" + | "sha1-2" + | "sha1withrsaencryption" + | "ssl3-sha1" => { + type $type = ::sha1::Sha1; + $body + } + "rsa-sha224" | "sha224" | "sha224withrsaencryption" => { + type $type = ::sha2::Sha224; + $body + } + "rsa-sha256" | "sha256" | "sha256withrsaencryption" => { + type $type = ::sha2::Sha256; + $body + } + "rsa-sha384" | "sha384" | "sha384withrsaencryption" => { + type $type = ::sha2::Sha384; + $body + } + "rsa-sha512" | "sha512" | "sha512withrsaencryption" => { + type $type = ::sha2::Sha512; + $body + } + "rsa-sha512/224" | "sha512-224" | "sha512-224withrsaencryption" => { + type $type = ::sha2::Sha512_224; + $body + } + "rsa-sha512/256" | "sha512-256" | "sha512-256withrsaencryption" => { + type $type = ::sha2::Sha512_256; + $body + } + "rsa-sha3-224" | "id-rsassa-pkcs1-v1_5-with-sha3-224" | "sha3-224" => { + type $type = ::sha3::Sha3_224; + $body + } + "rsa-sha3-256" | "id-rsassa-pkcs1-v1_5-with-sha3-256" | "sha3-256" => { + type $type = ::sha3::Sha3_256; + $body + } + "rsa-sha3-384" | "id-rsassa-pkcs1-v1_5-with-sha3-384" | "sha3-384" => { + type $type = ::sha3::Sha3_384; + $body + } + "rsa-sha3-512" | "id-rsassa-pkcs1-v1_5-with-sha3-512" | "sha3-512" => { + type $type = ::sha3::Sha3_512; + $body + } + _ => $other, + } + }; +} - Sha3_224(Box<sha3::Sha3_224>), - Sha3_256(Box<sha3::Sha3_256>), - Sha3_384(Box<sha3::Sha3_384>), - Sha3_512(Box<sha3::Sha3_512>), +pub(crate) use match_fixed_digest_with_oid; - Sm3(Box<sm3::Sm3>), +pub enum Hash { + FixedSize(Box<dyn DynDigest>), Shake128(Box<sha3::Shake128>, /* output_length: */ Option<usize>), Shake256(Box<sha3::Shake256>, /* output_length: */ Option<usize>), @@ -98,105 +178,32 @@ impl Hash { _ => {} } - let algorithm = match algorithm_name { - "blake2b512" => Blake2b512(Default::default()), - "blake2s256" => Blake2s256(Default::default()), - - "md4" => Md4(Default::default()), - "md5" => Md5(Default::default()), - - "ripemd160" => Ripemd160(Default::default()), - - "sha1" => Sha1(Default::default()), - "sha224" => Sha224(Default::default()), - "sha256" => Sha256(Default::default()), - "sha384" => Sha384(Default::default()), - "sha512" => Sha512(Default::default()), - "sha512-224" => Sha512_224(Default::default()), - "sha512-256" => Sha512_256(Default::default()), - - "sha3-224" => Sha3_224(Default::default()), - "sha3-256" => Sha3_256(Default::default()), - "sha3-384" => Sha3_384(Default::default()), - "sha3-512" => Sha3_512(Default::default()), - - "sm3" => Sm3(Default::default()), - + let algorithm = match_fixed_digest!( + algorithm_name, + fn <D>() { + let digest: D = Digest::new(); + if let Some(length) = output_length { + if length != digest.output_size() { + return Err(generic_error( + "Output length mismatch for non-extendable algorithm", + )); + } + } + FixedSize(Box::new(digest)) + }, _ => { return Err(generic_error(format!( "Digest method not supported: {algorithm_name}" ))) } - }; - if let Some(length) = output_length { - if length != algorithm.output_length() { - return Err(generic_error( - "Output length mismatch for non-extendable algorithm", - )); - } - } - Ok(algorithm) - } + ); - pub fn output_length(&self) -> usize { - match self { - Blake2b512(context) => context.output_size(), - Blake2s256(context) => context.output_size(), - - Md4(context) => context.output_size(), - Md5(context) => context.output_size(), - - Ripemd160(context) => context.output_size(), - - Sha1(context) => context.output_size(), - Sha224(context) => context.output_size(), - Sha256(context) => context.output_size(), - Sha384(context) => context.output_size(), - Sha512(context) => context.output_size(), - Sha512_224(context) => context.output_size(), - Sha512_256(context) => context.output_size(), - - Sha3_224(context) => context.output_size(), - Sha3_256(context) => context.output_size(), - Sha3_384(context) => context.output_size(), - Sha3_512(context) => context.output_size(), - - Sm3(context) => context.output_size(), - - Shake128(_, _) => unreachable!( - "output_length() should not be called on extendable algorithms" - ), - Shake256(_, _) => unreachable!( - "output_length() should not be called on extendable algorithms" - ), - } + Ok(algorithm) } pub fn update(&mut self, data: &[u8]) { match self { - Blake2b512(context) => Digest::update(&mut **context, data), - Blake2s256(context) => Digest::update(&mut **context, data), - - Md4(context) => Digest::update(&mut **context, data), - Md5(context) => Digest::update(&mut **context, data), - - Ripemd160(context) => Digest::update(&mut **context, data), - - Sha1(context) => Digest::update(&mut **context, data), - Sha224(context) => Digest::update(&mut **context, data), - Sha256(context) => Digest::update(&mut **context, data), - Sha384(context) => Digest::update(&mut **context, data), - Sha512(context) => Digest::update(&mut **context, data), - Sha512_224(context) => Digest::update(&mut **context, data), - Sha512_256(context) => Digest::update(&mut **context, data), - - Sha3_224(context) => Digest::update(&mut **context, data), - Sha3_256(context) => Digest::update(&mut **context, data), - Sha3_384(context) => Digest::update(&mut **context, data), - Sha3_512(context) => Digest::update(&mut **context, data), - - Sm3(context) => Digest::update(&mut **context, data), - + FixedSize(context) => DynDigest::update(&mut **context, data), Shake128(context, _) => Update::update(&mut **context, data), Shake256(context, _) => Update::update(&mut **context, data), }; @@ -204,28 +211,7 @@ impl Hash { pub fn digest_and_drop(self) -> Box<[u8]> { match self { - Blake2b512(context) => context.finalize(), - Blake2s256(context) => context.finalize(), - - Md4(context) => context.finalize(), - Md5(context) => context.finalize(), - - Ripemd160(context) => context.finalize(), - - Sha1(context) => context.finalize(), - Sha224(context) => context.finalize(), - Sha256(context) => context.finalize(), - Sha384(context) => context.finalize(), - Sha512(context) => context.finalize(), - Sha512_224(context) => context.finalize(), - Sha512_256(context) => context.finalize(), - - Sha3_224(context) => context.finalize(), - Sha3_256(context) => context.finalize(), - Sha3_384(context) => context.finalize(), - Sha3_512(context) => context.finalize(), - - Sm3(context) => context.finalize(), + FixedSize(context) => context.finalize(), // The default output lengths align with Node.js Shake128(context, output_length) => { @@ -242,69 +228,77 @@ impl Hash { output_length: Option<usize>, ) -> Result<Self, AnyError> { let hash = match self { - Shake128(context, _) => { - return Ok(Shake128(context.clone(), output_length)) + FixedSize(context) => { + if let Some(length) = output_length { + if length != context.output_size() { + return Err(generic_error( + "Output length mismatch for non-extendable algorithm", + )); + } + } + FixedSize(context.box_clone()) } - Shake256(context, _) => { - return Ok(Shake256(context.clone(), output_length)) - } - - Blake2b512(context) => Blake2b512(context.clone()), - Blake2s256(context) => Blake2s256(context.clone()), - - Md4(context) => Md4(context.clone()), - Md5(context) => Md5(context.clone()), - - Ripemd160(context) => Ripemd160(context.clone()), - - Sha1(context) => Sha1(context.clone()), - Sha224(context) => Sha224(context.clone()), - Sha256(context) => Sha256(context.clone()), - Sha384(context) => Sha384(context.clone()), - Sha512(context) => Sha512(context.clone()), - Sha512_224(context) => Sha512_224(context.clone()), - Sha512_256(context) => Sha512_256(context.clone()), - Sha3_224(context) => Sha3_224(context.clone()), - Sha3_256(context) => Sha3_256(context.clone()), - Sha3_384(context) => Sha3_384(context.clone()), - Sha3_512(context) => Sha3_512(context.clone()), - - Sm3(context) => Sm3(context.clone()), + Shake128(context, _) => Shake128(context.clone(), output_length), + Shake256(context, _) => Shake256(context.clone(), output_length), }; - - if let Some(length) = output_length { - if length != hash.output_length() { - return Err(generic_error( - "Output length mismatch for non-extendable algorithm", - )); - } - } - Ok(hash) } pub fn get_hashes() -> Vec<&'static str> { vec![ - "blake2s256", + "RSA-MD5", + "RSA-RIPEMD160", + "RSA-SHA1", + "RSA-SHA1-2", + "RSA-SHA224", + "RSA-SHA256", + "RSA-SHA3-224", + "RSA-SHA3-256", + "RSA-SHA3-384", + "RSA-SHA3-512", + "RSA-SHA384", + "RSA-SHA512", + "RSA-SHA512/224", + "RSA-SHA512/256", + "RSA-SM3", "blake2b512", - "md4", + "blake2s256", + "id-rsassa-pkcs1-v1_5-with-sha3-224", + "id-rsassa-pkcs1-v1_5-with-sha3-256", + "id-rsassa-pkcs1-v1_5-with-sha3-384", + "id-rsassa-pkcs1-v1_5-with-sha3-512", "md5", + "md5-sha1", + "md5WithRSAEncryption", + "ripemd", "ripemd160", + "ripemd160WithRSA", + "rmd160", "sha1", + "sha1WithRSAEncryption", "sha224", + "sha224WithRSAEncryption", "sha256", - "sha384", - "sha512", - "sha512-224", - "sha512-256", + "sha256WithRSAEncryption", "sha3-224", "sha3-256", "sha3-384", "sha3-512", + "sha384", + "sha384WithRSAEncryption", + "sha512", + "sha512-224", + "sha512-224WithRSAEncryption", + "sha512-256", + "sha512-256WithRSAEncryption", + "sha512WithRSAEncryption", "shake128", "shake256", "sm3", + "sm3WithRSAEncryption", + "ssl3-md5", + "ssl3-sha1", ] } } diff --git a/ext/node/ops/crypto/md5_sha1.rs b/ext/node/ops/crypto/md5_sha1.rs new file mode 100644 index 000000000..9164b0a1c --- /dev/null +++ b/ext/node/ops/crypto/md5_sha1.rs @@ -0,0 +1,99 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use core::fmt; +use digest::core_api::AlgorithmName; +use digest::core_api::BlockSizeUser; +use digest::core_api::Buffer; +use digest::core_api::BufferKindUser; +use digest::core_api::CoreWrapper; +use digest::core_api::FixedOutputCore; +use digest::core_api::OutputSizeUser; +use digest::core_api::UpdateCore; +use digest::HashMarker; +use digest::Output; +use digest::Reset; + +pub type Md5Sha1 = CoreWrapper<Md5Sha1Core>; + +pub struct Md5Sha1Core { + md5: md5::Md5Core, + sha1: sha1::Sha1Core, +} + +impl HashMarker for Md5Sha1Core {} + +impl BlockSizeUser for Md5Sha1Core { + type BlockSize = sec1::consts::U64; +} + +impl BufferKindUser for Md5Sha1Core { + type BufferKind = digest::block_buffer::Eager; +} + +impl OutputSizeUser for Md5Sha1Core { + type OutputSize = sec1::consts::U36; +} + +impl UpdateCore for Md5Sha1Core { + #[inline] + fn update_blocks(&mut self, blocks: &[digest::core_api::Block<Self>]) { + self.md5.update_blocks(blocks); + self.sha1.update_blocks(blocks); + } +} + +impl FixedOutputCore for Md5Sha1Core { + #[inline] + fn finalize_fixed_core( + &mut self, + buffer: &mut Buffer<Self>, + out: &mut Output<Self>, + ) { + let mut md5_output = Output::<md5::Md5Core>::default(); + self + .md5 + .finalize_fixed_core(&mut buffer.clone(), &mut md5_output); + let mut sha1_output = Output::<sha1::Sha1Core>::default(); + self.sha1.finalize_fixed_core(buffer, &mut sha1_output); + out[..16].copy_from_slice(&md5_output); + out[16..].copy_from_slice(&sha1_output); + } +} + +impl Default for Md5Sha1Core { + #[inline] + fn default() -> Self { + Self { + md5: Default::default(), + sha1: Default::default(), + } + } +} + +impl Clone for Md5Sha1Core { + #[inline] + fn clone(&self) -> Self { + Self { + md5: self.md5.clone(), + sha1: self.sha1.clone(), + } + } +} + +impl Reset for Md5Sha1Core { + #[inline] + fn reset(&mut self) { + *self = Default::default(); + } +} + +impl AlgorithmName for Md5Sha1Core { + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Md5Sha1") + } +} + +impl fmt::Debug for Md5Sha1Core { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("Md5Sha1Core { ... }") + } +} diff --git a/ext/node/ops/crypto/mod.rs b/ext/node/ops/crypto/mod.rs index 8ded3420a..1b545e024 100644 --- a/ext/node/ops/crypto/mod.rs +++ b/ext/node/ops/crypto/mod.rs @@ -46,9 +46,13 @@ use spki::EncodePublicKey; mod cipher; mod dh; mod digest; +mod md5_sha1; mod primes; pub mod x509; +use self::digest::match_fixed_digest_with_eager_block_buffer; +use self::digest::match_fixed_digest_with_oid; + #[op2(fast)] pub fn op_node_check_prime( #[bigint] num: i64, @@ -382,33 +386,30 @@ pub fn op_node_sign( RSA_ENCRYPTION_OID => { use rsa::pkcs1v15::SigningKey; let key = RsaPrivateKey::from_pkcs1_der(pkey)?; - Ok( - match digest_type { - "sha224" => { - let signing_key = SigningKey::<sha2::Sha224>::new(key); - signing_key.sign_prehash(digest)?.to_vec() - } - "sha256" => { - let signing_key = SigningKey::<sha2::Sha256>::new(key); - signing_key.sign_prehash(digest)?.to_vec() - } - "sha384" => { - let signing_key = SigningKey::<sha2::Sha384>::new(key); - signing_key.sign_prehash(digest)?.to_vec() - } - "sha512" => { - let signing_key = SigningKey::<sha2::Sha512>::new(key); - signing_key.sign_prehash(digest)?.to_vec() - } - _ => { - return Err(type_error(format!( - "Unknown digest algorithm: {}", - digest_type - ))) - } + + // md5-sha1 is special, because it's not wrapped in an ASN.1 DigestInfo + // (so has no prefix). + // See https://github.com/openssl/openssl/blob/af82623d32962b3eff5b0f0b0dedec5eb730b231/crypto/rsa/rsa_sign.c#L285 + if digest_type == "md5-sha1" { + let signing_key = SigningKey::<md5_sha1::Md5Sha1>::new_unprefixed(key); + let signature = signing_key.sign_prehash(digest)?.to_vec(); + return Ok(signature.into()); + } + + let signature = match_fixed_digest_with_oid!( + digest_type, + fn <D>() { + let signing_key = SigningKey::<D>::new(key); + signing_key.sign_prehash(digest)?.to_vec() + }, + _ => { + return Err(type_error(format!( + "digest not allowed for RSA signature: {}", + digest_type + ))) } - .into(), - ) + ); + Ok(signature.into()) } // signature structure encoding is DER by default for DSA and ECDSA. // @@ -456,29 +457,35 @@ pub fn op_node_verify( ))) } }; - Ok(match digest_type { - "sha224" => VerifyingKey::<sha2::Sha224>::new(key) - .verify_prehash(digest, &signature.try_into()?) - .is_ok(), - "sha256" => VerifyingKey::<sha2::Sha256>::new(key) - .verify_prehash(digest, &signature.try_into()?) - .is_ok(), - "sha384" => VerifyingKey::<sha2::Sha384>::new(key) - .verify_prehash(digest, &signature.try_into()?) - .is_ok(), - "sha512" => VerifyingKey::<sha2::Sha512>::new(key) + + // md5-sha1 is special, because it's not wrapped in an ASN.1 DigestInfo + // (so has no prefix). + // See https://github.com/openssl/openssl/blob/af82623d32962b3eff5b0f0b0dedec5eb730b231/crypto/rsa/rsa_sign.c#L285 + if digest_type == "md5-sha1" { + let verifying_key = + VerifyingKey::<md5_sha1::Md5Sha1>::new_unprefixed(key); + let verified = verifying_key .verify_prehash(digest, &signature.try_into()?) - .is_ok(), + .is_ok(); + return Ok(verified); + } + + Ok(match_fixed_digest_with_oid!( + digest_type, + fn <D>() { + let verifying_key = VerifyingKey::<D>::new(key); + verifying_key.verify_prehash(digest, &signature.try_into()?).is_ok() + }, _ => { return Err(type_error(format!( - "Unknown digest algorithm: {}", + "digest not allowed for RSA signature: {}", digest_type ))) } - }) + )) } _ => Err(type_error(format!( - "Verifying with {} keys is not supported yet", + "Verifying with {} keys is not supported", key_type ))), } @@ -488,28 +495,22 @@ fn pbkdf2_sync( password: &[u8], salt: &[u8], iterations: u32, - digest: &str, + algorithm_name: &str, derived_key: &mut [u8], ) -> Result<(), AnyError> { - macro_rules! pbkdf2_hmac { - ($digest:ty) => {{ - pbkdf2::pbkdf2_hmac::<$digest>(password, salt, iterations, derived_key) - }}; - } - - match digest { - "md4" => pbkdf2_hmac!(md4::Md4), - "md5" => pbkdf2_hmac!(md5::Md5), - "ripemd160" => pbkdf2_hmac!(ripemd::Ripemd160), - "sha1" => pbkdf2_hmac!(sha1::Sha1), - "sha224" => pbkdf2_hmac!(sha2::Sha224), - "sha256" => pbkdf2_hmac!(sha2::Sha256), - "sha384" => pbkdf2_hmac!(sha2::Sha384), - "sha512" => pbkdf2_hmac!(sha2::Sha512), - _ => return Err(type_error("Unknown digest")), - } - - Ok(()) + match_fixed_digest_with_eager_block_buffer!( + algorithm_name, + fn <D>() { + pbkdf2::pbkdf2_hmac::<D>(password, salt, iterations, derived_key); + Ok(()) + }, + _ => { + Err(type_error(format!( + "unsupported digest: {}", + algorithm_name + ))) + } + ) } #[op2] @@ -558,50 +559,40 @@ pub async fn op_node_generate_secret_async(#[smi] len: i32) -> ToJsBuffer { } fn hkdf_sync( - hash: &str, + digest_algorithm: &str, ikm: &[u8], salt: &[u8], info: &[u8], okm: &mut [u8], ) -> Result<(), AnyError> { - macro_rules! hkdf { - ($hash:ty) => {{ - let hk = Hkdf::<$hash>::new(Some(salt), ikm); + match_fixed_digest_with_eager_block_buffer!( + digest_algorithm, + fn <D>() { + let hk = Hkdf::<D>::new(Some(salt), ikm); hk.expand(info, okm) - .map_err(|_| type_error("HKDF-Expand failed"))?; - }}; - } - - match hash { - "md4" => hkdf!(md4::Md4), - "md5" => hkdf!(md5::Md5), - "ripemd160" => hkdf!(ripemd::Ripemd160), - "sha1" => hkdf!(sha1::Sha1), - "sha224" => hkdf!(sha2::Sha224), - "sha256" => hkdf!(sha2::Sha256), - "sha384" => hkdf!(sha2::Sha384), - "sha512" => hkdf!(sha2::Sha512), - _ => return Err(type_error("Unknown digest")), - } - - Ok(()) + .map_err(|_| type_error("HKDF-Expand failed")) + }, + _ => { + Err(type_error(format!("Unsupported digest: {}", digest_algorithm))) + } + ) } #[op2(fast)] pub fn op_node_hkdf( - #[string] hash: &str, + #[string] digest_algorithm: &str, #[buffer] ikm: &[u8], #[buffer] salt: &[u8], #[buffer] info: &[u8], #[buffer] okm: &mut [u8], ) -> Result<(), AnyError> { - hkdf_sync(hash, ikm, salt, info, okm) + hkdf_sync(digest_algorithm, ikm, salt, info, okm) } #[op2(async)] #[serde] pub async fn op_node_hkdf_async( - #[string] hash: String, + #[string] digest_algorithm: String, #[buffer] ikm: JsBuffer, #[buffer] salt: JsBuffer, #[buffer] info: JsBuffer, @@ -609,7 +600,7 @@ pub async fn op_node_hkdf_async( ) -> Result<ToJsBuffer, AnyError> { spawn_blocking(move || { let mut okm = vec![0u8; okm_len]; - hkdf_sync(&hash, &ikm, &salt, &info, &mut okm)?; + hkdf_sync(&digest_algorithm, &ikm, &salt, &info, &mut okm)?; Ok(okm.into()) }) .await? |