From 2e26d2fa9c9633259ab7fbdceeaa72f5f6a0caae Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Wed, 28 Dec 2022 17:52:27 +1000 Subject: [PATCH] refactor(crypto): single-export files --- crypto/_util.ts | 2 +- crypto/crypto.ts | 388 ++++++++++++++++++ crypto/{test.ts => crypto_test.ts} | 0 crypto/mod.ts | 384 +---------------- crypto/to_hash_string.ts | 33 ++ .../{util_test.ts => to_hash_string_test.ts} | 2 +- crypto/util.ts | 51 +-- http/file_server.ts | 3 +- http/file_server_test.ts | 2 +- 9 files changed, 451 insertions(+), 414 deletions(-) create mode 100644 crypto/crypto.ts rename crypto/{test.ts => crypto_test.ts} (100%) create mode 100644 crypto/to_hash_string.ts rename crypto/{util_test.ts => to_hash_string_test.ts} (99%) diff --git a/crypto/_util.ts b/crypto/_util.ts index 055fba8ee5df..3628251c52f4 100644 --- a/crypto/_util.ts +++ b/crypto/_util.ts @@ -8,7 +8,7 @@ const encoder = new TextEncoder(); * * @example Before: * ```ts - * import { crypto } from "https://deno.land/std@$STD_VERSION/crypto/mod.ts"; + * import { crypto } from "https://deno.land/std@$STD_VERSION/crypto/crypto.ts"; * * const encoder = new TextEncoder(); * diff --git a/crypto/crypto.ts b/crypto/crypto.ts new file mode 100644 index 000000000000..744488f814b9 --- /dev/null +++ b/crypto/crypto.ts @@ -0,0 +1,388 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +/** + * Extensions to the + * [Web Crypto](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API) + * supporting additional encryption APIs, but also delegating to the built-in + * APIs when possible. + * + * Provides additional digest algorithms that are not part of the WebCrypto + * standard as well as a `subtle.digest` and `subtle.digestSync` methods. It + * also provides a `subtle.timingSafeEqual()` method to compare array buffers + * or data views in a way that isn't prone to timing based attacks. + * + * The "polyfill" delegates to `WebCrypto` where possible. + * + * The {@linkcode KeyStack} export implements the {@linkcode KeyRing} interface + * for managing rotatable keys for signing data to prevent tampering, like with + * HTTP cookies. + * + * ## Supported algorithms + * + * Here is a list of supported algorithms. If the algorithm name in WebCrypto + * and Wasm/Rust is the same, this library prefers to use algorithms that are + * supported by WebCrypto. + * + * WebCrypto + * + * ```ts + * // https://deno.land/std/crypto/crypto.ts + * const webCryptoDigestAlgorithms = [ + * "SHA-384", + * "SHA-256", + * "SHA-512", + * // insecure (length-extendable and collidable): + * "SHA-1", + * ] as const; + * ``` + * + * Wasm/Rust + * + * ```ts + * // https://deno.land/std/_wasm_crypto/crypto.ts + * export const digestAlgorithms = [ + * "BLAKE2B-256", + * "BLAKE2B-384", + * "BLAKE2B", + * "BLAKE2S", + * "BLAKE3", + * "KECCAK-224", + * "KECCAK-256", + * "KECCAK-384", + * "KECCAK-512", + * "SHA-384", + * "SHA3-224", + * "SHA3-256", + * "SHA3-384", + * "SHA3-512", + * "SHAKE128", + * "SHAKE256", + * "TIGER", + * // insecure (length-extendable): + * "RIPEMD-160", + * "SHA-224", + * "SHA-256", + * "SHA-512", + * // insecure (collidable and length-extendable): + * "MD5", + * "SHA-1", + * ] as const; + * ``` + * + * ## Timing safe comparison + * + * When checking the values of cryptographic hashes are equal, default + * comparisons can be susceptible to timing based attacks, where attacker is + * able to find out information about the host system by repeatedly checking + * response times to equality comparisons of values. + * + * It is likely some form of timing safe equality will make its way to the + * WebCrypto standard (see: + * [w3c/webcrypto#270](https://github.com/w3c/webcrypto/issues/270)), but until + * that time, `timingSafeEqual()` is provided: + * + * ```ts + * import { crypto } from "https://deno.land/std@$STD_VERSION/crypto/mod.ts"; + * import { assert } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts"; + * + * const a = await crypto.subtle.digest( + * "SHA-384", + * new TextEncoder().encode("hello world"), + * ); + * const b = await crypto.subtle.digest( + * "SHA-384", + * new TextEncoder().encode("hello world"), + * ); + * const c = await crypto.subtle.digest( + * "SHA-384", + * new TextEncoder().encode("hello deno"), + * ); + * + * assert(crypto.subtle.timingSafeEqual(a, b)); + * assert(!crypto.subtle.timingSafeEqual(a, c)); + * ``` + * + * In addition to the method being part of the `crypto.subtle` interface, it is + * also loadable directly: + * + * ```ts + * import { timingSafeEqual } from "https://deno.land/std@$STD_VERSION/crypto/timing_safe_equal.ts"; + * import { assert } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts"; + * + * const a = await crypto.subtle.digest( + * "SHA-384", + * new TextEncoder().encode("hello world"), + * ); + * const b = await crypto.subtle.digest( + * "SHA-384", + * new TextEncoder().encode("hello world"), + * ); + * + * assert(timingSafeEqual(a, b)); + * ``` + * + * @example + * ```ts + * import { crypto } from "https://deno.land/std@$STD_VERSION/crypto/mod.ts"; + * + * // This will delegate to the runtime's WebCrypto implementation. + * console.log( + * new Uint8Array( + * await crypto.subtle.digest( + * "SHA-384", + * new TextEncoder().encode("hello world"), + * ), + * ), + * ); + * + * // This will use a bundled Wasm/Rust implementation. + * console.log( + * new Uint8Array( + * await crypto.subtle.digest( + * "BLAKE3", + * new TextEncoder().encode("hello world"), + * ), + * ), + * ); + * ``` + * + * @example Convert hash to a string + * + * ```ts + * import { + * crypto, + * toHashString, + * } from "https://deno.land/std@$STD_VERSION/crypto/mod.ts"; + * + * const hash = await crypto.subtle.digest( + * "SHA-384", + * new TextEncoder().encode("You hear that Mr. Anderson?"), + * ); + * + * // Hex encoding by default + * console.log(toHashString(hash)); + * + * // Or with base64 encoding + * console.log(toHashString(hash, "base64")); + * ``` + * + * @module + */ + +import { + DigestAlgorithm as WasmDigestAlgorithm, + digestAlgorithms as wasmDigestAlgorithms, + instantiateWasm, +} from "./_wasm/mod.ts"; +import { timingSafeEqual } from "./timing_safe_equal.ts"; +import { fnv } from "./_fnv/index.ts"; + +/** + * A copy of the global WebCrypto interface, with methods bound so they're + * safe to re-export. + */ +const webCrypto = ((crypto) => ({ + getRandomValues: crypto.getRandomValues?.bind(crypto), + randomUUID: crypto.randomUUID?.bind(crypto), + subtle: { + decrypt: crypto.subtle?.decrypt?.bind(crypto.subtle), + deriveBits: crypto.subtle?.deriveBits?.bind(crypto.subtle), + deriveKey: crypto.subtle?.deriveKey?.bind(crypto.subtle), + digest: crypto.subtle?.digest?.bind(crypto.subtle), + encrypt: crypto.subtle?.encrypt?.bind(crypto.subtle), + exportKey: crypto.subtle?.exportKey?.bind(crypto.subtle), + generateKey: crypto.subtle?.generateKey?.bind(crypto.subtle), + importKey: crypto.subtle?.importKey?.bind(crypto.subtle), + sign: crypto.subtle?.sign?.bind(crypto.subtle), + unwrapKey: crypto.subtle?.unwrapKey?.bind(crypto.subtle), + verify: crypto.subtle?.verify?.bind(crypto.subtle), + wrapKey: crypto.subtle?.wrapKey?.bind(crypto.subtle), + }, +}))(globalThis.crypto); + +const bufferSourceBytes = (data: BufferSource | unknown) => { + let bytes: Uint8Array | undefined; + if (data instanceof Uint8Array) { + bytes = data; + } else if (ArrayBuffer.isView(data)) { + bytes = new Uint8Array(data.buffer, data.byteOffset, data.byteLength); + } else if (data instanceof ArrayBuffer) { + bytes = new Uint8Array(data); + } + return bytes; +}; + +/** Extensions to the web standard `SubtleCrypto` interface. */ +export interface StdSubtleCrypto extends SubtleCrypto { + /** + * Returns a new `Promise` object that will digest `data` using the specified + * `AlgorithmIdentifier`. + */ + digest( + algorithm: DigestAlgorithm, + data: BufferSource | AsyncIterable | Iterable, + ): Promise; + + /** + * Returns a ArrayBuffer with the result of digesting `data` using the + * specified `AlgorithmIdentifier`. + */ + digestSync( + algorithm: DigestAlgorithm, + data: BufferSource | Iterable, + ): ArrayBuffer; + + /** Compare to array buffers or data views in a way that timing based attacks + * cannot gain information about the platform. */ + timingSafeEqual( + a: ArrayBufferLike | DataView, + b: ArrayBufferLike | DataView, + ): boolean; +} + +/** Extensions to the Web {@linkcode Crypto} interface. */ +export interface StdCrypto extends Crypto { + readonly subtle: StdSubtleCrypto; +} + +/** + * An wrapper for WebCrypto adding support for additional non-standard + * algorithms, but delegating to the runtime WebCrypto implementation whenever + * possible. + */ +const stdCrypto: StdCrypto = ((x) => x)({ + ...webCrypto, + subtle: { + ...webCrypto.subtle, + + /** + * Polyfills stream support until the Web Crypto API does so: + * @see {@link https://github.com/wintercg/proposal-webcrypto-streams} + */ + async digest( + algorithm: DigestAlgorithm, + data: BufferSource | AsyncIterable | Iterable, + ): Promise { + const { name, length } = normalizeAlgorithm(algorithm); + const bytes = bufferSourceBytes(data); + + if (FNVAlgorithms.includes(name)) { + return fnv(name, bytes); + } + + // We delegate to WebCrypto whenever possible, + if ( + // if the algorithm is supported by the WebCrypto standard, + (webCryptoDigestAlgorithms as readonly string[]).includes(name) && + // and the data is a single buffer, + bytes + ) { + return webCrypto.subtle.digest(algorithm, bytes); + } else if (wasmDigestAlgorithms.includes(name as WasmDigestAlgorithm)) { + if (bytes) { + // Otherwise, we use our bundled Wasm implementation via digestSync + // if it supports the algorithm. + return stdCrypto.subtle.digestSync(algorithm, bytes); + } else if ((data as Iterable)[Symbol.iterator]) { + return stdCrypto.subtle.digestSync( + algorithm, + data as Iterable, + ); + } else if ( + (data as AsyncIterable)[Symbol.asyncIterator] + ) { + const wasmCrypto = instantiateWasm(); + const context = new wasmCrypto.DigestContext(name); + for await (const chunk of data as AsyncIterable) { + const chunkBytes = bufferSourceBytes(chunk); + if (!chunkBytes) { + throw new TypeError("data contained chunk of the wrong type"); + } + context.update(chunkBytes); + } + return context.digestAndDrop(length).buffer; + } else { + throw new TypeError( + "data must be a BufferSource or [Async]Iterable", + ); + } + } else if (webCrypto.subtle?.digest) { + // (TypeScript type definitions prohibit this case.) If they're trying + // to call an algorithm we don't recognize, pass it along to WebCrypto + // in case it's a non-standard algorithm supported by the the runtime + // they're using. + return webCrypto.subtle.digest( + algorithm, + (data as unknown) as Uint8Array, + ); + } else { + throw new TypeError(`unsupported digest algorithm: ${algorithm}`); + } + }, + + digestSync( + algorithm: DigestAlgorithm, + data: BufferSource | Iterable, + ): ArrayBuffer { + algorithm = normalizeAlgorithm(algorithm); + + const bytes = bufferSourceBytes(data); + + if (FNVAlgorithms.includes(algorithm.name)) { + return fnv(algorithm.name, bytes); + } + + const wasmCrypto = instantiateWasm(); + if (bytes) { + return wasmCrypto.digest(algorithm.name, bytes, algorithm.length) + .buffer; + } else if ((data as Iterable)[Symbol.iterator]) { + const context = new wasmCrypto.DigestContext(algorithm.name); + for (const chunk of data as Iterable) { + 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; + } else { + throw new TypeError( + "data must be a BufferSource or Iterable", + ); + } + }, + + // TODO(@kitsonk): rework when https://github.com/w3c/webcrypto/issues/270 resolved + timingSafeEqual, + }, +}); + +const FNVAlgorithms = ["FNV32", "FNV32A", "FNV64", "FNV64A"]; + +/** Digest algorithms supported by WebCrypto. */ +const webCryptoDigestAlgorithms = [ + "SHA-384", + "SHA-256", + "SHA-512", + // insecure (length-extendable and collidable): + "SHA-1", +] as const; + +export type FNVAlgorithms = "FNV32" | "FNV32A" | "FNV64" | "FNV64A"; +export type DigestAlgorithmName = WasmDigestAlgorithm | FNVAlgorithms; + +export type DigestAlgorithmObject = { + name: DigestAlgorithmName; + length?: number; +}; + +export type DigestAlgorithm = DigestAlgorithmName | DigestAlgorithmObject; + +const normalizeAlgorithm = (algorithm: DigestAlgorithm) => + ((typeof algorithm === "string") ? { name: algorithm.toUpperCase() } : { + ...algorithm, + name: algorithm.name.toUpperCase(), + }) as DigestAlgorithmObject; + +export { stdCrypto as crypto }; diff --git a/crypto/test.ts b/crypto/crypto_test.ts similarity index 100% rename from crypto/test.ts rename to crypto/crypto_test.ts diff --git a/crypto/mod.ts b/crypto/mod.ts index c3e948621ff6..fa0329b9c2c3 100644 --- a/crypto/mod.ts +++ b/crypto/mod.ts @@ -6,386 +6,10 @@ * supporting additional encryption APIs, but also delegating to the built-in * APIs when possible. * - * Provides additional digest algorithms that are not part of the WebCrypto - * standard as well as a `subtle.digest` and `subtle.digestSync` methods. It - * also provides a `subtle.timingSafeEqual()` method to compare array buffers - * or data views in a way that isn't prone to timing based attacks. - * - * The "polyfill" delegates to `WebCrypto` where possible. - * - * The {@linkcode KeyStack} export implements the {@linkcode KeyRing} interface - * for managing rotatable keys for signing data to prevent tampering, like with - * HTTP cookies. - * - * ## Supported algorithms - * - * Here is a list of supported algorithms. If the algorithm name in WebCrypto - * and Wasm/Rust is the same, this library prefers to use algorithms that are - * supported by WebCrypto. - * - * WebCrypto - * - * ```ts - * // https://deno.land/std/crypto/mod.ts - * const webCryptoDigestAlgorithms = [ - * "SHA-384", - * "SHA-256", - * "SHA-512", - * // insecure (length-extendable and collidable): - * "SHA-1", - * ] as const; - * ``` - * - * Wasm/Rust - * - * ```ts - * // https://deno.land/std/_wasm_crypto/mod.ts - * export const digestAlgorithms = [ - * "BLAKE2B-256", - * "BLAKE2B-384", - * "BLAKE2B", - * "BLAKE2S", - * "BLAKE3", - * "KECCAK-224", - * "KECCAK-256", - * "KECCAK-384", - * "KECCAK-512", - * "SHA-384", - * "SHA3-224", - * "SHA3-256", - * "SHA3-384", - * "SHA3-512", - * "SHAKE128", - * "SHAKE256", - * "TIGER", - * // insecure (length-extendable): - * "RIPEMD-160", - * "SHA-224", - * "SHA-256", - * "SHA-512", - * // insecure (collidable and length-extendable): - * "MD5", - * "SHA-1", - * ] as const; - * ``` - * - * ## Timing safe comparison - * - * When checking the values of cryptographic hashes are equal, default - * comparisons can be susceptible to timing based attacks, where attacker is - * able to find out information about the host system by repeatedly checking - * response times to equality comparisons of values. - * - * It is likely some form of timing safe equality will make its way to the - * WebCrypto standard (see: - * [w3c/webcrypto#270](https://github.com/w3c/webcrypto/issues/270)), but until - * that time, `timingSafeEqual()` is provided: - * - * ```ts - * import { crypto } from "https://deno.land/std@$STD_VERSION/crypto/mod.ts"; - * import { assert } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts"; - * - * const a = await crypto.subtle.digest( - * "SHA-384", - * new TextEncoder().encode("hello world"), - * ); - * const b = await crypto.subtle.digest( - * "SHA-384", - * new TextEncoder().encode("hello world"), - * ); - * const c = await crypto.subtle.digest( - * "SHA-384", - * new TextEncoder().encode("hello deno"), - * ); - * - * assert(crypto.subtle.timingSafeEqual(a, b)); - * assert(!crypto.subtle.timingSafeEqual(a, c)); - * ``` - * - * In addition to the method being part of the `crypto.subtle` interface, it is - * also loadable directly: - * - * ```ts - * import { timingSafeEqual } from "https://deno.land/std@$STD_VERSION/crypto/timing_safe_equal.ts"; - * import { assert } from "https://deno.land/std@$STD_VERSION/testing/asserts.ts"; - * - * const a = await crypto.subtle.digest( - * "SHA-384", - * new TextEncoder().encode("hello world"), - * ); - * const b = await crypto.subtle.digest( - * "SHA-384", - * new TextEncoder().encode("hello world"), - * ); - * - * assert(timingSafeEqual(a, b)); - * ``` - * - * @example - * ```ts - * import { crypto } from "https://deno.land/std@$STD_VERSION/crypto/mod.ts"; - * - * // This will delegate to the runtime's WebCrypto implementation. - * console.log( - * new Uint8Array( - * await crypto.subtle.digest( - * "SHA-384", - * new TextEncoder().encode("hello world"), - * ), - * ), - * ); - * - * // This will use a bundled Wasm/Rust implementation. - * console.log( - * new Uint8Array( - * await crypto.subtle.digest( - * "BLAKE3", - * new TextEncoder().encode("hello world"), - * ), - * ), - * ); - * ``` - * - * @example Convert hash to a string - * - * ```ts - * import { - * crypto, - * toHashString, - * } from "https://deno.land/std@$STD_VERSION/crypto/mod.ts"; - * - * const hash = await crypto.subtle.digest( - * "SHA-384", - * new TextEncoder().encode("You hear that Mr. Anderson?"), - * ); - * - * // Hex encoding by default - * console.log(toHashString(hash)); - * - * // Or with base64 encoding - * console.log(toHashString(hash, "base64")); - * ``` - * * @module */ -import { - DigestAlgorithm as WasmDigestAlgorithm, - digestAlgorithms as wasmDigestAlgorithms, - instantiateWasm, -} from "./_wasm/mod.ts"; -import { timingSafeEqual } from "./timing_safe_equal.ts"; -import { fnv } from "./_fnv/index.ts"; - -export { type Data, type Key, KeyStack } from "./keystack.ts"; -export { toHashString } from "./util.ts"; - -/** - * A copy of the global WebCrypto interface, with methods bound so they're - * safe to re-export. - */ -const webCrypto = ((crypto) => ({ - getRandomValues: crypto.getRandomValues?.bind(crypto), - randomUUID: crypto.randomUUID?.bind(crypto), - subtle: { - decrypt: crypto.subtle?.decrypt?.bind(crypto.subtle), - deriveBits: crypto.subtle?.deriveBits?.bind(crypto.subtle), - deriveKey: crypto.subtle?.deriveKey?.bind(crypto.subtle), - digest: crypto.subtle?.digest?.bind(crypto.subtle), - encrypt: crypto.subtle?.encrypt?.bind(crypto.subtle), - exportKey: crypto.subtle?.exportKey?.bind(crypto.subtle), - generateKey: crypto.subtle?.generateKey?.bind(crypto.subtle), - importKey: crypto.subtle?.importKey?.bind(crypto.subtle), - sign: crypto.subtle?.sign?.bind(crypto.subtle), - unwrapKey: crypto.subtle?.unwrapKey?.bind(crypto.subtle), - verify: crypto.subtle?.verify?.bind(crypto.subtle), - wrapKey: crypto.subtle?.wrapKey?.bind(crypto.subtle), - }, -}))(globalThis.crypto); - -const bufferSourceBytes = (data: BufferSource | unknown) => { - let bytes: Uint8Array | undefined; - if (data instanceof Uint8Array) { - bytes = data; - } else if (ArrayBuffer.isView(data)) { - bytes = new Uint8Array(data.buffer, data.byteOffset, data.byteLength); - } else if (data instanceof ArrayBuffer) { - bytes = new Uint8Array(data); - } - return bytes; -}; - -/** Extensions to the web standard `SubtleCrypto` interface. */ -export interface StdSubtleCrypto extends SubtleCrypto { - /** - * Returns a new `Promise` object that will digest `data` using the specified - * `AlgorithmIdentifier`. - */ - digest( - algorithm: DigestAlgorithm, - data: BufferSource | AsyncIterable | Iterable, - ): Promise; - - /** - * Returns a ArrayBuffer with the result of digesting `data` using the - * specified `AlgorithmIdentifier`. - */ - digestSync( - algorithm: DigestAlgorithm, - data: BufferSource | Iterable, - ): ArrayBuffer; - - /** Compare to array buffers or data views in a way that timing based attacks - * cannot gain information about the platform. */ - timingSafeEqual( - a: ArrayBufferLike | DataView, - b: ArrayBufferLike | DataView, - ): boolean; -} - -/** Extensions to the Web {@linkcode Crypto} interface. */ -export interface StdCrypto extends Crypto { - readonly subtle: StdSubtleCrypto; -} - -/** - * An wrapper for WebCrypto adding support for additional non-standard - * algorithms, but delegating to the runtime WebCrypto implementation whenever - * possible. - */ -const stdCrypto: StdCrypto = ((x) => x)({ - ...webCrypto, - subtle: { - ...webCrypto.subtle, - - /** - * Polyfills stream support until the Web Crypto API does so: - * @see {@link https://github.com/wintercg/proposal-webcrypto-streams} - */ - async digest( - algorithm: DigestAlgorithm, - data: BufferSource | AsyncIterable | Iterable, - ): Promise { - const { name, length } = normalizeAlgorithm(algorithm); - const bytes = bufferSourceBytes(data); - - if (FNVAlgorithms.includes(name)) { - return fnv(name, bytes); - } - - // We delegate to WebCrypto whenever possible, - if ( - // if the algorithm is supported by the WebCrypto standard, - (webCryptoDigestAlgorithms as readonly string[]).includes(name) && - // and the data is a single buffer, - bytes - ) { - return webCrypto.subtle.digest(algorithm, bytes); - } else if (wasmDigestAlgorithms.includes(name as WasmDigestAlgorithm)) { - if (bytes) { - // Otherwise, we use our bundled Wasm implementation via digestSync - // if it supports the algorithm. - return stdCrypto.subtle.digestSync(algorithm, bytes); - } else if ((data as Iterable)[Symbol.iterator]) { - return stdCrypto.subtle.digestSync( - algorithm, - data as Iterable, - ); - } else if ( - (data as AsyncIterable)[Symbol.asyncIterator] - ) { - const wasmCrypto = instantiateWasm(); - const context = new wasmCrypto.DigestContext(name); - for await (const chunk of data as AsyncIterable) { - const chunkBytes = bufferSourceBytes(chunk); - if (!chunkBytes) { - throw new TypeError("data contained chunk of the wrong type"); - } - context.update(chunkBytes); - } - return context.digestAndDrop(length).buffer; - } else { - throw new TypeError( - "data must be a BufferSource or [Async]Iterable", - ); - } - } else if (webCrypto.subtle?.digest) { - // (TypeScript type definitions prohibit this case.) If they're trying - // to call an algorithm we don't recognize, pass it along to WebCrypto - // in case it's a non-standard algorithm supported by the the runtime - // they're using. - return webCrypto.subtle.digest( - algorithm, - (data as unknown) as Uint8Array, - ); - } else { - throw new TypeError(`unsupported digest algorithm: ${algorithm}`); - } - }, - - digestSync( - algorithm: DigestAlgorithm, - data: BufferSource | Iterable, - ): ArrayBuffer { - algorithm = normalizeAlgorithm(algorithm); - - const bytes = bufferSourceBytes(data); - - if (FNVAlgorithms.includes(algorithm.name)) { - return fnv(algorithm.name, bytes); - } - - const wasmCrypto = instantiateWasm(); - if (bytes) { - return wasmCrypto.digest(algorithm.name, bytes, algorithm.length) - .buffer; - } else if ((data as Iterable)[Symbol.iterator]) { - const context = new wasmCrypto.DigestContext(algorithm.name); - for (const chunk of data as Iterable) { - 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; - } else { - throw new TypeError( - "data must be a BufferSource or Iterable", - ); - } - }, - - // TODO(@kitsonk): rework when https://github.com/w3c/webcrypto/issues/270 resolved - timingSafeEqual, - }, -}); - -const FNVAlgorithms = ["FNV32", "FNV32A", "FNV64", "FNV64A"]; - -/** Digest algorithms supported by WebCrypto. */ -const webCryptoDigestAlgorithms = [ - "SHA-384", - "SHA-256", - "SHA-512", - // insecure (length-extendable and collidable): - "SHA-1", -] as const; - -export type FNVAlgorithms = "FNV32" | "FNV32A" | "FNV64" | "FNV64A"; -export type DigestAlgorithmName = WasmDigestAlgorithm | FNVAlgorithms; - -export type DigestAlgorithmObject = { - name: DigestAlgorithmName; - length?: number; -}; - -export type DigestAlgorithm = DigestAlgorithmName | DigestAlgorithmObject; - -const normalizeAlgorithm = (algorithm: DigestAlgorithm) => - ((typeof algorithm === "string") ? { name: algorithm.toUpperCase() } : { - ...algorithm, - name: algorithm.name.toUpperCase(), - }) as DigestAlgorithmObject; - -export { stdCrypto as crypto }; +export * from "./crypto.ts"; +export * from "./keystack.ts"; +export * from "./timing_safe_equal.ts"; +export * from "./to_hash_string.ts"; diff --git a/crypto/to_hash_string.ts b/crypto/to_hash_string.ts new file mode 100644 index 000000000000..649c60afa30f --- /dev/null +++ b/crypto/to_hash_string.ts @@ -0,0 +1,33 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +import { encode as hexEncode } from "../encoding/hex.ts"; +import { encode as base64Encode } from "../encoding/base64.ts"; + +const decoder = new TextDecoder(); + +/** + * Converts a hash to a string with a given encoding. + * @example + * ```ts + * import { crypto } from "https://deno.land/std@$STD_VERSION/crypto/crypto.ts"; + * import { toHashString } from "https://deno.land/std@$STD_VERSION/crypto/to_hash_string.ts" + * + * const hash = await crypto.subtle.digest("SHA-384", new TextEncoder().encode("You hear that Mr. Anderson?")); + * + * // Hex encoding by default + * console.log(toHashString(hash)); + * + * // Or with base64 encoding + * console.log(toHashString(hash, "base64")); + * ``` + */ +export function toHashString( + hash: ArrayBuffer, + encoding: "hex" | "base64" = "hex", +): string { + switch (encoding) { + case "hex": + return decoder.decode(hexEncode(new Uint8Array(hash))); + case "base64": + return base64Encode(hash); + } +} diff --git a/crypto/util_test.ts b/crypto/to_hash_string_test.ts similarity index 99% rename from crypto/util_test.ts rename to crypto/to_hash_string_test.ts index c840f3375656..61983e268657 100644 --- a/crypto/util_test.ts +++ b/crypto/to_hash_string_test.ts @@ -1,6 +1,6 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. import { crypto, type DigestAlgorithm } from "./mod.ts"; -import { toHashString } from "./util.ts"; +import { toHashString } from "./to_hash_string.ts"; import { repeat } from "../bytes/repeat.ts"; import { assertEquals } from "../testing/asserts.ts"; diff --git a/crypto/util.ts b/crypto/util.ts index b591d632aa99..914751326e96 100644 --- a/crypto/util.ts +++ b/crypto/util.ts @@ -1,32 +1,23 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. -import * as hex from "../encoding/hex.ts"; -import * as base64 from "../encoding/base64.ts"; -const decoder = new TextDecoder(); - -/** - * Converts a hash to a string with a given encoding. - * @example - * ```ts - * import { crypto, toHashString } from "https://deno.land/std@$STD_VERSION/crypto/mod.ts"; - * - * const hash = await crypto.subtle.digest("SHA-384", new TextEncoder().encode("You hear that Mr. Anderson?")); - * - * // Hex encoding by default - * console.log(toHashString(hash)); - * - * // Or with base64 encoding - * console.log(toHashString(hash, "base64")); - * ``` - */ -export function toHashString( - hash: ArrayBuffer, - encoding: "hex" | "base64" = "hex", -): string { - switch (encoding) { - case "hex": - return decoder.decode(hex.encode(new Uint8Array(hash))); - case "base64": - return base64.encode(hash); - } -} +export { + /** + * @deprecated (will be removed after 0.173.0) Import from `std/io/to_hash_string.ts` instead + * + * Converts a hash to a string with a given encoding. + * @example + * ```ts + * import { crypto } from "https://deno.land/std@$STD_VERSION/crypto/crypto.ts"; + * import { toHashString } from "https://deno.land/std@$STD_VERSION/crypto/to_hash_string.ts" + * + * const hash = await crypto.subtle.digest("SHA-384", new TextEncoder().encode("You hear that Mr. Anderson?")); + * + * // Hex encoding by default + * console.log(toHashString(hash)); + * + * // Or with base64 encoding + * console.log(toHashString(hash, "base64")); + * ``` + */ + toHashString, +} from "./to_hash_string.ts"; diff --git a/http/file_server.ts b/http/file_server.ts index f46202663476..f4810751f7eb 100644 --- a/http/file_server.ts +++ b/http/file_server.ts @@ -13,7 +13,8 @@ import { parse } from "../flags/mod.ts"; import { assert } from "../_util/asserts.ts"; import { red } from "../fmt/colors.ts"; import { compareEtag, createCommonResponse } from "./util.ts"; -import { DigestAlgorithm, toHashString } from "../crypto/mod.ts"; +import { DigestAlgorithm } from "../crypto/crypto.ts"; +import { toHashString } from "../crypto/to_hash_string.ts"; import { createHash } from "../crypto/_util.ts"; import { VERSION } from "../version.ts"; interface EntryInfo { diff --git a/http/file_server_test.ts b/http/file_server_test.ts index d79ba5c506a1..4bec1cde8383 100644 --- a/http/file_server_test.ts +++ b/http/file_server_test.ts @@ -10,7 +10,7 @@ import { TextLineStream } from "../streams/text_line_stream.ts"; import { serveDir, serveFile } from "./file_server.ts"; import { dirname, fromFileUrl, join, resolve } from "../path/mod.ts"; import { isWindows } from "../_util/os.ts"; -import { toHashString } from "../crypto/mod.ts"; +import { toHashString } from "../crypto/to_hash_string.ts"; import { createHash } from "../crypto/_util.ts"; import { VERSION } from "../version.ts";