Skip to content

Commit 0049368

Browse files
divybotlittledivy
andauthored
fix(ext/node): throw ERR_CRYPTO_HASH_FINALIZED on subsequent Hash.digest() calls (#33774)
## Summary Enables `test-crypto-hash` in node_compat suite. ## Test plan - [x] `cargo test --test node_compat -- test-crypto-hash` Co-authored-by: Divy Srivastava <me@littledivy.com>
1 parent 2d77222 commit 0049368

3 files changed

Lines changed: 24 additions & 6 deletions

File tree

ext/node/polyfills/internal/crypto/hash.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ function unwrapErr(ok: boolean) {
6161
}
6262

6363
const kHandle = Symbol("kHandle");
64+
const kFinalized = Symbol("kFinalized");
65+
66+
let warnedShakeOutputLength = false;
6467

6568
export function Hash(
6669
this: Hash,
@@ -86,8 +89,10 @@ export function Hash(
8689
if (
8790
!isCopy && xofLen === undefined &&
8891
(algoLower === "shake128" ||
89-
algoLower === "shake256")
92+
algoLower === "shake256") &&
93+
!warnedShakeOutputLength
9094
) {
95+
warnedShakeOutputLength = true;
9196
process.emitWarning(
9297
"Creating SHAKE128/256 digests without an explicit options.outputLength is deprecated.",
9398
"DeprecationWarning",
@@ -118,6 +123,7 @@ export function Hash(
118123

119124
interface Hash {
120125
[kHandle]: object;
126+
[kFinalized]: boolean;
121127
}
122128

123129
ObjectSetPrototypeOf(Hash.prototype, LazyTransform.prototype);
@@ -137,7 +143,13 @@ Hash.prototype._transform = function _transform(
137143
};
138144

139145
Hash.prototype._flush = function _flush(callback: () => void) {
140-
this.push(this.digest());
146+
// Internal Transform flush: pull the digest from the native handle without
147+
// marking the JS-level hash as finalised. Node does the same: its `_flush`
148+
// calls `this[kHandle].digest()` directly, allowing a single user-level
149+
// `hash.digest(encoding)` to still succeed afterwards (e.g. when the hash
150+
// is consumed via `stream.pipeline`).
151+
const digest = op_node_hash_digest(this[kHandle]);
152+
this.push(digest === null ? Buffer.alloc(0) : Buffer.from(digest));
141153
callback();
142154
};
143155

@@ -169,17 +181,22 @@ Hash.prototype.update = function update(
169181
};
170182

171183
Hash.prototype.digest = function digest(outputEncoding: Encoding | "buffer") {
184+
if (this[kFinalized]) {
185+
throw new ERR_CRYPTO_HASH_FINALIZED();
186+
}
172187
outputEncoding = outputEncoding || getDefaultEncoding();
173188
outputEncoding = `${outputEncoding}`;
174189

175190
if (outputEncoding === "hex") {
176191
const result = op_node_hash_digest_hex(this[kHandle]);
177192
if (result === null) throw new ERR_CRYPTO_HASH_FINALIZED();
193+
this[kFinalized] = true;
178194
return result;
179195
}
180196

181197
const digest = op_node_hash_digest(this[kHandle]);
182198
if (digest === null) throw new ERR_CRYPTO_HASH_FINALIZED();
199+
this[kFinalized] = true;
183200

184201
// TODO(@littedivy): Fast paths for below encodings.
185202
switch (outputEncoding) {

tests/node_compat/config.jsonc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,11 @@
502502
"parallel/test-crypto-gcm-implicit-short-tag.js": {},
503503
"parallel/test-crypto-getcipherinfo.js": {},
504504
"parallel/test-crypto-hash-stream-pipe.js": {},
505+
"parallel/test-crypto-hash.js": {
506+
"exitCode": 1,
507+
"output": "[WILDCARD]Mismatched <anonymous> function calls. Expected exactly 2, actual 1.[WILDCARD]",
508+
"reason": "Test's expectWarning expects DEP0198 to fire, but the SHAKE128/256 block is gated on `!process.features.openssl_is_boringssl`; Deno reports BoringSSL=true so the SHAKE block — and thus the DEP0198 emit — is skipped"
509+
},
505510
"parallel/test-crypto-hkdf.js": {},
506511
"parallel/test-crypto-hmac.js": {},
507512
"parallel/test-crypto-keygen.js": {},

tests/unit_node/crypto/crypto_hash_test.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,4 @@ Deno.test("[node/crypto.Hash] digest after stream.pipeline", async () => {
267267
h.digest("hex"),
268268
"2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
269269
);
270-
assertEquals(
271-
h.digest("base64"),
272-
"LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=",
273-
);
274270
});

0 commit comments

Comments
 (0)