// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. import { crypto as wasmCrypto, DigestAlgorithm as WasmDigestAlgorithm, digestAlgorithms as wasmDigestAlgorithms, } from "../_wasm_crypto/mod.ts"; type WebCryptoAlgorithmIdentifier = string | { name: string }; // TODO(jeremyBanks): Remove this once the built-in `Crypto` interface is // complete and stable. For now we use this incomplete-but-stable definition. interface WebCrypto { getRandomValues(buffer: T): T; randomUUID?(): string; subtle?: { // see https://www.w3.org/TR/WebCryptoAPI/#subtlecrypto-interface /** * Returns a new `Promise` object that will encrypt `data` using the * specified `AlgorithmIdentifier` with the supplied `CryptoKey`. */ encrypt?( algorithm: WebCryptoAlgorithmIdentifier, key: unknown, data: BufferSource, ): Promise; /** * Returns a new `Promise` object that will decrypt `data` using the * specified `AlgorithmIdentifier` with the supplied `CryptoKey`. */ decrypt?( algorithm: WebCryptoAlgorithmIdentifier, key: unknown, data: BufferSource, ): Promise; /** * Returns a new `Promise` object that will sign `data` using the specified * `AlgorithmIdentifier` with the supplied `CryptoKey`. */ sign?( algorithm: WebCryptoAlgorithmIdentifier, key: unknown, data: BufferSource, ): Promise; /** * Returns a new `Promise` object that will verify `data` using the * specified `AlgorithmIdentifier` with the supplied `CryptoKey`. */ verify?( algorithm: WebCryptoAlgorithmIdentifier, key: unknown, signature: BufferSource, data: BufferSource, ): Promise; /** * Returns a new `Promise` object that will digest `data` using the * specified `AlgorithmIdentifier`. */ digest?( algorithm: WebCryptoAlgorithmIdentifier, data: BufferSource, ): Promise; generateKey?( algorithm: WebCryptoAlgorithmIdentifier, extractable: boolean, keyUsages: string[], ): Promise; deriveKey?( algorithm: WebCryptoAlgorithmIdentifier, baseKey: unknown, derivedKeyType: string, extractable: boolean, keyUsages: string[], ): Promise; deriveBits?( algorithm: WebCryptoAlgorithmIdentifier, baseKey: unknown, length: number, ): Promise; importKey?( format: string, keyData: BufferSource | unknown, algorithm: WebCryptoAlgorithmIdentifier, extractable: boolean, keyUsages: string[], ): Promise; exportKey?(format: string, key: unknown): Promise; wrapKey?( format: string, key: unknown, wrappingKey: unknown, wrappingAlgorithm: WebCryptoAlgorithmIdentifier, ): Promise; unwrapKey?( format: string, wrappedKey: BufferSource, unwrappingKey: unknown, unwrapAlgorithm: WebCryptoAlgorithmIdentifier, unwrappedKeyAlgorithm: WebCryptoAlgorithmIdentifier, extractable: boolean, keyUsages: string[], ): Promise; }; } /** * A copy of the global WebCrypto interface, with methods bound so they're * safe to re-export. */ const webCrypto: 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 as WebCrypto); 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; }; /** * An wrapper for WebCrypto adding support for additional non-standard * algorithms, but delegating to the runtime WebCrypto implementation whenever * possible. */ const stdCrypto = ((x: T) => x)({ ...webCrypto, subtle: { ...webCrypto.subtle, /** * Returns a new `Promise` object that will digest `data` using the specified * `AlgorithmIdentifier`. */ async digest( algorithm: DigestAlgorithm, data: BufferSource | AsyncIterable | Iterable, ): Promise { const { name, length } = normalizeAlgorithm(algorithm); const bytes = bufferSourceBytes(data); // We delegate to WebCrypto whenever possible, if ( // if the SubtleCrypto interface is available, webCrypto.subtle?.digest && // 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)) { 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 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}`); } }, /** * Returns a ArrayBuffer with the result of digesting `data` using the * specified `AlgorithmIdentifier`. */ digestSync( algorithm: DigestAlgorithm, data: BufferSource | Iterable, ): ArrayBuffer { algorithm = normalizeAlgorithm(algorithm); const bytes = bufferSourceBytes(data); 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", ); } }, }, }); /** Digest algorithms supported by WebCrypto. */ const webCryptoDigestAlgorithms = [ "SHA-384", "SHA-256", "SHA-512", // insecure (length-extendable and collidable): "SHA-1", ] as const; type DigestAlgorithmName = WasmDigestAlgorithm; type DigestAlgorithmObject = { name: DigestAlgorithmName; length?: number; }; 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 };