Skip to content

Commit

Permalink
No commit message
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremyBanks committed Jul 31, 2021
1 parent 2cbf6c7 commit d9dd17f
Show file tree
Hide file tree
Showing 11 changed files with 2,629 additions and 2,489 deletions.
14 changes: 7 additions & 7 deletions _wasm_crypto/_build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,11 @@ if (
SOURCE_DATE_EPOCH: "1600000000",
TZ: "UTC",
LC_ALL: "C",
RUSTFLAGS: `--remap-path-prefix=${root}=. --remap-path-prefix=${home}=~`,
RUSTFLAGS: [
`--remap-path-prefix=${root}=.`,
`--remap-path-prefix=${home}=~`,
`-C target-feature=+simd128`,
].join(" "),
},
}).status()).success)
) {
Expand Down Expand Up @@ -105,7 +109,7 @@ 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.
// 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",
);
Expand All @@ -122,11 +126,7 @@ ${
)
}
// for testing/debugging
export const _wasm = wasm;
export const _wasmModule = wasmModule;
export const _wasmInstance = wasmInstance;
export const _wasmBytes = wasmBytes;
export const _heapByteLength = () => wasm.memory.buffer.byteLength;
`;

await Deno.writeTextFile("./crypto.wasm.js", wasmJs);
Expand Down
6 changes: 1 addition & 5 deletions _wasm_crypto/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -393,8 +393,4 @@ 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;
export const _heapByteLength = () => wasm.memory.buffer.byteLength;
4,889 changes: 2,430 additions & 2,459 deletions _wasm_crypto/crypto.wasm.js

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion _wasm_crypto/rust-toolchain

This file was deleted.

7 changes: 7 additions & 0 deletions _wasm_crypto/rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.

[toolchain]
channel = "1.54.0"
components = [ "rustfmt" ]
targets = [ "wasm32-unknown-unknown" ]
profile = "minimal"
4 changes: 2 additions & 2 deletions _wasm_crypto/src/digest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ impl Context {

/// Whether the algorithm has an extendable variable-length digest output
/// (whether it is an "XOF").
pub const fn extendable(&self) -> bool {
pub fn extendable(&self) -> bool {
matches!(self, Blake3(_) | Shake128(_) | Shake256(_))
}

Expand All @@ -156,7 +156,7 @@ impl Context {
/// 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 {
pub fn algorithm_name(&self) -> impl AsRef<str> {
match self {
Blake2b(_) => "BLAKE2B",
Blake2b256(_) => "BLAKE2B-256",
Expand Down
79 changes: 79 additions & 0 deletions _wasm_crypto/src/hmac.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use derive_more::{AsRef, From, Into};

#[derive(Clone)]
pub struct Hmac {
digest: crate::digest::Context,
outer_key: Box<[u8]>,
inner_key: Box<[u8]>,
}

impl Hmac {
pub fn new(
digest_algorithm: &str,
secret_key: &[u8],
) -> Result<Self, &'static str> {
let mut digest = crate::digest::Context::new(digest_algorithm)?;

assert!(
digest.input_block_length() > digest.output_length(),
"Digest algorithm is not compatible with HMAC. This should never happen."
);

let mut padded_key: Box<[u8]> =
vec![0; digest.input_block_length()].to_owned().into();

if secret_key.len() <= digest.input_block_length() {
padded_key.copy_from_slice(secret_key);
} else {
digest.update(secret_key);
let digested_key = digest.digest_and_reset(None)?;
padded_key.copy_from_slice(&digested_key);
}

let outer_key: Box<[u8]> =
padded_key.iter().map(|byte| byte ^ 0b0101_1100).collect();
let inner_key: Box<[u8]> =
padded_key.iter().map(|byte| byte ^ 0b0011_0110).collect();

digest.update(&inner_key);

Ok(Hmac {
digest,
inner_key,
outer_key,
})
}

pub fn algorithm_name(&self) -> impl AsRef<str> {
return format!("HMAC-{}", self.digest.algorithm_name().as_ref());
}

pub fn reset(&mut self) {
self.digest.reset();
self.digest.update(&self.inner_key);
}

pub fn update(&mut self, data: &[u8]) {
self.digest.update(data)
}

pub fn digest(&self) -> Result<Box<[u8]>, &'static str> {
self.clone().digest_and_drop()
}

pub fn digest_and_reset(&mut self) -> Result<Box<[u8]>, &'static str> {
let inner_digest = self.digest.digest_and_reset(None)?;
self.digest.update(&self.outer_key);
self.digest.update(&inner_digest);
let digest = self.digest.digest_and_reset(None)?;
self.digest.update(&self.inner_key);
Ok(digest)
}

pub fn digest_and_drop(mut self) -> Result<Box<[u8]>, &'static str> {
let inner_digest = self.digest.digest_and_reset(None)?;
self.digest.update(&self.outer_key);
self.digest.update(&inner_digest);
self.digest.digest_and_drop(None)
}
}
1 change: 1 addition & 0 deletions _wasm_crypto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use js_sys::Uint8Array;
use wasm_bindgen::prelude::*;

mod digest;
mod hmac;

/// Returns the digest of the given `data` using the given hash `algorithm`.
///
Expand Down
3 changes: 0 additions & 3 deletions _wasm_crypto/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
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;
Expand All @@ -20,8 +19,6 @@ Deno.test("test", async () => {
});

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);
});
96 changes: 95 additions & 1 deletion crypto/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
# crypto

## Usage
Browser-compatible.

The `crypto` module wraps and extends the standard WebCrypto `Crypto` and
`SubtleCrypto` interfaces, exporting a `crypto` object that can be imported to
use in place of the built-in `window.crypto`.

For operations and algorithms that are
[part of the WebCrypto standard](https://w3c.github.io/webcrypto/#algorithm-overview),
this module will simply delegate to the runtime. For the supported the
non-standard operations and algorithms (listed below), this module will use a
bundled WASM/Rust implementation instead.

## Example

```typescript
import { crypto } from "https://deno.land/std@$STD_VERSION/crypto/mod.ts";
Expand All @@ -11,3 +23,85 @@ 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"));
```

## Capabilities

This module is primarily meant to extend the runtime's existing WebCrypto
implementation, not replace it, so this module itself does not directly
implement very much of the WebCrypto specification. However, this does include
an implementation of the standard digests and HMAC signatures, which will be
used if running in an environment that does not provide its own implementation.

### Hash Digests (`crypto.subtle.digest[Sync](…)`)

In addition to full support for the standard WebCrypto interface and input
types, our implementation supports many other algorithms, and suports
`AsyncIterable` and `Iterable` inputs to allow incremental digesting of large
messages. (This is a superset of the proposed addition of ReadableStream support
to the spec: <https://github.com/w3c/webcrypto/issues/73>.) We also add a
non-standard `.digestSync(…)` variant accepting the same arguments (except that
it can't support `AsyncIterable` input).

We also add an optional `{ length?: number }` parameter for extendable-output
algorithms (XOFs). For example, instead of passing just the algorithm name,
`"BLAKE3"`, you can add a length by passing `{ name: "BLAKE3", length: 20 }`.

### MACs/Secret-Key Signatures (`crypto.subtle.sign[Sync](…)` and `.importKey[Sync(…)]`)

WebCrypto provides several key management features that may need to be
implemented by the runtime to provide the intended security guarauntees. We do
not attempt to directly support any of that, but only support the basic case of
importing and using a raw key from JavaScript:

```ts
const key = await crypto.subtle.importKey("raw", {
name: "HMAC",
algorithm: "SHA-256",
}, keyBytes);
const mac = await crypto.subtle.sign(key, messageBytes);
```

We support `HMAC` used with any of the supported digest algorithms listed below.
The supported algorithms marked as "keyable" can also be used directly via their
keyed-hashing modes. For example, we support both `HMAC-BLAKE3`:

```ts
await crypto.subtle.importKey("raw", {
name: "HMAC",
algorithm: "BLAKE3",
}, keyBytes);
```

and direct `BLAKE3` keyed hashing:

```ts
await crypto.subtle.importKey("raw", "BLAKE3", keyBytes);
```

### Supported Digest Algorithms

- `BLAKE2B-256` (keyable)
- `BLAKE2B-384` (keyable)
- `BLAKE2B` (keyable)
- `BLAKE2S` (keyable)
- `BLAKE3` (XOF, keyable but key must be 32 bytes)
- `KECCAK224`
- `KECCAK256`
- `KECCAK384`
- `KECCAK512`
- [🚫] `MD5`
- [⚠️] `RIPEMD-160`
- [🚫] `SHA-1` (WebCrypto-standard)
- [⚠️] `SHA-224`
- [⚠️] `SHA-256` (WebCrypto-standard)
- `SHA-384` (WebCrypto-standard)
- [⚠️] `SHA-512` (WebCrypto-standard)
- `SHA3-224`
- `SHA3-256`
- `SHA3-384`
- `SHA3-512`
- `SHAKE128` (XOF)
- `SHAKE256` (XOF)

[🚫]: https://en.wikipedia.org/wiki/Collision_attack "collision attacks possible"
[⚠️]: https://en.wikipedia.org/wiki/Length_extension_attack "length-extension attacks possible"
18 changes: 7 additions & 11 deletions crypto/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,22 +108,20 @@ Deno.test("[crypto/digest] Memory use should remain reasonable even with large i
await process.stdin.write(
new TextEncoder().encode(`
import { crypto as stdCrypto } from "./mod.ts";
import { _wasm } from "../_wasm_crypto/crypto.js";
const { memory } = _wasm as { memory: WebAssembly.Memory };
import { _heapByteLength } from "../_wasm_crypto/crypto.js";
const toHexString = (bytes: ArrayBuffer): string =>
new Uint8Array(bytes).reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), "");
const heapBytesInitial = memory.buffer.byteLength;
const heapBytesInitial = _heapByteLength();
const smallData = new Uint8Array(64);
const smallDigest = toHexString(stdCrypto.subtle.digestSync("BLAKE3", smallData.buffer));
const heapBytesAfterSmall = memory.buffer.byteLength;
const heapBytesAfterSmall = _heapByteLength();
const largeData = new Uint8Array(64_000_000);
const largeDigest = toHexString(stdCrypto.subtle.digestSync("BLAKE3", largeData.buffer));
const heapBytesAfterLarge = memory.buffer.byteLength;
const heapBytesAfterLarge = _heapByteLength();
console.log(JSON.stringify(
{
Expand Down Expand Up @@ -202,11 +200,9 @@ Deno.test("[crypto/digest] Memory use should remain reasonable even with many ca
await process.stdin.write(
new TextEncoder().encode(`
import { crypto as stdCrypto } from "./mod.ts";
import { _wasm } from "../_wasm_crypto/crypto.js";
const { memory } = _wasm as { memory: WebAssembly.Memory };
import { _heapByteLength } from "../_wasm_crypto/crypto.js";
const heapBytesInitial = memory.buffer.byteLength;
const heapBytesInitial = _heapByteLength();
let state = new ArrayBuffer(0);
Expand All @@ -219,7 +215,7 @@ Deno.test("[crypto/digest] Memory use should remain reasonable even with many ca
}, state);
}
const heapBytesFinal = memory.buffer.byteLength;
const heapBytesFinal = _heapByteLength();
const stateFinal = toHexString(state);
Expand Down

0 comments on commit d9dd17f

Please sign in to comment.