Skip to content

Commit

Permalink
fix(crypto): validate that digest length must be an integer between 0…
Browse files Browse the repository at this point in the history
… and isize::MAX (#3799)
  • Loading branch information
jeremyBanks authored Nov 15, 2023
1 parent 255a376 commit c93b978
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 14 deletions.
40 changes: 34 additions & 6 deletions crypto/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,9 @@ const stdCrypto: StdCrypto = ((x) => x)({
data: BufferSource | AsyncIterable<BufferSource> | Iterable<BufferSource>,
): Promise<ArrayBuffer> {
const { name, length } = normalizeAlgorithm(algorithm);

assertValidDigestLength(length);

const bytes = bufferSourceBytes(data);

if (FNVAlgorithms.includes(name)) {
Expand Down Expand Up @@ -280,28 +283,30 @@ const stdCrypto: StdCrypto = ((x) => x)({
algorithm: DigestAlgorithm,
data: BufferSource | Iterable<BufferSource>,
): ArrayBuffer {
algorithm = normalizeAlgorithm(algorithm);
const { name, length } = normalizeAlgorithm(algorithm);

assertValidDigestLength(length);

const bytes = bufferSourceBytes(data);

if (FNVAlgorithms.includes(algorithm.name)) {
return fnv(algorithm.name, bytes);
if (FNVAlgorithms.includes(name)) {
return fnv(name, bytes);
}

const wasmCrypto = instantiateWasm();
if (bytes) {
return wasmCrypto.digest(algorithm.name, bytes, algorithm.length)
return wasmCrypto.digest(name, bytes, length)
.buffer;
} else if ((data as Iterable<BufferSource>)[Symbol.iterator]) {
const context = new wasmCrypto.DigestContext(algorithm.name);
const context = new wasmCrypto.DigestContext(name);
for (const chunk of data as Iterable<BufferSource>) {
const chunkBytes = bufferSourceBytes(chunk);
if (!chunkBytes) {
throw new TypeError("data contained chunk of the wrong type");
}
context.update(chunkBytes);
}
return context.digestAndDrop(algorithm.length).buffer;
return context.digestAndDrop(length).buffer;
} else {
throw new TypeError(
"data must be a BufferSource or Iterable<BufferSource>",
Expand All @@ -328,6 +333,29 @@ const webCryptoDigestAlgorithms = [
export type FNVAlgorithms = "FNV32" | "FNV32A" | "FNV64" | "FNV64A";
export type DigestAlgorithmName = WasmDigestAlgorithm | FNVAlgorithms;

/*
* The largest digest length the current WASM implementation can support. This
* is the value of `isize::MAX` on 32-bit platforms like WASM, which is the
* maximum allowed capacity of a Rust `Vec<u8>`.
*/
const MAX_DIGEST_LENGTH = 0x7FFF_FFFF;

/**
* Asserts that a number is a valid length for a digest, which must be an
* integer that fits in a Rust `Vec<u8>`, or be undefined.
*/
function assertValidDigestLength(value?: number) {
if (
value !== undefined &&
(value < 0 || value > MAX_DIGEST_LENGTH ||
!Number.isInteger(value))
) {
throw new RangeError(
`length must be an integer between 0 and ${MAX_DIGEST_LENGTH}, inclusive`,
);
}
}

export type DigestAlgorithmObject = {
name: DigestAlgorithmName;
length?: number;
Expand Down
37 changes: 29 additions & 8 deletions crypto/crypto_test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
import { assert, assertEquals, assertInstanceOf } from "../assert/mod.ts";
import { assert, assertEquals, assertInstanceOf, fail } from "../assert/mod.ts";
import { crypto as stdCrypto } from "./mod.ts";
import { repeat } from "../bytes/repeat.ts";
import { dirname, fromFileUrl } from "../path/mod.ts";
Expand Down Expand Up @@ -1363,7 +1363,11 @@ for (const algorithm of digestAlgorithms) {
const bytePieces = pieces.map((piece) =>
typeof piece === "string" ? new TextEncoder().encode(piece) : piece
) as Array<BufferSource>;
try {

// Expected value will either be a hex string, if the case is expected
// to return successfully, or an error class/constructor function, if
// the case is expected to throw.
if (typeof expected === "string") {
const actual = toHexString(
await stdCrypto.subtle.digest({
...options,
Expand All @@ -1379,15 +1383,32 @@ for (const algorithm of digestAlgorithms) {
JSON.stringify(options)
}) returned unexpected value\n actual: ${actual}\nexpected: ${expected}`,
);
} catch (error) {
if (expected instanceof Function) {
assert(
error instanceof expected,
`got a different error than expected: ${error}`,
} else if (typeof expected === "function") {
let error;
try {
await stdCrypto.subtle.digest({
...options,
name: algorithm,
}, bytePieces);
} catch (caughtError) {
error = caughtError;
}
if (error !== undefined) {
assertInstanceOf(
error,
expected,
);
} else {
throw error;
fail(
`${algorithm} of ${caption}${
i > 0 ? ` (but not until variation [${i}]!)` : ""
} with options ${
JSON.stringify(options)
}) expected an exception of type ${expected.name}, but none was thrown.`,
);
}
} else {
throw new TypeError("expected value has an unexpected type");
}
}
}
Expand Down

0 comments on commit c93b978

Please sign in to comment.