diff --git a/cli/BUILD.gn b/cli/BUILD.gn index 049553c48a..8e7e5c3051 100644 --- a/cli/BUILD.gn +++ b/cli/BUILD.gn @@ -88,6 +88,7 @@ ts_sources = [ "../js/files.ts", "../js/flatbuffers.ts", "../js/form_data.ts", + "../js/get_random_values.ts", "../js/globals.ts", "../js/headers.ts", "../js/io.ts", diff --git a/cli/main.rs b/cli/main.rs index ff2ed6006a..3ee8fb0edb 100644 --- a/cli/main.rs +++ b/cli/main.rs @@ -11,6 +11,7 @@ extern crate clap; extern crate deno; #[cfg(unix)] extern crate nix; +extern crate rand; mod ansi; pub mod compiler; diff --git a/cli/msg.fbs b/cli/msg.fbs index 6efa871b61..9e4bf658e7 100644 --- a/cli/msg.fbs +++ b/cli/msg.fbs @@ -19,6 +19,7 @@ union Any { FetchRes, FormatError, FormatErrorRes, + GetRandomValues, GlobalTimer, GlobalTimerRes, GlobalTimerStop, @@ -578,4 +579,6 @@ table Seek { whence: uint; } +table GetRandomValues {} + root_type Base; diff --git a/cli/ops.rs b/cli/ops.rs index 023662c205..9b5bc0079e 100644 --- a/cli/ops.rs +++ b/cli/ops.rs @@ -13,6 +13,7 @@ use crate::js_errors::apply_source_map; use crate::js_errors::JSErrorColor; use crate::msg; use crate::msg_util; +use crate::rand; use crate::repl; use crate::resolve_addr::resolve_addr; use crate::resources; @@ -39,6 +40,7 @@ use futures::Sink; use futures::Stream; use hyper; use hyper::rt::Future; +use rand::{thread_rng, Rng}; use remove_dir_all::remove_dir_all; use std; use std::convert::From; @@ -195,6 +197,7 @@ pub fn op_selector_std(inner_type: msg::Any) -> Option { msg::Any::Exit => Some(op_exit), msg::Any::Fetch => Some(op_fetch), msg::Any::FormatError => Some(op_format_error), + msg::Any::GetRandomValues => Some(op_get_random_values), msg::Any::GlobalTimer => Some(op_global_timer), msg::Any::GlobalTimerStop => Some(op_global_timer_stop), msg::Any::IsTTY => Some(op_is_tty), @@ -2168,3 +2171,12 @@ fn op_host_post_message( }); Box::new(op) } + +fn op_get_random_values( + _state: &ThreadSafeState, + _base: &msg::Base<'_>, + data: Option, +) -> Box { + thread_rng().fill(&mut data.unwrap()[..]); + Box::new(ok_future(empty_buf())) +} diff --git a/js/get_random_values.ts b/js/get_random_values.ts new file mode 100644 index 0000000000..eb46ed0098 --- /dev/null +++ b/js/get_random_values.ts @@ -0,0 +1,35 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +import * as msg from "gen/cli/msg_generated"; +import * as flatbuffers from "./flatbuffers"; +import * as dispatch from "./dispatch"; +import { assert } from "./util"; + +function req( + typedArray: ArrayBufferView +): [flatbuffers.Builder, msg.Any, flatbuffers.Offset, ArrayBufferView] { + const builder = flatbuffers.createBuilder(); + const inner = msg.GetRandomValues.createGetRandomValues(builder); + return [builder, msg.Any.GetRandomValues, inner, typedArray]; +} + +/** Synchronously collects cryptographically secure random values. The + * underlying CSPRNG in use is Rust's `rand::rngs::ThreadRng`. + * + * const arr = new Uint8Array(32); + * crypto.getRandomValues(arr); + */ +export function getRandomValues< + T extends + | Int8Array + | Uint8Array + | Uint8ClampedArray + | Int16Array + | Uint16Array + | Int32Array + | Uint32Array +>(typedArray: T): T { + assert(typedArray !== null, "Input must not be null"); + assert(typedArray.length <= 65536, "Input must not be longer than 65536"); + dispatch.sendSync(...req(typedArray as ArrayBufferView)); + return typedArray; +} diff --git a/js/get_random_values_test.ts b/js/get_random_values_test.ts new file mode 100644 index 0000000000..68c13d5973 --- /dev/null +++ b/js/get_random_values_test.ts @@ -0,0 +1,51 @@ +// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license. +import { test, assertNotEquals, assertStrictEq } from "./test_util.ts"; + +test(function getRandomValuesInt8Array(): void { + const arr = new Int8Array(32); + crypto.getRandomValues(arr); + assertNotEquals(arr, new Int8Array(32)); +}); + +test(function getRandomValuesUint8Array(): void { + const arr = new Uint8Array(32); + crypto.getRandomValues(arr); + assertNotEquals(arr, new Uint8Array(32)); +}); + +test(function getRandomValuesUint8ClampedArray(): void { + const arr = new Uint8ClampedArray(32); + crypto.getRandomValues(arr); + assertNotEquals(arr, new Uint8ClampedArray(32)); +}); + +test(function getRandomValuesInt16Array(): void { + const arr = new Int16Array(4); + crypto.getRandomValues(arr); + assertNotEquals(arr, new Int16Array(4)); +}); + +test(function getRandomValuesUint16Array(): void { + const arr = new Uint16Array(4); + crypto.getRandomValues(arr); + assertNotEquals(arr, new Uint16Array(4)); +}); + +test(function getRandomValuesInt32Array(): void { + const arr = new Int32Array(8); + crypto.getRandomValues(arr); + assertNotEquals(arr, new Int32Array(8)); +}); + +test(function getRandomValuesUint32Array(): void { + const arr = new Uint32Array(8); + crypto.getRandomValues(arr); + assertNotEquals(arr, new Uint32Array(8)); +}); + +test(function getRandomValuesReturnValue(): void { + const arr = new Uint32Array(8); + const rtn = crypto.getRandomValues(arr); + assertNotEquals(arr, new Uint32Array(8)); + assertStrictEq(rtn, arr); +}); diff --git a/js/globals.ts b/js/globals.ts index 7652156616..c8289d3055 100644 --- a/js/globals.ts +++ b/js/globals.ts @@ -10,6 +10,7 @@ import { window } from "./window"; import * as blob from "./blob"; import * as consoleTypes from "./console"; +import * as csprng from "./get_random_values"; import * as customEvent from "./custom_event"; import * as deno from "./deno"; import * as domTypes from "./dom_types"; @@ -69,6 +70,10 @@ window.console = console; window.setTimeout = timers.setTimeout; window.setInterval = timers.setInterval; window.location = (undefined as unknown) as domTypes.Location; +// The following Crypto interface implementation is not up to par with the +// standard https://www.w3.org/TR/WebCryptoAPI/#crypto-interface as it does not +// yet incorporate the SubtleCrypto interface as its "subtle" property. +window.crypto = (csprng as unknown) as Crypto; // When creating the runtime type library, we use modifications to `window` to // determine what is in the global namespace. When we put a class in the @@ -135,3 +140,19 @@ export interface ImportMeta { url: string; main: boolean; } + +export interface Crypto { + readonly subtle: null; + getRandomValues: < + T extends + | Int8Array + | Uint8Array + | Uint8ClampedArray + | Int16Array + | Uint16Array + | Int32Array + | Uint32Array + >( + typedArray: T + ) => T; +} diff --git a/js/test_util.ts b/js/test_util.ts index 9d13af6344..af5dbd2b5f 100644 --- a/js/test_util.ts +++ b/js/test_util.ts @@ -14,7 +14,9 @@ import { } from "./deps/https/deno.land/std/testing/asserts.ts"; export { assert, - assertEquals + assertEquals, + assertNotEquals, + assertStrictEq } from "./deps/https/deno.land/std/testing/asserts.ts"; interface TestPermissions { diff --git a/js/unit_tests.ts b/js/unit_tests.ts index b0c6a12f9d..eb6f62bdb1 100644 --- a/js/unit_tests.ts +++ b/js/unit_tests.ts @@ -19,6 +19,7 @@ import "./fetch_test.ts"; import "./file_test.ts"; import "./files_test.ts"; import "./form_data_test.ts"; +import "./get_random_values_test.ts"; import "./globals_test.ts"; import "./headers_test.ts"; import "./link_test.ts";