mirror of
https://github.com/denoland/deno.git
synced 2024-11-28 16:20:57 -05:00
fix(ext/node): rewrite crypto.Hash (#24302)
Changes in this PR: - Added new fixed size hash algorithms (blake2b512, blake2s256, sha512-224, sha512-256, sha3-224, sha3-256, sha3-384, sha3-512, sm3) - Added variable size hash algorithms (the concept), with the algorithms shake128 and shake256 - Use cppgc instead of resources for the hasher - Enable Node's crypto.Hash tests and fix found bugs
This commit is contained in:
parent
ff53506107
commit
1e8a6b94b1
13 changed files with 766 additions and 239 deletions
40
Cargo.lock
generated
40
Cargo.lock
generated
|
@ -480,6 +480,15 @@ dependencies = [
|
||||||
"wyz",
|
"wyz",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "blake2"
|
||||||
|
version = "0.10.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block"
|
name = "block"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
|
@ -1653,6 +1662,7 @@ dependencies = [
|
||||||
"aead-gcm-stream",
|
"aead-gcm-stream",
|
||||||
"aes",
|
"aes",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
"blake2",
|
||||||
"brotli",
|
"brotli",
|
||||||
"bytes",
|
"bytes",
|
||||||
"cbc",
|
"cbc",
|
||||||
|
@ -1706,8 +1716,10 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"sha1",
|
"sha1",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
"sha3",
|
||||||
"signature",
|
"signature",
|
||||||
"simd-json",
|
"simd-json",
|
||||||
|
"sm3",
|
||||||
"spki",
|
"spki",
|
||||||
"tokio",
|
"tokio",
|
||||||
"url",
|
"url",
|
||||||
|
@ -3666,6 +3678,15 @@ dependencies = [
|
||||||
"signature",
|
"signature",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "keccak"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654"
|
||||||
|
dependencies = [
|
||||||
|
"cpufeatures",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "khronos-egl"
|
name = "khronos-egl"
|
||||||
version = "6.0.0"
|
version = "6.0.0"
|
||||||
|
@ -5795,6 +5816,16 @@ dependencies = [
|
||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha3"
|
||||||
|
version = "0.10.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
|
"keccak",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shell-escape"
|
name = "shell-escape"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
|
@ -5910,6 +5941,15 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sm3"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ebb9a3b702d0a7e33bc4d85a14456633d2b165c2ad839c5fd9a8417c1ab15860"
|
||||||
|
dependencies = [
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.13.2"
|
version = "1.13.2"
|
||||||
|
|
|
@ -17,6 +17,7 @@ path = "lib.rs"
|
||||||
aead-gcm-stream = "0.1"
|
aead-gcm-stream = "0.1"
|
||||||
aes.workspace = true
|
aes.workspace = true
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
|
blake2 = "0.10.6"
|
||||||
brotli.workspace = true
|
brotli.workspace = true
|
||||||
bytes.workspace = true
|
bytes.workspace = true
|
||||||
cbc.workspace = true
|
cbc.workspace = true
|
||||||
|
@ -70,8 +71,10 @@ sec1 = "0.7"
|
||||||
serde = "1.0.149"
|
serde = "1.0.149"
|
||||||
sha1.workspace = true
|
sha1.workspace = true
|
||||||
sha2.workspace = true
|
sha2.workspace = true
|
||||||
|
sha3 = "0.10.8"
|
||||||
signature.workspace = true
|
signature.workspace = true
|
||||||
simd-json = "0.13.4"
|
simd-json = "0.13.4"
|
||||||
|
sm3 = "0.4.2"
|
||||||
spki.workspace = true
|
spki.workspace = true
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
url.workspace = true
|
url.workspace = true
|
||||||
|
|
|
@ -1,107 +1,293 @@
|
||||||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
use deno_core::error::type_error;
|
use deno_core::error::generic_error;
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
use deno_core::Resource;
|
use deno_core::GcResource;
|
||||||
use digest::Digest;
|
use digest::Digest;
|
||||||
use digest::DynDigest;
|
use digest::DynDigest;
|
||||||
use std::borrow::Cow;
|
use digest::ExtendableOutput;
|
||||||
|
use digest::Update;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
pub struct Hasher {
|
||||||
|
pub hash: Rc<RefCell<Option<Hash>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GcResource for Hasher {}
|
||||||
|
|
||||||
|
impl Hasher {
|
||||||
|
pub fn new(
|
||||||
|
algorithm: &str,
|
||||||
|
output_length: Option<usize>,
|
||||||
|
) -> Result<Self, AnyError> {
|
||||||
|
let hash = Hash::new(algorithm, output_length)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
hash: Rc::new(RefCell::new(Some(hash))),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&self, data: &[u8]) -> bool {
|
||||||
|
if let Some(hash) = self.hash.borrow_mut().as_mut() {
|
||||||
|
hash.update(data);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn digest(&self) -> Option<Box<[u8]>> {
|
||||||
|
let hash = self.hash.borrow_mut().take()?;
|
||||||
|
Some(hash.digest_and_drop())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clone_inner(
|
||||||
|
&self,
|
||||||
|
output_length: Option<usize>,
|
||||||
|
) -> Result<Option<Self>, AnyError> {
|
||||||
|
let hash = self.hash.borrow();
|
||||||
|
let Some(hash) = hash.as_ref() else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
let hash = hash.clone_hash(output_length)?;
|
||||||
|
Ok(Some(Self {
|
||||||
|
hash: Rc::new(RefCell::new(Some(hash))),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub enum Hash {
|
pub enum Hash {
|
||||||
|
Blake2b512(Box<blake2::Blake2b512>),
|
||||||
|
Blake2s256(Box<blake2::Blake2s256>),
|
||||||
|
|
||||||
Md4(Box<md4::Md4>),
|
Md4(Box<md4::Md4>),
|
||||||
Md5(Box<md5::Md5>),
|
Md5(Box<md5::Md5>),
|
||||||
|
|
||||||
Ripemd160(Box<ripemd::Ripemd160>),
|
Ripemd160(Box<ripemd::Ripemd160>),
|
||||||
|
|
||||||
Sha1(Box<sha1::Sha1>),
|
Sha1(Box<sha1::Sha1>),
|
||||||
|
|
||||||
Sha224(Box<sha2::Sha224>),
|
Sha224(Box<sha2::Sha224>),
|
||||||
Sha256(Box<sha2::Sha256>),
|
Sha256(Box<sha2::Sha256>),
|
||||||
Sha384(Box<sha2::Sha384>),
|
Sha384(Box<sha2::Sha384>),
|
||||||
Sha512(Box<sha2::Sha512>),
|
Sha512(Box<sha2::Sha512>),
|
||||||
}
|
Sha512_224(Box<sha2::Sha512_224>),
|
||||||
|
Sha512_256(Box<sha2::Sha512_256>),
|
||||||
|
|
||||||
pub struct Context {
|
Sha3_224(Box<sha3::Sha3_224>),
|
||||||
pub hash: Rc<RefCell<Hash>>,
|
Sha3_256(Box<sha3::Sha3_256>),
|
||||||
}
|
Sha3_384(Box<sha3::Sha3_384>),
|
||||||
|
Sha3_512(Box<sha3::Sha3_512>),
|
||||||
|
|
||||||
impl Context {
|
Sm3(Box<sm3::Sm3>),
|
||||||
pub fn new(algorithm: &str) -> Result<Self, AnyError> {
|
|
||||||
Ok(Self {
|
|
||||||
hash: Rc::new(RefCell::new(Hash::new(algorithm)?)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(&self, data: &[u8]) {
|
Shake128(Box<sha3::Shake128>, /* output_length: */ Option<usize>),
|
||||||
self.hash.borrow_mut().update(data);
|
Shake256(Box<sha3::Shake256>, /* output_length: */ Option<usize>),
|
||||||
}
|
|
||||||
|
|
||||||
pub fn digest(self) -> Result<Box<[u8]>, AnyError> {
|
|
||||||
let hash = Rc::try_unwrap(self.hash)
|
|
||||||
.map_err(|_| type_error("Hash context is already in use"))?;
|
|
||||||
|
|
||||||
let hash = hash.into_inner();
|
|
||||||
Ok(hash.digest_and_drop())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for Context {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
hash: Rc::new(RefCell::new(self.hash.borrow().clone())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Resource for Context {
|
|
||||||
fn name(&self) -> Cow<str> {
|
|
||||||
"cryptoDigest".into()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
use Hash::*;
|
use Hash::*;
|
||||||
|
|
||||||
impl Hash {
|
impl Hash {
|
||||||
pub fn new(algorithm_name: &str) -> Result<Self, AnyError> {
|
pub fn new(
|
||||||
Ok(match algorithm_name {
|
algorithm_name: &str,
|
||||||
|
output_length: Option<usize>,
|
||||||
|
) -> Result<Self, AnyError> {
|
||||||
|
match algorithm_name {
|
||||||
|
"shake128" => return Ok(Shake128(Default::default(), output_length)),
|
||||||
|
"shake256" => return Ok(Shake256(Default::default(), output_length)),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
let algorithm = match algorithm_name {
|
||||||
|
"blake2b512" => Blake2b512(Default::default()),
|
||||||
|
"blake2s256" => Blake2s256(Default::default()),
|
||||||
|
|
||||||
"md4" => Md4(Default::default()),
|
"md4" => Md4(Default::default()),
|
||||||
"md5" => Md5(Default::default()),
|
"md5" => Md5(Default::default()),
|
||||||
|
|
||||||
"ripemd160" => Ripemd160(Default::default()),
|
"ripemd160" => Ripemd160(Default::default()),
|
||||||
|
|
||||||
"sha1" => Sha1(Default::default()),
|
"sha1" => Sha1(Default::default()),
|
||||||
"sha224" => Sha224(Default::default()),
|
"sha224" => Sha224(Default::default()),
|
||||||
"sha256" => Sha256(Default::default()),
|
"sha256" => Sha256(Default::default()),
|
||||||
"sha384" => Sha384(Default::default()),
|
"sha384" => Sha384(Default::default()),
|
||||||
"sha512" => Sha512(Default::default()),
|
"sha512" => Sha512(Default::default()),
|
||||||
_ => return Err(type_error("unsupported algorithm")),
|
"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()),
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
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"
|
||||||
|
),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, data: &[u8]) {
|
pub fn update(&mut self, data: &[u8]) {
|
||||||
match self {
|
match self {
|
||||||
|
Blake2b512(context) => Digest::update(&mut **context, data),
|
||||||
|
Blake2s256(context) => Digest::update(&mut **context, data),
|
||||||
|
|
||||||
Md4(context) => Digest::update(&mut **context, data),
|
Md4(context) => Digest::update(&mut **context, data),
|
||||||
Md5(context) => Digest::update(&mut **context, data),
|
Md5(context) => Digest::update(&mut **context, data),
|
||||||
|
|
||||||
Ripemd160(context) => Digest::update(&mut **context, data),
|
Ripemd160(context) => Digest::update(&mut **context, data),
|
||||||
|
|
||||||
Sha1(context) => Digest::update(&mut **context, data),
|
Sha1(context) => Digest::update(&mut **context, data),
|
||||||
Sha224(context) => Digest::update(&mut **context, data),
|
Sha224(context) => Digest::update(&mut **context, data),
|
||||||
Sha256(context) => Digest::update(&mut **context, data),
|
Sha256(context) => Digest::update(&mut **context, data),
|
||||||
Sha384(context) => Digest::update(&mut **context, data),
|
Sha384(context) => Digest::update(&mut **context, data),
|
||||||
Sha512(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),
|
||||||
|
|
||||||
|
Shake128(context, _) => Update::update(&mut **context, data),
|
||||||
|
Shake256(context, _) => Update::update(&mut **context, data),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn digest_and_drop(self) -> Box<[u8]> {
|
pub fn digest_and_drop(self) -> Box<[u8]> {
|
||||||
match self {
|
match self {
|
||||||
|
Blake2b512(context) => context.finalize(),
|
||||||
|
Blake2s256(context) => context.finalize(),
|
||||||
|
|
||||||
Md4(context) => context.finalize(),
|
Md4(context) => context.finalize(),
|
||||||
Md5(context) => context.finalize(),
|
Md5(context) => context.finalize(),
|
||||||
|
|
||||||
Ripemd160(context) => context.finalize(),
|
Ripemd160(context) => context.finalize(),
|
||||||
|
|
||||||
Sha1(context) => context.finalize(),
|
Sha1(context) => context.finalize(),
|
||||||
Sha224(context) => context.finalize(),
|
Sha224(context) => context.finalize(),
|
||||||
Sha256(context) => context.finalize(),
|
Sha256(context) => context.finalize(),
|
||||||
Sha384(context) => context.finalize(),
|
Sha384(context) => context.finalize(),
|
||||||
Sha512(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(),
|
||||||
|
|
||||||
|
// The default output lengths align with Node.js
|
||||||
|
Shake128(context, output_length) => {
|
||||||
|
context.finalize_boxed(output_length.unwrap_or(16))
|
||||||
|
}
|
||||||
|
Shake256(context, output_length) => {
|
||||||
|
context.finalize_boxed(output_length.unwrap_or(32))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clone_hash(
|
||||||
|
&self,
|
||||||
|
output_length: Option<usize>,
|
||||||
|
) -> Result<Self, AnyError> {
|
||||||
|
let hash = match self {
|
||||||
|
Shake128(context, _) => {
|
||||||
|
return Ok(Shake128(context.clone(), output_length))
|
||||||
|
}
|
||||||
|
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()),
|
||||||
|
};
|
||||||
|
|
||||||
|
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> {
|
pub fn get_hashes() -> Vec<&'static str> {
|
||||||
vec![
|
vec![
|
||||||
|
"blake2s256",
|
||||||
|
"blake2b512",
|
||||||
"md4",
|
"md4",
|
||||||
"md5",
|
"md5",
|
||||||
"ripemd160",
|
"ripemd160",
|
||||||
|
@ -110,21 +296,15 @@ impl Hash {
|
||||||
"sha256",
|
"sha256",
|
||||||
"sha384",
|
"sha384",
|
||||||
"sha512",
|
"sha512",
|
||||||
|
"sha512-224",
|
||||||
|
"sha512-256",
|
||||||
|
"sha3-224",
|
||||||
|
"sha3-256",
|
||||||
|
"sha3-384",
|
||||||
|
"sha3-512",
|
||||||
|
"shake128",
|
||||||
|
"shake256",
|
||||||
|
"sm3",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for Hash {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
match self {
|
|
||||||
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()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ use deno_core::serde_v8::BigInt as V8BigInt;
|
||||||
use deno_core::unsync::spawn_blocking;
|
use deno_core::unsync::spawn_blocking;
|
||||||
use deno_core::JsBuffer;
|
use deno_core::JsBuffer;
|
||||||
use deno_core::OpState;
|
use deno_core::OpState;
|
||||||
use deno_core::ResourceId;
|
|
||||||
use deno_core::StringOrBuffer;
|
use deno_core::StringOrBuffer;
|
||||||
use deno_core::ToJsBuffer;
|
use deno_core::ToJsBuffer;
|
||||||
use elliptic_curve::sec1::ToEncodedPoint;
|
use elliptic_curve::sec1::ToEncodedPoint;
|
||||||
|
@ -96,18 +95,13 @@ pub fn op_node_check_prime_bytes_async(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[op2(fast)]
|
#[op2]
|
||||||
#[smi]
|
#[cppgc]
|
||||||
pub fn op_node_create_hash(
|
pub fn op_node_create_hash(
|
||||||
state: &mut OpState,
|
|
||||||
#[string] algorithm: &str,
|
#[string] algorithm: &str,
|
||||||
) -> u32 {
|
output_length: Option<u32>,
|
||||||
state
|
) -> Result<digest::Hasher, AnyError> {
|
||||||
.resource_table
|
digest::Hasher::new(algorithm, output_length.map(|l| l as usize))
|
||||||
.add(match digest::Context::new(algorithm) {
|
|
||||||
Ok(context) => context,
|
|
||||||
Err(_) => return 0,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[op2]
|
#[op2]
|
||||||
|
@ -118,65 +112,44 @@ pub fn op_node_get_hashes() -> Vec<&'static str> {
|
||||||
|
|
||||||
#[op2(fast)]
|
#[op2(fast)]
|
||||||
pub fn op_node_hash_update(
|
pub fn op_node_hash_update(
|
||||||
state: &mut OpState,
|
#[cppgc] hasher: &digest::Hasher,
|
||||||
#[smi] rid: u32,
|
|
||||||
#[buffer] data: &[u8],
|
#[buffer] data: &[u8],
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let context = match state.resource_table.get::<digest::Context>(rid) {
|
hasher.update(data)
|
||||||
Ok(context) => context,
|
|
||||||
_ => return false,
|
|
||||||
};
|
|
||||||
context.update(data);
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[op2(fast)]
|
#[op2(fast)]
|
||||||
pub fn op_node_hash_update_str(
|
pub fn op_node_hash_update_str(
|
||||||
state: &mut OpState,
|
#[cppgc] hasher: &digest::Hasher,
|
||||||
#[smi] rid: u32,
|
|
||||||
#[string] data: &str,
|
#[string] data: &str,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let context = match state.resource_table.get::<digest::Context>(rid) {
|
hasher.update(data.as_bytes())
|
||||||
Ok(context) => context,
|
|
||||||
_ => return false,
|
|
||||||
};
|
|
||||||
context.update(data.as_bytes());
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[op2]
|
#[op2]
|
||||||
#[serde]
|
#[buffer]
|
||||||
pub fn op_node_hash_digest(
|
pub fn op_node_hash_digest(
|
||||||
state: &mut OpState,
|
#[cppgc] hasher: &digest::Hasher,
|
||||||
#[smi] rid: ResourceId,
|
) -> Option<Box<[u8]>> {
|
||||||
) -> Result<ToJsBuffer, AnyError> {
|
hasher.digest()
|
||||||
let context = state.resource_table.take::<digest::Context>(rid)?;
|
|
||||||
let context = Rc::try_unwrap(context)
|
|
||||||
.map_err(|_| type_error("Hash context is already in use"))?;
|
|
||||||
Ok(context.digest()?.into())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[op2]
|
#[op2]
|
||||||
#[string]
|
#[string]
|
||||||
pub fn op_node_hash_digest_hex(
|
pub fn op_node_hash_digest_hex(
|
||||||
state: &mut OpState,
|
#[cppgc] hasher: &digest::Hasher,
|
||||||
#[smi] rid: ResourceId,
|
) -> Option<String> {
|
||||||
) -> Result<String, AnyError> {
|
let digest = hasher.digest()?;
|
||||||
let context = state.resource_table.take::<digest::Context>(rid)?;
|
Some(faster_hex::hex_string(&digest))
|
||||||
let context = Rc::try_unwrap(context)
|
|
||||||
.map_err(|_| type_error("Hash context is already in use"))?;
|
|
||||||
let digest = context.digest()?;
|
|
||||||
Ok(faster_hex::hex_string(&digest))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[op2(fast)]
|
#[op2]
|
||||||
#[smi]
|
#[cppgc]
|
||||||
pub fn op_node_hash_clone(
|
pub fn op_node_hash_clone(
|
||||||
state: &mut OpState,
|
#[cppgc] hasher: &digest::Hasher,
|
||||||
#[smi] rid: ResourceId,
|
output_length: Option<u32>,
|
||||||
) -> Result<ResourceId, AnyError> {
|
) -> Result<Option<digest::Hasher>, AnyError> {
|
||||||
let context = state.resource_table.get::<digest::Context>(rid)?;
|
hasher.clone_inner(output_length.map(|l| l as usize))
|
||||||
Ok(state.resource_table.add(context.as_ref().clone()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[op2]
|
#[op2]
|
||||||
|
|
|
@ -13,8 +13,8 @@ import {
|
||||||
op_node_hash_update,
|
op_node_hash_update,
|
||||||
op_node_hash_update_str,
|
op_node_hash_update_str,
|
||||||
} from "ext:core/ops";
|
} from "ext:core/ops";
|
||||||
|
import { primordials } from "ext:core/mod.js";
|
||||||
|
|
||||||
import { TextEncoder } from "ext:deno_web/08_text_encoding.js";
|
|
||||||
import { Buffer } from "node:buffer";
|
import { Buffer } from "node:buffer";
|
||||||
import { Transform } from "node:stream";
|
import { Transform } from "node:stream";
|
||||||
import {
|
import {
|
||||||
|
@ -22,7 +22,11 @@ import {
|
||||||
forgivingBase64UrlEncode as encodeToBase64Url,
|
forgivingBase64UrlEncode as encodeToBase64Url,
|
||||||
} from "ext:deno_web/00_infra.js";
|
} from "ext:deno_web/00_infra.js";
|
||||||
import type { TransformOptions } from "ext:deno_node/_stream.d.ts";
|
import type { TransformOptions } from "ext:deno_node/_stream.d.ts";
|
||||||
import { validateString } from "ext:deno_node/internal/validators.mjs";
|
import {
|
||||||
|
validateEncoding,
|
||||||
|
validateString,
|
||||||
|
validateUint32,
|
||||||
|
} from "ext:deno_node/internal/validators.mjs";
|
||||||
import type {
|
import type {
|
||||||
BinaryToTextEncoding,
|
BinaryToTextEncoding,
|
||||||
Encoding,
|
Encoding,
|
||||||
|
@ -32,119 +36,148 @@ import {
|
||||||
KeyObject,
|
KeyObject,
|
||||||
prepareSecretKey,
|
prepareSecretKey,
|
||||||
} from "ext:deno_node/internal/crypto/keys.ts";
|
} from "ext:deno_node/internal/crypto/keys.ts";
|
||||||
|
import {
|
||||||
|
ERR_CRYPTO_HASH_FINALIZED,
|
||||||
|
ERR_INVALID_ARG_TYPE,
|
||||||
|
NodeError,
|
||||||
|
} from "ext:deno_node/internal/errors.ts";
|
||||||
|
import LazyTransform from "ext:deno_node/internal/streams/lazy_transform.mjs";
|
||||||
|
import {
|
||||||
|
getDefaultEncoding,
|
||||||
|
toBuf,
|
||||||
|
} from "ext:deno_node/internal/crypto/util.ts";
|
||||||
|
import { isArrayBufferView } from "ext:deno_node/internal/util/types.ts";
|
||||||
|
|
||||||
|
const { ReflectApply, ObjectSetPrototypeOf } = primordials;
|
||||||
|
|
||||||
// TODO(@littledivy): Use Result<T, E> instead of boolean when
|
|
||||||
// https://bugs.chromium.org/p/v8/issues/detail?id=13600 is fixed.
|
|
||||||
function unwrapErr(ok: boolean) {
|
function unwrapErr(ok: boolean) {
|
||||||
if (!ok) {
|
if (!ok) throw new ERR_CRYPTO_HASH_FINALIZED();
|
||||||
throw new Error("Context is not initialized");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const coerceToBytes = (data: string | BufferSource): Uint8Array => {
|
declare const __hasher: unique symbol;
|
||||||
if (data instanceof Uint8Array) {
|
type Hasher = { __hasher: typeof __hasher };
|
||||||
return data;
|
|
||||||
} else if (typeof data === "string") {
|
const kHandle = Symbol("kHandle");
|
||||||
// This assumes UTF-8, which may not be correct.
|
|
||||||
return new TextEncoder().encode(data);
|
export function Hash(
|
||||||
} else if (ArrayBuffer.isView(data)) {
|
this: Hash,
|
||||||
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
algorithm: string | Hasher,
|
||||||
} else if (data instanceof ArrayBuffer) {
|
options?: { outputLength?: number },
|
||||||
return new Uint8Array(data);
|
): Hash {
|
||||||
} else {
|
if (!(this instanceof Hash)) {
|
||||||
throw new TypeError("expected data to be string | BufferSource");
|
return new Hash(algorithm, options);
|
||||||
}
|
}
|
||||||
|
if (!(typeof algorithm === "object")) {
|
||||||
|
validateString(algorithm, "algorithm");
|
||||||
|
}
|
||||||
|
const xofLen = typeof options === "object" && options !== null
|
||||||
|
? options.outputLength
|
||||||
|
: undefined;
|
||||||
|
if (xofLen !== undefined) {
|
||||||
|
validateUint32(xofLen, "options.outputLength");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this[kHandle] = typeof algorithm === "object"
|
||||||
|
? op_node_hash_clone(algorithm, xofLen)
|
||||||
|
: op_node_create_hash(algorithm.toLowerCase(), xofLen);
|
||||||
|
} catch (err) {
|
||||||
|
// TODO(lucacasonato): don't do this
|
||||||
|
if (err.message === "Output length mismatch for non-extendable algorithm") {
|
||||||
|
throw new NodeError(
|
||||||
|
"ERR_OSSL_EVP_NOT_XOF_OR_INVALID_LENGTH",
|
||||||
|
"Invalid XOF digest length",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this[kHandle] === null) throw new ERR_CRYPTO_HASH_FINALIZED();
|
||||||
|
|
||||||
|
ReflectApply(LazyTransform, this, [options]);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Hash {
|
||||||
|
[kHandle]: object;
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectSetPrototypeOf(Hash.prototype, LazyTransform.prototype);
|
||||||
|
ObjectSetPrototypeOf(Hash, LazyTransform);
|
||||||
|
|
||||||
|
Hash.prototype.copy = function copy(options?: { outputLength: number }) {
|
||||||
|
return new Hash(this[kHandle], options);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
Hash.prototype._transform = function _transform(
|
||||||
* The Hash class is a utility for creating hash digests of data. It can be used in one of two ways:
|
chunk: string | Buffer,
|
||||||
*
|
encoding: Encoding | "buffer",
|
||||||
* - As a stream that is both readable and writable, where data is written to produce a computed hash digest on the readable side, or
|
callback: () => void,
|
||||||
* - Using the hash.update() and hash.digest() methods to produce the computed hash.
|
) {
|
||||||
*
|
this.update(chunk, encoding);
|
||||||
* The crypto.createHash() method is used to create Hash instances. Hash objects are not to be created directly using the new keyword.
|
callback();
|
||||||
*/
|
};
|
||||||
export class Hash extends Transform {
|
|
||||||
#context: number;
|
|
||||||
|
|
||||||
constructor(
|
Hash.prototype._flush = function _flush(callback: () => void) {
|
||||||
algorithm: string | number,
|
this.push(this.digest());
|
||||||
_opts?: TransformOptions,
|
callback();
|
||||||
|
};
|
||||||
|
|
||||||
|
Hash.prototype.update = function update(
|
||||||
|
data: string | Buffer,
|
||||||
|
encoding: Encoding | "buffer",
|
||||||
|
) {
|
||||||
|
encoding = encoding || getDefaultEncoding();
|
||||||
|
|
||||||
|
if (typeof data === "string") {
|
||||||
|
validateEncoding(data, encoding);
|
||||||
|
} else if (!isArrayBufferView(data)) {
|
||||||
|
throw new ERR_INVALID_ARG_TYPE(
|
||||||
|
"data",
|
||||||
|
["string", "Buffer", "TypedArray", "DataView"],
|
||||||
|
data,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof data === "string" && (encoding === "utf8" || encoding === "buffer")
|
||||||
) {
|
) {
|
||||||
super({
|
unwrapErr(op_node_hash_update_str(this[kHandle], data));
|
||||||
transform(chunk: string, _encoding: string, callback: () => void) {
|
} else {
|
||||||
op_node_hash_update(context, coerceToBytes(chunk));
|
unwrapErr(op_node_hash_update(this[kHandle], toBuf(data, encoding)));
|
||||||
callback();
|
|
||||||
},
|
|
||||||
flush(callback: () => void) {
|
|
||||||
this.push(this.digest(undefined));
|
|
||||||
callback();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (typeof algorithm === "string") {
|
|
||||||
this.#context = op_node_create_hash(
|
|
||||||
algorithm.toLowerCase(),
|
|
||||||
);
|
|
||||||
if (this.#context === 0) {
|
|
||||||
throw new TypeError(`Unknown hash algorithm: ${algorithm}`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.#context = algorithm;
|
|
||||||
}
|
|
||||||
|
|
||||||
const context = this.#context;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
copy(): Hash {
|
return this;
|
||||||
return new Hash(op_node_hash_clone(this.#context));
|
};
|
||||||
|
|
||||||
|
Hash.prototype.digest = function digest(outputEncoding: Encoding | "buffer") {
|
||||||
|
outputEncoding = outputEncoding || getDefaultEncoding();
|
||||||
|
outputEncoding = `${outputEncoding}`;
|
||||||
|
|
||||||
|
if (outputEncoding === "hex") {
|
||||||
|
const result = op_node_hash_digest_hex(this[kHandle]);
|
||||||
|
if (result === null) throw new ERR_CRYPTO_HASH_FINALIZED();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
const digest = op_node_hash_digest(this[kHandle]);
|
||||||
* Updates the hash content with the given data.
|
if (digest === null) throw new ERR_CRYPTO_HASH_FINALIZED();
|
||||||
*/
|
|
||||||
update(data: string | ArrayBuffer, _encoding?: string): this {
|
|
||||||
if (typeof data === "string") {
|
|
||||||
unwrapErr(op_node_hash_update_str(this.#context, data));
|
|
||||||
} else {
|
|
||||||
unwrapErr(op_node_hash_update(this.#context, coerceToBytes(data)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
// TODO(@littedivy): Fast paths for below encodings.
|
||||||
}
|
switch (outputEncoding) {
|
||||||
|
case "binary":
|
||||||
/**
|
return String.fromCharCode(...digest);
|
||||||
* Calculates the digest of all of the data.
|
case "base64":
|
||||||
*
|
return encodeToBase64(digest);
|
||||||
* If encoding is provided a string will be returned; otherwise a Buffer is returned.
|
case "base64url":
|
||||||
*
|
return encodeToBase64Url(digest);
|
||||||
* Supported encodings are currently 'hex', 'binary', 'base64', 'base64url'.
|
case undefined:
|
||||||
*/
|
case "buffer":
|
||||||
digest(encoding?: string): Buffer | string {
|
|
||||||
if (encoding === "hex") {
|
|
||||||
return op_node_hash_digest_hex(this.#context);
|
|
||||||
}
|
|
||||||
|
|
||||||
const digest = op_node_hash_digest(this.#context);
|
|
||||||
if (encoding === undefined) {
|
|
||||||
return Buffer.from(digest);
|
return Buffer.from(digest);
|
||||||
}
|
default:
|
||||||
|
return Buffer.from(digest).toString(outputEncoding);
|
||||||
// TODO(@littedivy): Fast paths for below encodings.
|
|
||||||
switch (encoding) {
|
|
||||||
case "binary":
|
|
||||||
return String.fromCharCode(...digest);
|
|
||||||
case "base64":
|
|
||||||
return encodeToBase64(digest);
|
|
||||||
case "base64url":
|
|
||||||
return encodeToBase64Url(digest);
|
|
||||||
case "buffer":
|
|
||||||
return Buffer.from(digest);
|
|
||||||
default:
|
|
||||||
return Buffer.from(digest).toString(encoding);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export function Hmac(
|
export function Hmac(
|
||||||
hmac: string,
|
hmac: string,
|
||||||
|
@ -171,7 +204,7 @@ class HmacImpl extends Transform {
|
||||||
super({
|
super({
|
||||||
transform(chunk: string, encoding: string, callback: () => void) {
|
transform(chunk: string, encoding: string, callback: () => void) {
|
||||||
// deno-lint-ignore no-explicit-any
|
// deno-lint-ignore no-explicit-any
|
||||||
self.update(coerceToBytes(chunk), encoding as any);
|
self.update(Buffer.from(chunk), encoding as any);
|
||||||
callback();
|
callback();
|
||||||
},
|
},
|
||||||
flush(callback: () => void) {
|
flush(callback: () => void) {
|
||||||
|
@ -219,9 +252,10 @@ class HmacImpl extends Transform {
|
||||||
digest(encoding?: BinaryToTextEncoding): Buffer | string {
|
digest(encoding?: BinaryToTextEncoding): Buffer | string {
|
||||||
const result = this.#hash.digest();
|
const result = this.#hash.digest();
|
||||||
|
|
||||||
return new Hash(this.#algorithm).update(this.#opad).update(result).digest(
|
return new Hash(this.#algorithm).update(this.#opad).update(result)
|
||||||
encoding,
|
.digest(
|
||||||
);
|
encoding,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
update(data: string | ArrayBuffer, inputEncoding?: Encoding): this {
|
update(data: string | ArrayBuffer, inputEncoding?: Encoding): this {
|
||||||
|
|
|
@ -61,6 +61,7 @@ util::unit_test_factory!(
|
||||||
crypto_cipher_gcm_test = crypto / crypto_cipher_gcm_test,
|
crypto_cipher_gcm_test = crypto / crypto_cipher_gcm_test,
|
||||||
crypto_hash_test = crypto / crypto_hash_test,
|
crypto_hash_test = crypto / crypto_hash_test,
|
||||||
crypto_key_test = crypto / crypto_key_test,
|
crypto_key_test = crypto / crypto_key_test,
|
||||||
|
crypto_misc_test = crypto / crypto_misc_test,
|
||||||
crypto_sign_test = crypto / crypto_sign_test,
|
crypto_sign_test = crypto / crypto_sign_test,
|
||||||
events_test,
|
events_test,
|
||||||
dgram_test,
|
dgram_test,
|
||||||
|
|
|
@ -132,16 +132,17 @@
|
||||||
"tmpdir.js"
|
"tmpdir.js"
|
||||||
],
|
],
|
||||||
"fixtures": [
|
"fixtures": [
|
||||||
"GH-1899-output.js",
|
|
||||||
"a.js",
|
"a.js",
|
||||||
"child-process-spawn-node.js",
|
|
||||||
"child_process_should_emit_error.js",
|
"child_process_should_emit_error.js",
|
||||||
|
"child-process-spawn-node.js",
|
||||||
"echo.js",
|
"echo.js",
|
||||||
"elipses.txt",
|
"elipses.txt",
|
||||||
"empty.txt",
|
"empty.txt",
|
||||||
"exit.js",
|
"exit.js",
|
||||||
|
"GH-1899-output.js",
|
||||||
"loop.js",
|
"loop.js",
|
||||||
"print-chars.js",
|
"print-chars.js",
|
||||||
|
"sample.png",
|
||||||
"x.txt"
|
"x.txt"
|
||||||
],
|
],
|
||||||
"fixtures/keys": ["agent1-cert.pem", "agent1-key.pem", "ca1-cert.pem"],
|
"fixtures/keys": ["agent1-cert.pem", "agent1-key.pem", "ca1-cert.pem"],
|
||||||
|
@ -253,6 +254,7 @@
|
||||||
"test-console-tty-colors.js",
|
"test-console-tty-colors.js",
|
||||||
"test-crypto-dh-shared.js",
|
"test-crypto-dh-shared.js",
|
||||||
"test-crypto-dh.js",
|
"test-crypto-dh.js",
|
||||||
|
"test-crypto-hash.js",
|
||||||
"test-crypto-hkdf.js",
|
"test-crypto-hkdf.js",
|
||||||
"test-crypto-hmac.js",
|
"test-crypto-hmac.js",
|
||||||
"test-crypto-prime.js",
|
"test-crypto-prime.js",
|
||||||
|
@ -701,8 +703,8 @@
|
||||||
"test-zlib-zero-windowBits.js"
|
"test-zlib-zero-windowBits.js"
|
||||||
],
|
],
|
||||||
"pseudo-tty": [
|
"pseudo-tty": [
|
||||||
"console-dumb-tty.js",
|
|
||||||
"console_colors.js",
|
"console_colors.js",
|
||||||
|
"console-dumb-tty.js",
|
||||||
"no_dropped_stdio.js",
|
"no_dropped_stdio.js",
|
||||||
"no_interleaved_stdio.js",
|
"no_interleaved_stdio.js",
|
||||||
"test-tty-color-support-warning-2.js",
|
"test-tty-color-support-warning-2.js",
|
||||||
|
|
|
@ -464,7 +464,6 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co
|
||||||
- [parallel/test-crypto-from-binary.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-from-binary.js)
|
- [parallel/test-crypto-from-binary.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-from-binary.js)
|
||||||
- [parallel/test-crypto-getcipherinfo.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-getcipherinfo.js)
|
- [parallel/test-crypto-getcipherinfo.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-getcipherinfo.js)
|
||||||
- [parallel/test-crypto-hash-stream-pipe.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-hash-stream-pipe.js)
|
- [parallel/test-crypto-hash-stream-pipe.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-hash-stream-pipe.js)
|
||||||
- [parallel/test-crypto-hash.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-hash.js)
|
|
||||||
- [parallel/test-crypto-key-objects-messageport.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-key-objects-messageport.js)
|
- [parallel/test-crypto-key-objects-messageport.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-key-objects-messageport.js)
|
||||||
- [parallel/test-crypto-key-objects.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-key-objects.js)
|
- [parallel/test-crypto-key-objects.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-key-objects.js)
|
||||||
- [parallel/test-crypto-keygen-async-dsa-key-object.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-async-dsa-key-object.js)
|
- [parallel/test-crypto-keygen-async-dsa-key-object.js](https://github.com/nodejs/node/tree/v18.12.1/test/parallel/test-crypto-keygen-async-dsa-key-object.js)
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
import { magenta } from "@std/fmt/colors.ts";
|
import { magenta } from "@std/fmt/colors.ts";
|
||||||
import { pooledMap } from "@std/async/pool.ts";
|
import { pooledMap } from "@std/async/pool.ts";
|
||||||
import { dirname, fromFileUrl, join } from "@std/path/mod.ts";
|
import { dirname, fromFileUrl, join } from "@std/path/mod.ts";
|
||||||
import { fail } from "@std/assert/mod.ts";
|
import { assertEquals, fail } from "@std/assert/mod.ts";
|
||||||
import {
|
import {
|
||||||
config,
|
config,
|
||||||
getPathsFromTestSuites,
|
getPathsFromTestSuites,
|
||||||
|
@ -169,12 +169,14 @@ Deno.test("Node.js compatibility", async (t) => {
|
||||||
function checkConfigTestFilesOrder(testFileLists: Array<string[]>) {
|
function checkConfigTestFilesOrder(testFileLists: Array<string[]>) {
|
||||||
for (const testFileList of testFileLists) {
|
for (const testFileList of testFileLists) {
|
||||||
const sortedTestList = JSON.parse(JSON.stringify(testFileList));
|
const sortedTestList = JSON.parse(JSON.stringify(testFileList));
|
||||||
sortedTestList.sort();
|
sortedTestList.sort((a: string, b: string) =>
|
||||||
if (JSON.stringify(testFileList) !== JSON.stringify(sortedTestList)) {
|
a.toLowerCase().localeCompare(b.toLowerCase())
|
||||||
throw new Error(
|
);
|
||||||
`File names in \`config.json\` are not correct order.`,
|
assertEquals(
|
||||||
);
|
testFileList,
|
||||||
}
|
sortedTestList,
|
||||||
|
"File names in `config.json` are not correct order.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
BIN
tests/node_compat/test/fixtures/sample.png
vendored
Normal file
BIN
tests/node_compat/test/fixtures/sample.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.2 KiB |
285
tests/node_compat/test/parallel/test-crypto-hash.js
Normal file
285
tests/node_compat/test/parallel/test-crypto-hash.js
Normal file
|
@ -0,0 +1,285 @@
|
||||||
|
// deno-fmt-ignore-file
|
||||||
|
// deno-lint-ignore-file
|
||||||
|
|
||||||
|
// Copyright Joyent and Node contributors. All rights reserved. MIT license.
|
||||||
|
// Taken from Node 18.12.1
|
||||||
|
// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually.
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
const common = require('../common');
|
||||||
|
if (!common.hasCrypto)
|
||||||
|
common.skip('missing crypto');
|
||||||
|
|
||||||
|
const assert = require('assert');
|
||||||
|
const crypto = require('crypto');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const fixtures = require('../common/fixtures');
|
||||||
|
|
||||||
|
let cryptoType;
|
||||||
|
let digest;
|
||||||
|
|
||||||
|
// Test hashing
|
||||||
|
const a1 = crypto.createHash('sha1').update('Test123').digest('hex');
|
||||||
|
const a2 = crypto.createHash('sha256').update('Test123').digest('base64');
|
||||||
|
const a3 = crypto.createHash('sha512').update('Test123').digest(); // buffer
|
||||||
|
const a4 = crypto.createHash('sha1').update('Test123').digest('buffer');
|
||||||
|
|
||||||
|
// stream interface
|
||||||
|
let a5 = crypto.createHash('sha512');
|
||||||
|
a5.end('Test123');
|
||||||
|
a5 = a5.read();
|
||||||
|
|
||||||
|
let a6 = crypto.createHash('sha512');
|
||||||
|
a6.write('Te');
|
||||||
|
a6.write('st');
|
||||||
|
a6.write('123');
|
||||||
|
a6.end();
|
||||||
|
a6 = a6.read();
|
||||||
|
|
||||||
|
let a7 = crypto.createHash('sha512');
|
||||||
|
a7.end();
|
||||||
|
a7 = a7.read();
|
||||||
|
|
||||||
|
let a8 = crypto.createHash('sha512');
|
||||||
|
a8.write('');
|
||||||
|
a8.end();
|
||||||
|
a8 = a8.read();
|
||||||
|
|
||||||
|
if (!common.hasFipsCrypto) {
|
||||||
|
cryptoType = 'md5';
|
||||||
|
digest = 'latin1';
|
||||||
|
const a0 = crypto.createHash(cryptoType).update('Test123').digest(digest);
|
||||||
|
assert.strictEqual(
|
||||||
|
a0,
|
||||||
|
'h\u00ea\u00cb\u0097\u00d8o\fF!\u00fa+\u000e\u0017\u00ca\u00bd\u008c',
|
||||||
|
`${cryptoType} with ${digest} digest failed to evaluate to expected hash`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
cryptoType = 'md5';
|
||||||
|
digest = 'hex';
|
||||||
|
assert.strictEqual(
|
||||||
|
a1,
|
||||||
|
'8308651804facb7b9af8ffc53a33a22d6a1c8ac2',
|
||||||
|
`${cryptoType} with ${digest} digest failed to evaluate to expected hash`);
|
||||||
|
cryptoType = 'sha256';
|
||||||
|
digest = 'base64';
|
||||||
|
assert.strictEqual(
|
||||||
|
a2,
|
||||||
|
'2bX1jws4GYKTlxhloUB09Z66PoJZW+y+hq5R8dnx9l4=',
|
||||||
|
`${cryptoType} with ${digest} digest failed to evaluate to expected hash`);
|
||||||
|
cryptoType = 'sha512';
|
||||||
|
digest = 'latin1';
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
a3,
|
||||||
|
Buffer.from(
|
||||||
|
'\u00c1(4\u00f1\u0003\u001fd\u0097!O\'\u00d4C/&Qz\u00d4' +
|
||||||
|
'\u0094\u0015l\u00b8\u008dQ+\u00db\u001d\u00c4\u00b5}\u00b2' +
|
||||||
|
'\u00d6\u0092\u00a3\u00df\u00a2i\u00a1\u009b\n\n*\u000f' +
|
||||||
|
'\u00d7\u00d6\u00a2\u00a8\u0085\u00e3<\u0083\u009c\u0093' +
|
||||||
|
'\u00c2\u0006\u00da0\u00a1\u00879(G\u00ed\'',
|
||||||
|
'latin1'),
|
||||||
|
`${cryptoType} with ${digest} digest failed to evaluate to expected hash`);
|
||||||
|
cryptoType = 'sha1';
|
||||||
|
digest = 'hex';
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
a4,
|
||||||
|
Buffer.from('8308651804facb7b9af8ffc53a33a22d6a1c8ac2', 'hex'),
|
||||||
|
`${cryptoType} with ${digest} digest failed to evaluate to expected hash`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Stream interface should produce the same result.
|
||||||
|
assert.deepStrictEqual(a5, a3);
|
||||||
|
assert.deepStrictEqual(a6, a3);
|
||||||
|
assert.notStrictEqual(a7, undefined);
|
||||||
|
assert.notStrictEqual(a8, undefined);
|
||||||
|
|
||||||
|
// Test multiple updates to same hash
|
||||||
|
const h1 = crypto.createHash('sha1').update('Test123').digest('hex');
|
||||||
|
const h2 = crypto.createHash('sha1').update('Test').update('123').digest('hex');
|
||||||
|
assert.strictEqual(h1, h2);
|
||||||
|
|
||||||
|
// Test hashing for binary files
|
||||||
|
const fn = fixtures.path('sample.png');
|
||||||
|
const sha1Hash = crypto.createHash('sha1');
|
||||||
|
const fileStream = fs.createReadStream(fn);
|
||||||
|
fileStream.on('data', function(data) {
|
||||||
|
sha1Hash.update(data);
|
||||||
|
});
|
||||||
|
fileStream.on('close', common.mustCall(function() {
|
||||||
|
// Test SHA1 of sample.png
|
||||||
|
assert.strictEqual(sha1Hash.digest('hex'),
|
||||||
|
'22723e553129a336ad96e10f6aecdf0f45e4149e');
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Issue https://github.com/nodejs/node-v0.x-archive/issues/2227: unknown digest
|
||||||
|
// method should throw an error.
|
||||||
|
assert.throws(function() {
|
||||||
|
crypto.createHash('xyzzy');
|
||||||
|
}, /Digest method not supported/);
|
||||||
|
|
||||||
|
// Issue https://github.com/nodejs/node/issues/9819: throwing encoding used to
|
||||||
|
// segfault.
|
||||||
|
assert.throws(
|
||||||
|
() => crypto.createHash('sha256').digest({
|
||||||
|
toString: () => { throw new Error('boom'); },
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: 'Error',
|
||||||
|
message: 'boom'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Issue https://github.com/nodejs/node/issues/25487: error message for invalid
|
||||||
|
// arg type to update method should include all possible types
|
||||||
|
assert.throws(
|
||||||
|
() => crypto.createHash('sha256').update(),
|
||||||
|
{
|
||||||
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
|
name: 'TypeError',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Default UTF-8 encoding
|
||||||
|
const hutf8 = crypto.createHash('sha512').update('УТФ-8 text').digest('hex');
|
||||||
|
assert.strictEqual(
|
||||||
|
hutf8,
|
||||||
|
'4b21bbd1a68e690a730ddcb5a8bc94ead9879ffe82580767ad7ec6fa8ba2dea6' +
|
||||||
|
'43a821af66afa9a45b6a78c712fecf0e56dc7f43aef4bcfc8eb5b4d8dca6ea5b');
|
||||||
|
|
||||||
|
assert.notStrictEqual(
|
||||||
|
hutf8,
|
||||||
|
crypto.createHash('sha512').update('УТФ-8 text', 'latin1').digest('hex'));
|
||||||
|
|
||||||
|
const h3 = crypto.createHash('sha256');
|
||||||
|
h3.digest();
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => h3.digest(),
|
||||||
|
{
|
||||||
|
code: 'ERR_CRYPTO_HASH_FINALIZED',
|
||||||
|
name: 'Error'
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => h3.update('foo'),
|
||||||
|
{
|
||||||
|
code: 'ERR_CRYPTO_HASH_FINALIZED',
|
||||||
|
name: 'Error'
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
crypto.createHash('sha256').update('test').digest('ucs2'),
|
||||||
|
crypto.createHash('sha256').update('test').digest().toString('ucs2'));
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => crypto.createHash(),
|
||||||
|
{
|
||||||
|
code: 'ERR_INVALID_ARG_TYPE',
|
||||||
|
name: 'TypeError',
|
||||||
|
message: 'The "algorithm" argument must be of type string. ' +
|
||||||
|
'Received undefined'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
{
|
||||||
|
const Hash = crypto.Hash;
|
||||||
|
const instance = crypto.Hash('sha256');
|
||||||
|
assert(instance instanceof Hash, 'Hash is expected to return a new instance' +
|
||||||
|
' when called without `new`');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test XOF hash functions and the outputLength option.
|
||||||
|
{
|
||||||
|
// Default outputLengths.
|
||||||
|
assert.strictEqual(crypto.createHash('shake128').digest('hex'),
|
||||||
|
'7f9c2ba4e88f827d616045507605853e');
|
||||||
|
assert.strictEqual(crypto.createHash('shake128', null).digest('hex'),
|
||||||
|
'7f9c2ba4e88f827d616045507605853e');
|
||||||
|
assert.strictEqual(crypto.createHash('shake256').digest('hex'),
|
||||||
|
'46b9dd2b0ba88d13233b3feb743eeb24' +
|
||||||
|
'3fcd52ea62b81b82b50c27646ed5762f');
|
||||||
|
assert.strictEqual(crypto.createHash('shake256', { outputLength: 0 })
|
||||||
|
.copy() // Default outputLength.
|
||||||
|
.digest('hex'),
|
||||||
|
'46b9dd2b0ba88d13233b3feb743eeb24' +
|
||||||
|
'3fcd52ea62b81b82b50c27646ed5762f');
|
||||||
|
|
||||||
|
// Short outputLengths.
|
||||||
|
assert.strictEqual(crypto.createHash('shake128', { outputLength: 0 })
|
||||||
|
.digest('hex'),
|
||||||
|
'');
|
||||||
|
assert.strictEqual(crypto.createHash('shake128', { outputLength: 5 })
|
||||||
|
.copy({ outputLength: 0 })
|
||||||
|
.digest('hex'),
|
||||||
|
'');
|
||||||
|
assert.strictEqual(crypto.createHash('shake128', { outputLength: 5 })
|
||||||
|
.digest('hex'),
|
||||||
|
'7f9c2ba4e8');
|
||||||
|
assert.strictEqual(crypto.createHash('shake128', { outputLength: 0 })
|
||||||
|
.copy({ outputLength: 5 })
|
||||||
|
.digest('hex'),
|
||||||
|
'7f9c2ba4e8');
|
||||||
|
assert.strictEqual(crypto.createHash('shake128', { outputLength: 15 })
|
||||||
|
.digest('hex'),
|
||||||
|
'7f9c2ba4e88f827d61604550760585');
|
||||||
|
assert.strictEqual(crypto.createHash('shake256', { outputLength: 16 })
|
||||||
|
.digest('hex'),
|
||||||
|
'46b9dd2b0ba88d13233b3feb743eeb24');
|
||||||
|
|
||||||
|
// Large outputLengths.
|
||||||
|
assert.strictEqual(crypto.createHash('shake128', { outputLength: 128 })
|
||||||
|
.digest('hex'),
|
||||||
|
'7f9c2ba4e88f827d616045507605853e' +
|
||||||
|
'd73b8093f6efbc88eb1a6eacfa66ef26' +
|
||||||
|
'3cb1eea988004b93103cfb0aeefd2a68' +
|
||||||
|
'6e01fa4a58e8a3639ca8a1e3f9ae57e2' +
|
||||||
|
'35b8cc873c23dc62b8d260169afa2f75' +
|
||||||
|
'ab916a58d974918835d25e6a435085b2' +
|
||||||
|
'badfd6dfaac359a5efbb7bcc4b59d538' +
|
||||||
|
'df9a04302e10c8bc1cbf1a0b3a5120ea');
|
||||||
|
const superLongHash = crypto.createHash('shake256', {
|
||||||
|
outputLength: 1024 * 1024
|
||||||
|
}).update('The message is shorter than the hash!')
|
||||||
|
.digest('hex');
|
||||||
|
assert.strictEqual(superLongHash.length, 2 * 1024 * 1024);
|
||||||
|
assert.ok(superLongHash.endsWith('193414035ddba77bf7bba97981e656ec'));
|
||||||
|
assert.ok(superLongHash.startsWith('a2a28dbc49cfd6e5d6ceea3d03e77748'));
|
||||||
|
|
||||||
|
// Non-XOF hash functions should accept valid outputLength options as well.
|
||||||
|
assert.strictEqual(crypto.createHash('sha224', { outputLength: 28 })
|
||||||
|
.digest('hex'),
|
||||||
|
'd14a028c2a3a2bc9476102bb288234c4' +
|
||||||
|
'15a2b01f828ea62ac5b3e42f');
|
||||||
|
|
||||||
|
// Passing invalid sizes should throw during creation.
|
||||||
|
assert.throws(() => {
|
||||||
|
crypto.createHash('sha256', { outputLength: 28 });
|
||||||
|
}, {
|
||||||
|
code: 'ERR_OSSL_EVP_NOT_XOF_OR_INVALID_LENGTH'
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const outputLength of [null, {}, 'foo', false]) {
|
||||||
|
assert.throws(() => crypto.createHash('sha256', { outputLength }),
|
||||||
|
{ code: 'ERR_INVALID_ARG_TYPE' });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const outputLength of [-1, .5, Infinity, 2 ** 90]) {
|
||||||
|
assert.throws(() => crypto.createHash('sha256', { outputLength }),
|
||||||
|
{ code: 'ERR_OUT_OF_RANGE' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const h = crypto.createHash('sha512');
|
||||||
|
h.digest();
|
||||||
|
assert.throws(() => h.copy(), { code: 'ERR_CRYPTO_HASH_FINALIZED' });
|
||||||
|
assert.throws(() => h.digest(), { code: 'ERR_CRYPTO_HASH_FINALIZED' });
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const a = crypto.createHash('sha512').update('abc');
|
||||||
|
const b = a.copy();
|
||||||
|
const c = b.copy().update('def');
|
||||||
|
const d = crypto.createHash('sha512').update('abcdef');
|
||||||
|
assert.strictEqual(a.digest('hex'), b.digest('hex'));
|
||||||
|
assert.strictEqual(c.digest('hex'), d.digest('hex'));
|
||||||
|
}
|
|
@ -1,11 +1,5 @@
|
||||||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
import {
|
import { createHash, createHmac, getHashes } from "node:crypto";
|
||||||
createHash,
|
|
||||||
createHmac,
|
|
||||||
getHashes,
|
|
||||||
randomFillSync,
|
|
||||||
randomUUID,
|
|
||||||
} from "node:crypto";
|
|
||||||
import { Buffer } from "node:buffer";
|
import { Buffer } from "node:buffer";
|
||||||
import { Readable } from "node:stream";
|
import { Readable } from "node:stream";
|
||||||
import { assert, assertEquals } from "@std/assert/mod.ts";
|
import { assert, assertEquals } from "@std/assert/mod.ts";
|
||||||
|
@ -123,17 +117,13 @@ Deno.test("[node/crypto.getHashes]", () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Deno.test("[node/crypto.getRandomUUID] works the same way as Web Crypto API", () => {
|
Deno.test("[node/crypto.hash] supports buffer args", () => {
|
||||||
assertEquals(randomUUID().length, crypto.randomUUID().length);
|
const buffer = Buffer.from("abc");
|
||||||
assertEquals(typeof randomUUID(), typeof crypto.randomUUID());
|
const d = createHash("sha1").update(buffer).digest("hex");
|
||||||
|
assertEquals(d, "a9993e364706816aba3e25717850c26c9cd0d89d");
|
||||||
});
|
});
|
||||||
|
|
||||||
Deno.test("[node/crypto.randomFillSync] supported arguments", () => {
|
Deno.test("[node/crypto.hash] does not leak", () => {
|
||||||
const buf = new Uint8Array(10);
|
const hasher = createHash("sha1");
|
||||||
|
hasher.update("abc");
|
||||||
assert(randomFillSync(buf));
|
|
||||||
assert(randomFillSync(buf, 0));
|
|
||||||
// @ts-ignore: arraybuffer arguments are valid.
|
|
||||||
assert(randomFillSync(buf.buffer));
|
|
||||||
assert(randomFillSync(new DataView(buf.buffer)));
|
|
||||||
});
|
});
|
||||||
|
|
18
tests/unit_node/crypto/crypto_misc_test.ts
Normal file
18
tests/unit_node/crypto/crypto_misc_test.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
import { randomFillSync, randomUUID } from "node:crypto";
|
||||||
|
import { assert, assertEquals } from "../../unit/test_util.ts";
|
||||||
|
|
||||||
|
Deno.test("[node/crypto.getRandomUUID] works the same way as Web Crypto API", () => {
|
||||||
|
assertEquals(randomUUID().length, crypto.randomUUID().length);
|
||||||
|
assertEquals(typeof randomUUID(), typeof crypto.randomUUID());
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test("[node/crypto.randomFillSync] supported arguments", () => {
|
||||||
|
const buf = new Uint8Array(10);
|
||||||
|
|
||||||
|
assert(randomFillSync(buf));
|
||||||
|
assert(randomFillSync(buf, 0));
|
||||||
|
// @ts-ignore: arraybuffer arguments are valid.
|
||||||
|
assert(randomFillSync(buf.buffer));
|
||||||
|
assert(randomFillSync(new DataView(buf.buffer)));
|
||||||
|
});
|
Loading…
Reference in a new issue