feat(crypto): add std/crypto wrapping and extending runtime WebCrypto (#1025)

This commit is contained in:
Jeremy Banks 2021-07-29 07:37:02 -04:00 committed by GitHub
parent 5606a10728
commit 662139c097
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 5816 additions and 46 deletions

View File

@ -96,7 +96,7 @@ jobs:
run: |-
set -o errexit
shopt -s inherit_errexit
declare modifications="$(git diff --name-only HEAD~ -- ./hash/_wasm/)"
declare modifications="$(git diff --name-only HEAD~ -- ./_wasm_crypto)"
declare modified="$([[ "$modifications" ]] && echo true || echo false)"
echo "::set-output name=modified::$modified"
echo "Hash source modified in this commit? $modified"
@ -113,6 +113,7 @@ jobs:
# This must match the version in hash/_wasm/rust-toolchain:
rust-version: 1.53.0
targets: wasm32-unknown-unknown
components: rustfmt
- name: Set up wasm-bindgen-cli
run: |-
@ -124,7 +125,7 @@ jobs:
id: build
if: success() && steps.source.outputs.modified == 'true'
run: |-
./hash/_wasm/build.ts
./_wasm_crypto/_build.ts
set -o errexit
shopt -s inherit_errexit

3
.gitignore vendored
View File

@ -6,5 +6,4 @@ package.json
package-lock.json
.vscode/settings.json
**/cov/
/hash/_wasm/target
/hash/_wasm/out
/_wasm_crypto/target

View File

@ -0,0 +1,4 @@
# Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
max_width = 80
tab_spaces = 2
edition = "2018"

418
_wasm_crypto/Cargo.lock generated Normal file
View File

@ -0,0 +1,418 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "arrayref"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544"
[[package]]
name = "arrayvec"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4dc07131ffa69b8072d35f5007352af944213cde02545e2103680baed38fcd"
[[package]]
name = "blake2"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a5720225ef5daecf08657f23791354e1685a8c91a4c60c7f3d3b2892f978f4"
dependencies = [
"crypto-mac 0.8.0",
"digest",
"opaque-debug",
]
[[package]]
name = "blake3"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcd555c66291d5f836dbb6883b48660ece810fe25a31f3bdfb911945dff2691f"
dependencies = [
"arrayref",
"arrayvec",
"cc",
"cfg-if",
"constant_time_eq",
"crypto-mac 0.11.1",
"digest",
]
[[package]]
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"block-padding",
"generic-array",
]
[[package]]
name = "block-padding"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
[[package]]
name = "bumpalo"
version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631"
[[package]]
name = "cc"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "constant_time_eq"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "convert_case"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]]
name = "cpufeatures"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef"
dependencies = [
"libc",
]
[[package]]
name = "crypto-mac"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab"
dependencies = [
"generic-array",
"subtle",
]
[[package]]
name = "crypto-mac"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
dependencies = [
"generic-array",
"subtle",
]
[[package]]
name = "deno_std_wasm_crypto"
version = "0.0.0"
dependencies = [
"blake2",
"blake3",
"derive_more",
"digest",
"generic-array",
"js-sys",
"md-5",
"ripemd160",
"sha-1",
"sha2",
"sha3",
"typenum",
"wasm-bindgen",
]
[[package]]
name = "derive_more"
version = "0.99.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40eebddd2156ce1bb37b20bbe5151340a31828b1f2d22ba4141f3531710e38df"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
"rustc_version",
"syn",
]
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array",
]
[[package]]
name = "generic-array"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "js-sys"
version = "0.3.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "keccak"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "md-5"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15"
dependencies = [
"block-buffer",
"digest",
"opaque-debug",
]
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "pest"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
dependencies = [
"ucd-trie",
]
[[package]]
name = "proc-macro2"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "ripemd160"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251"
dependencies = [
"block-buffer",
"digest",
"opaque-debug",
]
[[package]]
name = "rustc_version"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
dependencies = [
"semver",
]
[[package]]
name = "semver"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver-parser"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
dependencies = [
"pest",
]
[[package]]
name = "sha-1"
version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a0c8611594e2ab4ebbf06ec7cbbf0a99450b8570e96cbf5188b5d5f6ef18d81"
dependencies = [
"block-buffer",
"cfg-if",
"cpufeatures",
"digest",
"opaque-debug",
]
[[package]]
name = "sha2"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12"
dependencies = [
"block-buffer",
"cfg-if",
"cpufeatures",
"digest",
"opaque-debug",
]
[[package]]
name = "sha3"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809"
dependencies = [
"block-buffer",
"digest",
"keccak",
"opaque-debug",
]
[[package]]
name = "subtle"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "syn"
version = "1.0.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "typenum"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
[[package]]
name = "ucd-trie"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "version_check"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "wasm-bindgen"
version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900"
dependencies = [
"bumpalo",
"lazy_static",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f"

35
_wasm_crypto/Cargo.toml Normal file
View File

@ -0,0 +1,35 @@
# Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
[package]
edition = "2018"
name = "deno_std_wasm_crypto"
repository = "https://github.com/denoland/deno_std"
authors = ["the Deno authors"]
license = "MIT"
publish = false
version = "0.0.0"
[lib]
crate-type = ["cdylib"]
[profile.release]
lto = true
opt-level = 3
[dependencies]
blake2 = "0.9.1"
derive_more = "0.99.16"
digest = "0.9.0"
generic-array = "0.14.4"
js-sys = "0.3.51"
md-5 = "0.9.1"
ripemd160 = "0.9.1"
sha-1 = "0.9.7"
sha2 = "0.9.5"
sha3 = "0.9.1"
typenum = "1.13.0"
wasm-bindgen = "0.2.74"
[dependencies.blake3]
version = "1.0.0"
features = ["std", "traits-preview"]

25
_wasm_crypto/README.md Normal file
View File

@ -0,0 +1,25 @@
`std/_crypto_wasm` is only for internal use, such as by `std/crypto` and
`std/node/crypto`. Its interface may not be stable between releases and it
should not be imported directly.
## How to Build
### Prerequisite
Requires Rust's WASM target and the wasm-bindgen CLI.
```sh
cargo build --target wasm32-unknown-unknown
rustup target add wasm32-unknown-unknown
# This must match the version in hash/_wasm/Cargo.lock:
cargo install -f wasm-bindgen-cli --version 0.2.74
```
### Build
```sh
deno run --allow-all ./_build.ts
```
This will regenerate `./crypto.js` and `./crypto.wasm.js` from the Rust source.

150
_wasm_crypto/_build.ts Executable file
View File

@ -0,0 +1,150 @@
#!/usr/bin/env -S deno run --allow-run --allow-read --allow-write --allow-env
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
import * as base64 from "../encoding/base64.ts";
const home = Deno.env.get("HOME");
const root = new URL(".", import.meta.url).pathname;
// Run in the same directory as this script is located.
if (new URL(import.meta.url).protocol === "file:") {
Deno.chdir(root);
} else {
console.error("build.ts can only be run locally (from a file: URL).");
Deno.exit(1);
}
// Format the Rust code.
if (
!((await Deno.run({
cmd: [
"cargo",
"fmt",
],
}).status()).success)
) {
console.error(`Failed to format the Rust code.`);
Deno.exit(1);
}
// Compile the Rust code to WASM.
if (
!((await Deno.run({
cmd: [
"cargo",
"build",
"--release",
"--target",
"wasm32-unknown-unknown",
],
env: {
// eliminate some potential sources of non-determinism
SOURCE_DATE_EPOCH: "1600000000",
TZ: "UTC",
LC_ALL: "C",
RUSTFLAGS: `--remap-path-prefix=${root}=. --remap-path-prefix=${home}=~`,
},
}).status()).success)
) {
console.error(`Failed to compile the Rust code to WASM.`);
Deno.exit(1);
}
const copyrightLine = `// Copyright 2018-${
new Date().getFullYear()
} the Deno authors. All rights reserved. MIT license.`;
// Generate JavaScript bindings from WASM.
if (
!((await Deno.run({
cmd: [
"wasm-bindgen",
"./target/wasm32-unknown-unknown/release/deno_std_wasm_crypto.wasm",
"--target",
"deno",
"--weak-refs",
"--out-dir",
"./target/wasm32-bindgen-deno-js",
],
}).status()).success)
) {
console.error(`Failed to generate JavaScript bindings from WASM.`);
Deno.exit(1);
}
// Encode WASM binary as a TypeScript module.
const generatedWasm = await Deno.readFile(
"./target/wasm32-bindgen-deno-js/deno_std_wasm_crypto_bg.wasm",
);
// Format WASM binary size with _ thousands separators for human readablity,
// so that any changes in size will be clear in diffs.
const formattedWasmSize = generatedWasm.length.toString().padStart(
Math.ceil(generatedWasm.length.toString().length / 3) * 3,
).replace(/...\B/g, "$&_").trim();
// Generate a hash of the WASM in the format required by subresource integrity.
const wasmIntegrity = `sha256-${
base64.encode(await crypto.subtle!.digest("SHA-256", generatedWasm))
}`;
const wasmJs = `${copyrightLine}
// This file is automatically @generated by _build.ts
// It is not intended for manual editing.
import * as base64 from "../encoding/base64.ts";
export const size = ${formattedWasmSize};
export const name = "crypto.wasm";
export const type = "application/wasm";
export const hash = ${JSON.stringify(wasmIntegrity)};
export const data = base64.decode("\\\n${
base64.encode(generatedWasm).replace(/.{78}/g, "$&\\\n")
}\\\n");
export default data;
`;
// Modify the generated WASM bindings, replacing the runtime fetching of the
// WASM binary file with a static TypeScript import of the copy we encoded
// above. This eliminates the need for net or read permissions.
const generatedScript = await Deno.readTextFile(
"./target/wasm32-bindgen-deno-js/deno_std_wasm_crypto.js",
);
const bindingsJs = `${copyrightLine}
// This file is automatically @generated by _build.ts
// It is not intended for manual editing.
// deno-lint-ignore-file
import wasmBytes from "./crypto.wasm.js";
${
generatedScript.replace(
/^const file =.*?;\nconst wasmFile =.*?;\nconst wasmModule =.*?;\n/sm,
`const wasmModule = new WebAssembly.Module(wasmBytes);`,
)
}
// for testing/debugging
export const _wasm = wasm;
export const _wasmModule = wasmModule;
export const _wasmInstance = wasmInstance;
export const _wasmBytes = wasmBytes;
`;
await Deno.writeTextFile("./crypto.wasm.js", wasmJs);
await Deno.writeTextFile("./crypto.js", bindingsJs);
// Format the generated files.
if (
!(await Deno.run({
cmd: [
"deno",
"fmt",
"./crypto.wasm.js",
"./crypto.js",
],
}).status()).success
) {
console.error(
`Failed to format generated code.`,
);
Deno.exit(1);
}

400
_wasm_crypto/crypto.js Normal file
View File

@ -0,0 +1,400 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
// This file is automatically @generated by _build.ts
// It is not intended for manual editing.
// deno-lint-ignore-file
import wasmBytes from "./crypto.wasm.js";
const heap = new Array(32).fill(undefined);
heap.push(undefined, null, true, false);
function getObject(idx) {
return heap[idx];
}
let heap_next = heap.length;
function dropObject(idx) {
if (idx < 36) return;
heap[idx] = heap_next;
heap_next = idx;
}
function takeObject(idx) {
const ret = getObject(idx);
dropObject(idx);
return ret;
}
function addHeapObject(obj) {
if (heap_next === heap.length) heap.push(heap.length + 1);
const idx = heap_next;
heap_next = heap[idx];
heap[idx] = obj;
return idx;
}
let cachedTextDecoder = new TextDecoder("utf-8", {
ignoreBOM: true,
fatal: true,
});
cachedTextDecoder.decode();
let cachegetUint8Memory0 = null;
function getUint8Memory0() {
if (
cachegetUint8Memory0 === null ||
cachegetUint8Memory0.buffer !== wasm.memory.buffer
) {
cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
}
return cachegetUint8Memory0;
}
function getStringFromWasm0(ptr, len) {
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
}
let WASM_VECTOR_LEN = 0;
let cachedTextEncoder = new TextEncoder("utf-8");
const encodeString = function (arg, view) {
return cachedTextEncoder.encodeInto(arg, view);
};
function passStringToWasm0(arg, malloc, realloc) {
if (realloc === undefined) {
const buf = cachedTextEncoder.encode(arg);
const ptr = malloc(buf.length);
getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
WASM_VECTOR_LEN = buf.length;
return ptr;
}
let len = arg.length;
let ptr = malloc(len);
const mem = getUint8Memory0();
let offset = 0;
for (; offset < len; offset++) {
const code = arg.charCodeAt(offset);
if (code > 0x7F) break;
mem[ptr + offset] = code;
}
if (offset !== len) {
if (offset !== 0) {
arg = arg.slice(offset);
}
ptr = realloc(ptr, len, len = offset + arg.length * 3);
const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
const ret = encodeString(arg, view);
offset += ret.written;
}
WASM_VECTOR_LEN = offset;
return ptr;
}
function isLikeNone(x) {
return x === undefined || x === null;
}
let cachegetInt32Memory0 = null;
function getInt32Memory0() {
if (
cachegetInt32Memory0 === null ||
cachegetInt32Memory0.buffer !== wasm.memory.buffer
) {
cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer);
}
return cachegetInt32Memory0;
}
function getArrayU8FromWasm0(ptr, len) {
return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);
}
/**
* Returns the digest of the given `data` using the given hash `algorithm`.
*
* `length` will usually be left `undefined` to use the default length for
* the algorithm. For algorithms with variable-length output, it can be used
* to specify a non-negative integer number of bytes.
*
* An error will be thrown if `algorithm` is not a supported hash algorithm or
* `length` is not a supported length for the algorithm.
* @param {string} algorithm
* @param {Uint8Array} data
* @param {number | undefined} length
* @returns {Uint8Array}
*/
export function digest(algorithm, data, length) {
try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
var ptr0 = passStringToWasm0(
algorithm,
wasm.__wbindgen_malloc,
wasm.__wbindgen_realloc,
);
var len0 = WASM_VECTOR_LEN;
wasm.digest(
retptr,
ptr0,
len0,
addHeapObject(data),
!isLikeNone(length),
isLikeNone(length) ? 0 : length,
);
var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1];
var v1 = getArrayU8FromWasm0(r0, r1).slice();
wasm.__wbindgen_free(r0, r1 * 1);
return v1;
} finally {
wasm.__wbindgen_add_to_stack_pointer(16);
}
}
const DigestContextFinalization = new FinalizationRegistry((ptr) =>
wasm.__wbg_digestcontext_free(ptr)
);
/**
* A context for incrementally computing a digest using a given hash algorithm.
*/
export class DigestContext {
static __wrap(ptr) {
const obj = Object.create(DigestContext.prototype);
obj.ptr = ptr;
DigestContextFinalization.register(obj, obj.ptr, obj);
return obj;
}
__destroy_into_raw() {
const ptr = this.ptr;
this.ptr = 0;
DigestContextFinalization.unregister(this);
return ptr;
}
free() {
const ptr = this.__destroy_into_raw();
wasm.__wbg_digestcontext_free(ptr);
}
/**
* Creates a new context incrementally computing a digest using the given
* hash algorithm.
*
* An error will be thrown if `algorithm` is not a supported hash algorithm.
* @param {string} algorithm
*/
constructor(algorithm) {
var ptr0 = passStringToWasm0(
algorithm,
wasm.__wbindgen_malloc,
wasm.__wbindgen_realloc,
);
var len0 = WASM_VECTOR_LEN;
var ret = wasm.digestcontext_new(ptr0, len0);
return DigestContext.__wrap(ret);
}
/**
* Update the digest's internal state with the additional input `data`.
*
* If the `data` array view is large, it will be split into subarrays (via
* JavaScript bindings) which will be processed sequentially in order to
* limit the amount of memory that needs to be allocated in the WASM heap.
* @param {Uint8Array} data
*/
update(data) {
wasm.digestcontext_update(this.ptr, addHeapObject(data));
}
/**
* Returns the digest of the input data so far. This may be called repeatedly
* without side effects.
*
* `length` will usually be left `undefined` to use the default length for
* the algorithm. For algorithms with variable-length output, it can be used
* to specify a non-negative integer number of bytes.
*
* An error will be thrown if `algorithm` is not a supported hash algorithm or
* `length` is not a supported length for the algorithm.
* @param {number | undefined} length
* @returns {Uint8Array}
*/
digest(length) {
try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
wasm.digestcontext_digest(
retptr,
this.ptr,
!isLikeNone(length),
isLikeNone(length) ? 0 : length,
);
var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1];
var v0 = getArrayU8FromWasm0(r0, r1).slice();
wasm.__wbindgen_free(r0, r1 * 1);
return v0;
} finally {
wasm.__wbindgen_add_to_stack_pointer(16);
}
}
/**
* Returns the digest of the input data so far, and resets this context to
* its initial state, as though it has not yet been provided with any input
* data. (It will still use the same algorithm.)
*
* `length` will usually be left `undefined` to use the default length for
* the algorithm. For algorithms with variable-length output, it can be used
* to specify a non-negative integer number of bytes.
*
* An error will be thrown if `algorithm` is not a supported hash algorithm or
* `length` is not a supported length for the algorithm.
* @param {number | undefined} length
* @returns {Uint8Array}
*/
digestAndReset(length) {
try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
wasm.digestcontext_digestAndReset(
retptr,
this.ptr,
!isLikeNone(length),
isLikeNone(length) ? 0 : length,
);
var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1];
var v0 = getArrayU8FromWasm0(r0, r1).slice();
wasm.__wbindgen_free(r0, r1 * 1);
return v0;
} finally {
wasm.__wbindgen_add_to_stack_pointer(16);
}
}
/**
* Returns the digest of the input data so far, and then drops the context
* from memory on the WASM side. This context must no longer be used, and any
* further method calls will result in null pointer errors being thrown.
* https://github.com/rustwasm/wasm-bindgen/blob/bf39cfd8/crates/backend/src/codegen.rs#L186
*
* `length` will usually be left `undefined` to use the default length for
* the algorithm. For algorithms with variable-length output, it can be used
* to specify a non-negative integer number of bytes.
*
* An error will be thrown if `algorithm` is not a supported hash algorithm or
* `length` is not a supported length for the algorithm.
* @param {number | undefined} length
* @returns {Uint8Array}
*/
digestAndDrop(length) {
try {
const ptr = this.__destroy_into_raw();
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
wasm.digestcontext_digestAndDrop(
retptr,
ptr,
!isLikeNone(length),
isLikeNone(length) ? 0 : length,
);
var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1];
var v0 = getArrayU8FromWasm0(r0, r1).slice();
wasm.__wbindgen_free(r0, r1 * 1);
return v0;
} finally {
wasm.__wbindgen_add_to_stack_pointer(16);
}
}
/**
* Resets this context to its initial state, as though it has not yet been
* provided with any input data. (It will still use the same algorithm.)
*/
reset() {
wasm.digestcontext_reset(this.ptr);
}
/**
* Returns a new `DigestContext` that is a copy of this one, i.e., using the
* same algorithm and with a copy of the same internal state.
*
* This may be a more efficient option for computing multiple digests that
* start with a common prefix.
* @returns {DigestContext}
*/
clone() {
var ret = wasm.digestcontext_clone(this.ptr);
return DigestContext.__wrap(ret);
}
}
const imports = {
__wbindgen_placeholder__: {
__wbg_new_f85dbdfb9cdbe2ec: function (arg0, arg1) {
var ret = new TypeError(getStringFromWasm0(arg0, arg1));
return addHeapObject(ret);
},
__wbindgen_object_drop_ref: function (arg0) {
takeObject(arg0);
},
__wbg_byteLength_e0515bc94cfc5dee: function (arg0) {
var ret = getObject(arg0).byteLength;
return ret;
},
__wbg_byteOffset_77eec84716a2e737: function (arg0) {
var ret = getObject(arg0).byteOffset;
return ret;
},
__wbg_buffer_1c5918a4ab656ff7: function (arg0) {
var ret = getObject(arg0).buffer;
return addHeapObject(ret);
},
__wbg_newwithbyteoffsetandlength_e57ad1f2ce812c03: function (
arg0,
arg1,
arg2,
) {
var ret = new Uint8Array(getObject(arg0), arg1 >>> 0, arg2 >>> 0);
return addHeapObject(ret);
},
__wbg_length_2d56cb37075fcfb1: function (arg0) {
var ret = getObject(arg0).length;
return ret;
},
__wbindgen_memory: function () {
var ret = wasm.memory;
return addHeapObject(ret);
},
__wbg_buffer_9e184d6f785de5ed: function (arg0) {
var ret = getObject(arg0).buffer;
return addHeapObject(ret);
},
__wbg_new_e8101319e4cf95fc: function (arg0) {
var ret = new Uint8Array(getObject(arg0));
return addHeapObject(ret);
},
__wbg_set_e8ae7b27314e8b98: function (arg0, arg1, arg2) {
getObject(arg0).set(getObject(arg1), arg2 >>> 0);
},
__wbindgen_throw: function (arg0, arg1) {
throw new Error(getStringFromWasm0(arg0, arg1));
},
__wbindgen_rethrow: function (arg0) {
throw takeObject(arg0);
},
},
};
const wasmModule = new WebAssembly.Module(wasmBytes);
const wasmInstance = new WebAssembly.Instance(wasmModule, imports);
const wasm = wasmInstance.exports;
// for testing/debugging
export const _wasm = wasm;
export const _wasmModule = wasmModule;
export const _wasmInstance = wasmInstance;
export const _wasmBytes = wasmBytes;

2492
_wasm_crypto/crypto.wasm.js Normal file

File diff suppressed because it is too large Load Diff

40
_wasm_crypto/mod.ts Normal file
View File

@ -0,0 +1,40 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
export * as crypto from "./crypto.js";
/**
* All cryptographic hash/digest algorithms supported by std/_wasm_crypto.
*
* For algorithms that are supported by WebCrypto, the name here must match the
* one used by WebCrypto. Otherwise we should prefer the formatting used in the
* official specification. All names are uppercase to faciliate case-insensitive
* comparisons required by the WebCrypto spec.
*/
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",
// insecure (length-extendable):
"RIPEMD-160",
"SHA-224",
"SHA-256",
"SHA-512",
// insecure (collidable and length-extendable):
"MD5",
"SHA-1",
] as const;
/** An algorithm name supported by std/_wasm_crypto. */
export type DigestAlgorithm = typeof digestAlgorithms[number];

View File

@ -0,0 +1 @@
1.53.0

325
_wasm_crypto/src/digest.rs Normal file
View File

@ -0,0 +1,325 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use digest::{
BlockInput, Digest, DynDigest, ExtendableOutput, Reset, Update,
VariableOutput,
};
/// Enum wrapper over all supported digest implementations.
#[derive(Clone)]
pub enum Context {
Blake2b(Box<blake2::Blake2b>),
Blake2b256(Box<blake2::VarBlake2b>),
Blake2b384(Box<blake2::VarBlake2b>),
Blake2s(Box<blake2::Blake2s>),
Blake3(Box<blake3::Hasher>),
Keccak224(Box<sha3::Keccak224>),
Keccak256(Box<sha3::Keccak256>),
Keccak384(Box<sha3::Keccak384>),
Keccak512(Box<sha3::Keccak512>),
Md5(Box<md5::Md5>),
Ripemd160(Box<ripemd160::Ripemd160>),
Sha1(Box<sha1::Sha1>),
Sha3_224(Box<sha3::Sha3_224>),
Sha3_256(Box<sha3::Sha3_256>),
Sha3_384(Box<sha3::Sha3_384>),
Sha3_512(Box<sha3::Sha3_512>),
Sha224(Box<sha2::Sha224>),
Sha256(Box<sha2::Sha256>),
Sha384(Box<sha2::Sha384>),
Sha512(Box<sha2::Sha512>),
Shake128(Box<sha3::Shake128>),
Shake256(Box<sha3::Shake256>),
}
use Context::*;
impl Context {
pub fn new(algorithm_name: &str) -> Result<Context, &'static str> {
Ok(match algorithm_name {
"BLAKE2B" => Blake2b(Default::default()),
"BLAKE2B-256" => {
Blake2b256(Box::new(blake2::VarBlake2b::new(256 / 8).unwrap()))
}
"BLAKE2B-384" => {
Blake2b384(Box::new(blake2::VarBlake2b::new(384 / 8).unwrap()))
}
"BLAKE2S" => Blake2s(Default::default()),
"BLAKE3" => Blake3(Default::default()),
"KECCAK-224" => Keccak224(Default::default()),
"KECCAK-256" => Keccak256(Default::default()),
"KECCAK-384" => Keccak384(Default::default()),
"KECCAK-512" => Keccak512(Default::default()),
"MD5" => Md5(Default::default()),
"RIPEMD-160" => Ripemd160(Default::default()),
"SHA-1" => Sha1(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()),
"SHA-224" => Sha224(Default::default()),
"SHA-256" => Sha256(Default::default()),
"SHA-384" => Sha384(Default::default()),
"SHA-512" => Sha512(Default::default()),
"SHAKE128" => Shake128(Default::default()),
"SHAKE256" => Shake256(Default::default()),
_ => return Err("unsupported algorithm"),
})
}
/// The input block length for the algorithm, in bytes.
pub fn input_block_length(&self) -> usize {
// For algorithm types that implement BlockInput and have a statically
// available BlockSize as part of their type definition, we use that value.
fn static_block_length<T: BlockInput>(_: &T) -> usize {
<T::BlockSize as typenum::Unsigned>::to_usize()
}
match self {
Blake2b(context) => static_block_length(&**context),
Blake2b256(context) => static_block_length(&**context),
Blake2b384(context) => static_block_length(&**context),
Blake2s(context) => static_block_length(&**context),
Blake3(context) => static_block_length(&**context),
Keccak224(context) => static_block_length(&**context),
Keccak256(context) => static_block_length(&**context),
Keccak384(context) => static_block_length(&**context),
Keccak512(context) => static_block_length(&**context),
Md5(context) => static_block_length(&**context),
Ripemd160(context) => static_block_length(&**context),
Sha1(context) => static_block_length(&**context),
Sha3_224(context) => static_block_length(&**context),
Sha3_256(context) => static_block_length(&**context),
Sha3_384(context) => static_block_length(&**context),
Sha3_512(context) => static_block_length(&**context),
Sha224(context) => static_block_length(&**context),
Sha256(context) => static_block_length(&**context),
Sha384(context) => static_block_length(&**context),
Sha512(context) => static_block_length(&**context),
// https://doi.org/10.6028/NIST.FIPS.202 specifies that:
// - In general, the input block size (in bits) of a sponge function is
// its rate.
// - SPONGE[f, pad, r] = The sponge function in which the underlying
// function is f, the padding rule is pad, and the rate is r.
// - KECCAK[c] = SPONGE[KECCAK-p[1600, 24], pad10*1, 1600c]
// - SHAKE128(M, d) = KECCAK[256] (M || 1111, d)
Shake128(_) => 168, // implying a rate of (1600 - 256) bits = 168 bytes
// - SHAKE256(M, d) = KECCAK[512] (M || 1111, d).
Shake256(_) => 136, // implying a rate of (1600 - 512) bits = 136 bytes
}
}
/// The output digest length for the algorithm, in bytes.
///
/// If the algorithm is variable-length, this returns its default length.
pub fn output_length(&self) -> usize {
match self {
Blake2b(context) => context.output_size(),
Blake2b256(context) => context.output_size(),
Blake2b384(context) => context.output_size(),
Blake2s(context) => context.output_size(),
Blake3(context) => context.output_size(),
Keccak224(context) => context.output_size(),
Keccak256(context) => context.output_size(),
Keccak384(context) => context.output_size(),
Keccak512(context) => context.output_size(),
Md5(context) => context.output_size(),
Ripemd160(context) => context.output_size(),
Sha1(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(),
Sha224(context) => context.output_size(),
Sha256(context) => context.output_size(),
Sha384(context) => context.output_size(),
Sha512(context) => context.output_size(),
// https://doi.org/10.6028/NIST.FIPS.202's Table 4 indicates that in order
// to reach the target security strength for these algorithms, the output
// size (in bits) needs to be (at least) two times larger than that
// security strength (in bits).
Shake128(_) => 32, // implying a length of (2 * 128) bits = 32 bytes
Shake256(_) => 64, // implying a length of (2 * 256) bits = 64 bytes
}
}
/// Whether the algorithm has an extendable variable-length digest output
/// (whether it is an "XOF").
pub const fn extendable(&self) -> bool {
matches!(self, Blake3(_) | Shake128(_) | Shake256(_))
}
/// The name of the algorithm used by this context.
///
/// Names are all uppercase (for ease of case-insensitive comparisons) and
/// will match the name formatting used in WebCrypto if the algorithm is
/// supported by WebCrypto, and otherwise match the formatting used in the
/// official specification for the algorithm.
pub const fn algorithm_name(&self) -> &'static str {
match self {
Blake2b(_) => "BLAKE2B",
Blake2b256(_) => "BLAKE2B-256",
Blake2b384(_) => "BLAKE2B-384",
Blake2s(_) => "BLAKE2S",
Blake3(_) => "BLAKE3",
Keccak224(_) => "KECCAK-224",
Keccak256(_) => "KECCAK-256",
Keccak384(_) => "KECCAK-384",
Keccak512(_) => "KECCAK-512",
Md5(_) => "MD5",
Ripemd160(_) => "RIPEMD-160",
Sha1(_) => "SHA-1",
Sha3_224(_) => "SHA3-224",
Sha3_256(_) => "SHA3-256",
Sha3_384(_) => "SHA3-384",
Sha3_512(_) => "SHA3-512",
Sha224(_) => "SHA-224",
Sha256(_) => "SHA-256",
Sha384(_) => "SHA-384",
Sha512(_) => "SHA-512",
Shake128(_) => "SHAKE128",
Shake256(_) => "SHAKE256",
}
}
pub fn reset(&mut self) {
match self {
Blake2b(context) => Reset::reset(&mut **context),
Blake2b256(context) => Reset::reset(&mut **context),
Blake2b384(context) => Reset::reset(&mut **context),
Blake2s(context) => Reset::reset(&mut **context),
Blake3(context) => Reset::reset(&mut **context),
Keccak224(context) => Reset::reset(&mut **context),
Keccak256(context) => Reset::reset(&mut **context),
Keccak384(context) => Reset::reset(&mut **context),
Keccak512(context) => Reset::reset(&mut **context),
Md5(context) => Reset::reset(&mut **context),
Ripemd160(context) => Reset::reset(&mut **context),
Sha1(context) => Reset::reset(&mut **context),
Sha3_224(context) => Reset::reset(&mut **context),
Sha3_256(context) => Reset::reset(&mut **context),
Sha3_384(context) => Reset::reset(&mut **context),
Sha3_512(context) => Reset::reset(&mut **context),
Sha224(context) => Reset::reset(&mut **context),
Sha256(context) => Reset::reset(&mut **context),
Sha384(context) => Reset::reset(&mut **context),
Sha512(context) => Reset::reset(&mut **context),
Shake128(context) => Reset::reset(&mut **context),
Shake256(context) => Reset::reset(&mut **context),
};
}
pub fn update(&mut self, data: &[u8]) {
match self {
Blake2b(context) => Digest::update(&mut **context, data),
Blake2b256(context) => (&mut **context).update(data),
Blake2b384(context) => (&mut **context).update(data),
Blake2s(context) => Digest::update(&mut **context, data),
Blake3(context) => Digest::update(&mut **context, data),
Keccak224(context) => Digest::update(&mut **context, data),
Keccak256(context) => Digest::update(&mut **context, data),
Keccak384(context) => Digest::update(&mut **context, data),
Keccak512(context) => Digest::update(&mut **context, data),
Md5(context) => Digest::update(&mut **context, data),
Ripemd160(context) => Digest::update(&mut **context, data),
Sha1(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),
Sha224(context) => Digest::update(&mut **context, data),
Sha256(context) => Digest::update(&mut **context, data),
Sha384(context) => Digest::update(&mut **context, data),
Sha512(context) => Digest::update(&mut **context, data),
Shake128(context) => (&mut **context).update(data),
Shake256(context) => (&mut **context).update(data),
};
}
pub fn digest_and_drop(
self,
length: Option<usize>,
) -> Result<Box<[u8]>, &'static str> {
if let Some(length) = length {
if !self.extendable() && length != self.output_length() {
return Err(
"non-default length specified for non-extendable algorithm",
);
}
}
let length = length.unwrap_or_else(|| self.output_length());
Ok(match self {
Blake2b(context) => context.finalize(),
Blake2b256(context) => context.finalize_boxed(),
Blake2b384(context) => context.finalize_boxed(),
Blake2s(context) => context.finalize(),
Blake3(context) => context.finalize_boxed(length),
Keccak224(context) => context.finalize(),
Keccak256(context) => context.finalize(),
Keccak384(context) => context.finalize(),
Keccak512(context) => context.finalize(),
Md5(context) => context.finalize(),
Ripemd160(context) => context.finalize(),
Sha1(context) => context.finalize(),
Sha3_224(context) => context.finalize(),
Sha3_256(context) => context.finalize(),
Sha3_384(context) => context.finalize(),
Sha3_512(context) => context.finalize(),
Sha224(context) => context.finalize(),
Sha256(context) => context.finalize(),
Sha384(context) => context.finalize(),
Sha512(context) => context.finalize(),
Shake128(context) => context.finalize_boxed(length),
Shake256(context) => context.finalize_boxed(length),
})
}
pub fn digest_and_reset(
&mut self,
length: Option<usize>,
) -> Result<Box<[u8]>, &'static str> {
if let Some(length) = length {
if !self.extendable() && length != self.output_length() {
return Err(
"non-default length specified for non-extendable algorithm",
);
}
}
let length = length.unwrap_or_else(|| self.output_length());
Ok(match self {
Blake2b(context) => DynDigest::finalize_reset(context.as_mut()),
Blake2b256(context) => context.finalize_boxed_reset(),
Blake2b384(context) => context.finalize_boxed_reset(),
Blake2s(context) => DynDigest::finalize_reset(context.as_mut()),
Blake3(context) => context.finalize_boxed_reset(length),
Keccak224(context) => DynDigest::finalize_reset(context.as_mut()),
Keccak256(context) => DynDigest::finalize_reset(context.as_mut()),
Keccak384(context) => DynDigest::finalize_reset(context.as_mut()),
Keccak512(context) => DynDigest::finalize_reset(context.as_mut()),
Md5(context) => DynDigest::finalize_reset(context.as_mut()),
Ripemd160(context) => DynDigest::finalize_reset(context.as_mut()),
Sha1(context) => DynDigest::finalize_reset(context.as_mut()),
Sha3_224(context) => DynDigest::finalize_reset(context.as_mut()),
Sha3_256(context) => DynDigest::finalize_reset(context.as_mut()),
Sha3_384(context) => DynDigest::finalize_reset(context.as_mut()),
Sha3_512(context) => DynDigest::finalize_reset(context.as_mut()),
Sha224(context) => DynDigest::finalize_reset(context.as_mut()),
Sha256(context) => DynDigest::finalize_reset(context.as_mut()),
Sha384(context) => DynDigest::finalize_reset(context.as_mut()),
Sha512(context) => DynDigest::finalize_reset(context.as_mut()),
Shake128(context) => context.finalize_boxed_reset(length),
Shake256(context) => context.finalize_boxed_reset(length),
})
}
pub fn digest(
&self,
length: Option<usize>,
) -> Result<Box<[u8]>, &'static str> {
self.clone().digest_and_drop(length)
}
}

164
_wasm_crypto/src/lib.rs Normal file
View File

@ -0,0 +1,164 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use derive_more::{From, Into};
use js_sys::Uint8Array;
use wasm_bindgen::prelude::*;
mod digest;
/// Returns the digest of the given `data` using the given hash `algorithm`.
///
/// `length` will usually be left `undefined` to use the default length for
/// the algorithm. For algorithms with variable-length output, it can be used
/// to specify a non-negative integer number of bytes.
///
/// An error will be thrown if `algorithm` is not a supported hash algorithm or
/// `length` is not a supported length for the algorithm.
#[wasm_bindgen]
pub fn digest(
algorithm: String,
data: js_sys::Uint8Array,
length: Option<usize>,
) -> Result<Box<[u8]>, JsValue> {
let mut context = DigestContext::new(algorithm)?;
context.update(data)?;
context.digest_and_drop(length)
}
/// A context for incrementally computing a digest using a given hash algorithm.
#[wasm_bindgen]
#[derive(Clone, Into, From)]
pub struct DigestContext(digest::Context);
#[wasm_bindgen]
impl DigestContext {
/// Creates a new context incrementally computing a digest using the given
/// hash algorithm.
///
/// An error will be thrown if `algorithm` is not a supported hash algorithm.
#[wasm_bindgen(constructor)]
pub fn new(algorithm: String) -> Result<DigestContext, JsValue> {
Ok(
digest::Context::new(&algorithm)
.map_err(|message| JsValue::from(js_sys::TypeError::new(message)))?
.into(),
)
}
/// Update the digest's internal state with the additional input `data`.
///
/// If the `data` array view is large, it will be split into subarrays (via
/// JavaScript bindings) which will be processed sequentially in order to
/// limit the amount of memory that needs to be allocated in the WASM heap.
#[wasm_bindgen]
pub fn update(&mut self, data: js_sys::Uint8Array) -> Result<(), JsValue> {
// Every method call on `data` has to go through the JavaScript bindings, so
// try to minimize them. Splitting on the JavaScript side could be more
// efficient, but this is called from multiple places in JavaScript so it
// seems simpler to keep it here for now.
let length = data.byte_length();
let base_offset = data.byte_offset();
let buffer = data.buffer();
let chunk_size: u32 = 65_536;
if length <= chunk_size {
let chunk = data.to_vec();
self.0.update(&chunk);
} else {
let mut offset = 0;
while offset < length {
let chunk = Uint8Array::new_with_byte_offset_and_length(
&buffer,
base_offset + offset,
chunk_size.min(length - offset),
)
.to_vec();
self.0.update(&chunk);
offset += chunk_size;
}
}
Ok(())
}
/// Returns the digest of the input data so far. This may be called repeatedly
/// without side effects.
///
/// `length` will usually be left `undefined` to use the default length for
/// the algorithm. For algorithms with variable-length output, it can be used
/// to specify a non-negative integer number of bytes.
///
/// An error will be thrown if `algorithm` is not a supported hash algorithm or
/// `length` is not a supported length for the algorithm.
#[wasm_bindgen]
pub fn digest(&self, length: Option<usize>) -> Result<Box<[u8]>, JsValue> {
self
.0
.digest(length)
.map_err(|message| JsValue::from(js_sys::TypeError::new(message)))
}
/// Returns the digest of the input data so far, and resets this context to
/// its initial state, as though it has not yet been provided with any input
/// data. (It will still use the same algorithm.)
///
/// `length` will usually be left `undefined` to use the default length for
/// the algorithm. For algorithms with variable-length output, it can be used
/// to specify a non-negative integer number of bytes.
///
/// An error will be thrown if `algorithm` is not a supported hash algorithm or
/// `length` is not a supported length for the algorithm.
#[wasm_bindgen(js_name=digestAndReset)]
pub fn digest_and_reset(
&mut self,
length: Option<usize>,
) -> Result<Box<[u8]>, JsValue> {
self
.0
.digest_and_reset(length)
.map_err(|message| JsValue::from(js_sys::TypeError::new(message)))
}
/// Returns the digest of the input data so far, and then drops the context
/// from memory on the WASM side. This context must no longer be used, and any
/// further method calls will result in null pointer errors being thrown.
/// https://github.com/rustwasm/wasm-bindgen/blob/bf39cfd8/crates/backend/src/codegen.rs#L186
///
/// `length` will usually be left `undefined` to use the default length for
/// the algorithm. For algorithms with variable-length output, it can be used
/// to specify a non-negative integer number of bytes.
///
/// An error will be thrown if `algorithm` is not a supported hash algorithm or
/// `length` is not a supported length for the algorithm.
#[wasm_bindgen(js_name=digestAndDrop)]
pub fn digest_and_drop(
mut self,
length: Option<usize>,
) -> Result<Box<[u8]>, JsValue> {
self
.0
.digest_and_reset(length)
.map_err(|message| JsValue::from(js_sys::TypeError::new(message)))
}
/// Resets this context to its initial state, as though it has not yet been
/// provided with any input data. (It will still use the same algorithm.)
#[wasm_bindgen]
pub fn reset(&mut self) -> Result<(), JsValue> {
self.0.reset();
Ok(())
}
/// Returns a new `DigestContext` that is a copy of this one, i.e., using the
/// same algorithm and with a copy of the same internal state.
///
/// This may be a more efficient option for computing multiple digests that
/// start with a common prefix.
#[wasm_bindgen]
#[allow(clippy::should_implement_trait)]
pub fn clone(&self) -> DigestContext {
Clone::clone(self)
}
}

27
_wasm_crypto/test.ts Normal file
View File

@ -0,0 +1,27 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
import { assertEquals } from "../testing/asserts.ts";
import { crypto as wasmCrypto } from "./mod.ts";
import { _wasmBytes as wasmBytes } from "./crypto.js";
import * as wasmFileModule from "./crypto.wasm.js";
const webCrypto = globalThis.crypto;
Deno.test("test", async () => {
const input = new TextEncoder().encode("SHA-384");
const wasmDigest = wasmCrypto.digest("SHA-384", input, undefined);
const webDigest = new Uint8Array(
await webCrypto.subtle!.digest("SHA-384", input),
);
assertEquals(wasmDigest, webDigest);
});
Deno.test("Inlined WASM file's metadata should match its content", () => {
assertEquals(wasmBytes.length, wasmFileModule.size);
assertEquals(wasmBytes.byteLength, wasmFileModule.size);
assertEquals(wasmFileModule.data.length, wasmFileModule.size);
assertEquals(wasmFileModule.data.buffer.byteLength, wasmFileModule.size);
});

13
crypto/README.md Normal file
View File

@ -0,0 +1,13 @@
# crypto
## Usage
```typescript
import { crypto } from "https://deno.land/std@$STD_VERSION/crypto/mod.ts";
// This will delegate to the runtime's WebCrypto implementation.
console.log(await crypto.subtle.digest("SHA-384", "hello world"));
// This will use a bundled WASM/Rust implementation.
console.log(await crypto.subtle.digest("BLAKE3", "hello world"));
```

81
crypto/_benches/bench.ts Normal file
View File

@ -0,0 +1,81 @@
#!/usr/bin/env -S deno run
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
import {
bench,
runBenchmarks,
} from "https://deno.land/std@0.102.0/testing/bench.ts";
import { createHash } from "https://deno.land/std@0.102.0/hash/mod.ts";
import { assert, assertEquals } from "../../testing/asserts.ts";
import { crypto as stdCrypto } from "../mod.ts";
const webCrypto = globalThis.crypto;
// WASM is limited to 32-bit operations, which SHA-256 is optimized for, while
// SHA-512 is optimized for 64-bit operations and may be slower.
for (const algorithm of ["SHA-256", "SHA-512"] as const) {
for (
const length of [
64,
262_144,
4_194_304,
67_108_864,
524_291_328,
] as const
) {
// Create a test input buffer and do some operations to hopefully ensure
// it's fully initialized and not optimized away.
const buffer = new Uint8Array(length);
for (let i = 0; i < length; i++) {
buffer[i] = ((i + i % 13) + i % 31) % 255;
}
let sum = 0;
for (const byte of buffer) {
sum += byte;
}
assert(sum > 0);
for (
const implementation of [
"runtime WebCrypto (target)",
"std@0.102.0/hash WASM (baseline)",
"std/crypto WASM (you are here)",
] as const
) {
let lastDigest: ArrayBuffer | undefined;
bench({
name: `${algorithm.padEnd(12)} ${
length.toString().padStart(12)
}B ${implementation}`,
async func(timer) {
timer.start();
let digest;
if (implementation === "std/crypto WASM (you are here)") {
digest = stdCrypto.subtle.digestSync(algorithm, buffer);
} else if (implementation === "runtime WebCrypto (target)") {
digest = await webCrypto.subtle.digest(algorithm, buffer);
} else if (implementation === "std@0.102.0/hash WASM (baseline)") {
digest = createHash(
algorithm.toLowerCase().replace("-", "") as "sha256" | "sha512",
).update(buffer).digest();
} else {
throw new Error(`Unknown implementation ${implementation}`);
}
timer.stop();
assert(digest.byteLength > 0);
if (lastDigest) {
assertEquals(lastDigest, digest);
}
lastDigest = digest;
},
runs: 10,
});
}
}
}
await runBenchmarks();

282
crypto/mod.ts Normal file
View File

@ -0,0 +1,282 @@
// 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<T extends BufferSource>(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<unknown>;
/**
* 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<unknown>;
/**
* 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<unknown>;
/**
* 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<unknown>;
/**
* Returns a new `Promise` object that will digest `data` using the
* specified `AlgorithmIdentifier`.
*/
digest?(
algorithm: WebCryptoAlgorithmIdentifier,
data: BufferSource,
): Promise<ArrayBuffer>;
generateKey?(
algorithm: WebCryptoAlgorithmIdentifier,
extractable: boolean,
keyUsages: string[],
): Promise<unknown>;
deriveKey?(
algorithm: WebCryptoAlgorithmIdentifier,
baseKey: unknown,
derivedKeyType: string,
extractable: boolean,
keyUsages: string[],
): Promise<unknown>;
deriveBits?(
algorithm: WebCryptoAlgorithmIdentifier,
baseKey: unknown,
length: number,
): Promise<unknown>;
importKey?(
format: string,
keyData: BufferSource | unknown,
algorithm: WebCryptoAlgorithmIdentifier,
extractable: boolean,
keyUsages: string[],
): Promise<unknown>;
exportKey?(format: string, key: unknown): Promise<unknown>;
wrapKey?(
format: string,
key: unknown,
wrappingKey: unknown,
wrappingAlgorithm: WebCryptoAlgorithmIdentifier,
): Promise<unknown>;
unwrapKey?(
format: string,
wrappedKey: BufferSource,
unwrappingKey: unknown,
unwrapAlgorithm: WebCryptoAlgorithmIdentifier,
unwrappedKeyAlgorithm: WebCryptoAlgorithmIdentifier,
extractable: boolean,
keyUsages: string[],
): Promise<unknown>;
};
}
/**
* 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 = (<T extends WebCrypto>(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<BufferSource> | Iterable<BufferSource>,
): Promise<ArrayBuffer> {
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<BufferSource>)[Symbol.iterator]) {
return stdCrypto.subtle.digestSync(
algorithm,
data as Iterable<BufferSource>,
);
} else if (
(data as AsyncIterable<BufferSource>)[Symbol.asyncIterator]
) {
const context = new wasmCrypto.DigestContext(name);
for await (const chunk of data as AsyncIterable<BufferSource>) {
const chunkBytes = bufferSourceBytes(chunk);
if (!chunkBytes) {
throw new TypeError("data contained chunk of the wrong type");
}
context.update(chunkBytes);
}
return context.digestAndDrop(length);
} else {
throw new TypeError(
"data must be a BufferSource or [Async]Iterable<BufferSource>",
);
}
} 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 await 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<BufferSource>,
): ArrayBuffer {
algorithm = normalizeAlgorithm(algorithm);
const bytes = bufferSourceBytes(data);
if (bytes) {
return wasmCrypto.digest(algorithm.name, bytes, undefined);
} else if ((data as Iterable<BufferSource>)[Symbol.iterator]) {
const context = new wasmCrypto.DigestContext(algorithm.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);
} else {
throw new TypeError(
"data must be a BufferSource or Iterable<BufferSource>",
);
}
},
},
});
/** 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 };

1259
crypto/test.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
import { createHash } from "../../hash/mod.ts";
import { Buffer } from "../buffer.ts";
import { createHash } from "../crypto.ts";
import { MAX_ALLOC } from "./constants.ts";
import { HASH_DATA } from "./types.ts";
@ -22,20 +22,14 @@ type Algorithms =
| "sha384"
| "sha512";
function createHasher(alg: Algorithms) {
let normalizedAlg: NormalizedAlgorithms;
if (alg === "rmd160") {
normalizedAlg = "ripemd160";
} else {
normalizedAlg = alg;
}
return (value: Uint8Array) =>
Buffer.from(createHash(normalizedAlg).update(value).digest());
}
const createHasher = (algorithm: string) =>
(value: Uint8Array) =>
Buffer.from(createHash(algorithm).update(value).digest() as Buffer);
function getZeroes(zeros: number) {
return Buffer.alloc(zeros);
}
const sizes = {
md5: 16,
sha1: 20,

View File

@ -385,7 +385,7 @@ Deno.test("pbkdf2 hashes data correctly", () => {
dkLen,
algorithm as Algorithms,
(err, res) => {
assert(!err);
assert(!err, String(err));
assertEquals(
res?.toString("hex"),
results[algorithm as Algorithms],

View File

@ -2,16 +2,35 @@
// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license.
import { default as randomBytes } from "./_crypto/randomBytes.ts";
import {
createHash as stdCreateHash,
Hasher,
SupportedAlgorithm,
supportedAlgorithms,
} from "../hash/mod.ts";
crypto as wasmCrypto,
DigestAlgorithm,
digestAlgorithms,
} from "../_wasm_crypto/mod.ts";
import { pbkdf2, pbkdf2Sync } from "./_crypto/pbkdf2.ts";
import { Buffer } from "./buffer.ts";
import { Transform } from "./stream.ts";
import { TransformOptions } from "./_stream/transform.ts";
import { encode as encodeToHex } from "../encoding/hex.ts";
import { encode as encodeToBase64 } from "../encoding/base64.ts";
const coerceToBytes = (data: string | BufferSource): Uint8Array => {
if (data instanceof Uint8Array) {
return data;
} else if (typeof data === "string") {
// This assumes UTF-8, which may not be correct.
return new TextEncoder().encode(data);
} else if (ArrayBuffer.isView(data)) {
return new Uint8Array(
data.buffer,
data.byteOffset,
data.byteLength,
);
} else if (data instanceof ArrayBuffer) {
return new Uint8Array(data);
} else {
throw new TypeError("expected data to be string | BufferSource");
}
};
/**
* The Hash class is a utility for creating hash digests of data. It can be used in one of two ways:
@ -22,34 +41,58 @@ import { encode as encodeToHex } from "../encoding/hex.ts";
* The crypto.createHash() method is used to create Hash instances. Hash objects are not to be created directly using the new keyword.
*/
export class Hash extends Transform {
public hash: Hasher;
constructor(algorithm: SupportedAlgorithm, _opts?: TransformOptions) {
#context: wasmCrypto.DigestContext;
constructor(
algorithm: string | wasmCrypto.DigestContext,
_opts?: TransformOptions,
) {
super({
transform(chunk: string, _encoding: string, callback: () => void): void {
hash.update(chunk);
context.update(coerceToBytes(chunk));
callback();
},
flush(callback: () => void): void {
this.push(hash.digest());
this.push(context.digest(undefined));
callback();
},
});
const hash = this.hash = stdCreateHash(algorithm);
if (typeof algorithm === "string") {
// Node/OpenSSL and WebCrypto format some digest names differently;
// we attempt to handle those here.
algorithm = algorithm.toUpperCase();
if (opensslToWebCryptoDigestNames[algorithm]) {
algorithm = opensslToWebCryptoDigestNames[algorithm];
}
this.#context = new wasmCrypto.DigestContext(
algorithm as DigestAlgorithm,
);
} else {
this.#context = algorithm;
}
const context = this.#context;
}
// TODO(kt3k): Implement copy method
// copy(options) { ... }
copy(): Hash {
return new Hash(this.#context.clone());
}
/**
* Updates the hash content with the given data.
*/
update(data: string | ArrayBuffer, _encoding?: string): this {
let bytes;
if (typeof data === "string") {
data = new TextEncoder().encode(data);
this.hash.update(data);
bytes = coerceToBytes(data);
} else {
this.hash.update(data);
bytes = coerceToBytes(data);
}
this.#context.update(bytes);
return this;
}
@ -58,34 +101,50 @@ export class Hash extends Transform {
*
* If encoding is provided a string will be returned; otherwise a Buffer is returned.
*
* Supported encoding is currently 'hex' only. 'binary', 'base64' will be supported in the future versions.
* Supported encoding is currently 'hex', 'binary', 'base64'.
*/
digest(encoding?: string): Buffer | string {
const digest = this.hash.digest();
const digest = this.#context.digest(undefined);
if (encoding === undefined) {
return Buffer.from(digest);
}
switch (encoding) {
case "hex": {
case "hex":
return new TextDecoder().decode(encodeToHex(new Uint8Array(digest)));
}
// TODO(kt3k): Support more output encodings such as base64, binary, etc
default: {
case "binary":
return String.fromCharCode(...digest);
case "base64":
return encodeToBase64(digest);
default:
throw new Error(
`The output encoding for hash digest is not impelemented: ${encoding}`,
`The output encoding for hash digest is not implemented: ${encoding}`,
);
}
}
}
}
/**
* Supported digest names that OpenSSL/Node and WebCrypto identify differently.
*/
const opensslToWebCryptoDigestNames: Record<string, DigestAlgorithm> = {
"BLAKE2B512": "BLAKE2B",
"BLAKE2S256": "BLAKE2S",
"RIPEMD160": "RIPEMD-160",
"RMD160": "RIPEMD-160",
"SHA1": "SHA-1",
"SHA224": "SHA-224",
"SHA256": "SHA-256",
"SHA384": "SHA-384",
"SHA512": "SHA-512",
};
/**
* Creates and returns a Hash object that can be used to generate hash digests
* using the given `algorithm`. Optional `options` argument controls stream behavior.
*/
export function createHash(
algorithm: SupportedAlgorithm,
algorithm: string,
opts?: TransformOptions,
) {
return new Hash(algorithm, opts);
@ -94,8 +153,8 @@ export function createHash(
/**
* Returns an array of the names of the supported hash algorithms, such as 'sha1'.
*/
export function getHashes(): SupportedAlgorithm[] {
return supportedAlgorithms.slice();
export function getHashes(): readonly string[] {
return digestAlgorithms;
}
export default { Hash, createHash, getHashes, pbkdf2, pbkdf2Sync, randomBytes };

View File

@ -2,7 +2,9 @@
import { hasOwnProperty } from "../_util/has_own_property.ts";
import { BufReader, BufWriter } from "../io/bufio.ts";
import { readLong, readShort, sliceLongToBytes } from "../io/ioutil.ts";
import { Sha1 } from "../hash/sha1.ts";
import { crypto } from "../crypto/mod.ts";
import * as base64 from "../encoding/base64.ts";
import { writeResponse } from "../http/_io.ts";
import { TextProtoReader } from "../textproto/mod.ts";
import { Deferred, deferred } from "../async/deferred.ts";
@ -410,10 +412,9 @@ const kGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
/** Create value of Sec-WebSocket-Accept header from inputted nonce. */
export function createSecAccept(nonce: string): string {
const sha1 = new Sha1();
sha1.update(nonce + kGUID);
const bytes = sha1.digest();
return btoa(String.fromCharCode(...bytes));
return base64.encode(
crypto.subtle.digestSync("SHA-1", new TextEncoder().encode(nonce + kGUID)),
);
}
/** Upgrade inputted TCP connection into WebSocket connection. */