Implement on WASM via stdweb and bindgen; add tests

This commit is contained in:
Diggory Hardy 2019-02-25 12:54:46 +00:00
parent 5607368c93
commit 51303b5bb8
8 changed files with 331 additions and 1 deletions

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
/target
**/*.rs.bk
Cargo.lock
*.ts
*.js
*.wasm

View File

@ -9,6 +9,11 @@ description = "A small cross-platform library to securely get random data (entro
travis-ci = { repository = "rust-random/getrandom" }
appveyor = { repository = "rust-random/getrandom" }
[workspace]
members = [
"tests/wasm_bindgen",
]
[target.'cfg(unix)'.dependencies]
libc = "0.2"

View File

@ -97,9 +97,20 @@
#![no_std]
#![cfg_attr(feature = "stdweb", recursion_limit="128")]
#[cfg(not(target_env = "sgx"))]
#[macro_use] extern crate std;
// We have to do it here because we load macros
#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten"),
feature = "wasm-bindgen"))]
extern crate wasm_bindgen;
#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten"),
not(feature = "wasm-bindgen"),
feature = "stdweb"))]
#[macro_use] extern crate stdweb;
#[cfg(any(
target_os = "android",
target_os = "netbsd",
@ -108,8 +119,8 @@
target_os = "redox",
target_os = "dragonfly",
target_os = "haiku",
target_os = "emscripten",
target_os = "linux",
target_arch = "wasm32",
))]
mod utils;
mod error;
@ -213,3 +224,58 @@ mod_use!(
pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> {
getrandom_inner(dest)
}
// Due to rustwasm/wasm-bindgen#201 this can't be defined in the inner os
// modules, so hack around it for now and place it at the root.
#[cfg(all(feature = "wasm-bindgen", target_arch = "wasm32"))]
#[doc(hidden)]
#[allow(missing_debug_implementations)]
pub mod __wbg_shims {
// `extern { type Foo; }` isn't supported on 1.22 syntactically, so use a
// macro to work around that.
macro_rules! rust_122_compat {
($($t:tt)*) => ($($t)*)
}
rust_122_compat! {
extern crate wasm_bindgen;
pub use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
pub type Function;
#[wasm_bindgen(constructor)]
pub fn new(s: &str) -> Function;
#[wasm_bindgen(method)]
pub fn call(this: &Function, self_: &JsValue) -> JsValue;
pub type This;
#[wasm_bindgen(method, getter, structural, js_name = self)]
pub fn self_(me: &This) -> JsValue;
#[wasm_bindgen(method, getter, structural)]
pub fn crypto(me: &This) -> JsValue;
#[derive(Clone, Debug)]
pub type BrowserCrypto;
// TODO: these `structural` annotations here ideally wouldn't be here to
// avoid a JS shim, but for now with feature detection they're
// unavoidable.
#[wasm_bindgen(method, js_name = getRandomValues, structural, getter)]
pub fn get_random_values_fn(me: &BrowserCrypto) -> JsValue;
#[wasm_bindgen(method, js_name = getRandomValues, structural)]
pub fn get_random_values(me: &BrowserCrypto, buf: &mut [u8]);
#[wasm_bindgen(js_name = require)]
pub fn node_require(s: &str) -> NodeCrypto;
#[derive(Clone, Debug)]
pub type NodeCrypto;
#[wasm_bindgen(method, js_name = randomFillSync, structural)]
pub fn random_fill_sync(me: &NodeCrypto, buf: &mut [u8]);
}
}
}

View File

@ -7,3 +7,86 @@
// except according to those terms.
//! Implementation for WASM via wasm-bindgen
use std::cell::RefCell;
use std::mem;
use wasm_bindgen::prelude::*;
use super::__wbg_shims::*;
use super::{Error, UNAVAILABLE_ERROR};
use super::utils::use_init;
#[derive(Clone, Debug)]
pub enum RngSource {
Node(NodeCrypto),
Browser(BrowserCrypto),
}
thread_local!(
static RNG_SOURCE: RefCell<Option<RngSource>> = RefCell::new(None);
);
pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
assert_eq!(mem::size_of::<usize>(), 4);
RNG_SOURCE.with(|f| {
use_init(f, getrandom_init, |source| {
match *source {
RngSource::Node(ref n) => n.random_fill_sync(dest),
RngSource::Browser(ref n) => {
// see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues
//
// where it says:
//
// > A QuotaExceededError DOMException is thrown if the
// > requested length is greater than 65536 bytes.
for chunk in dest.chunks_mut(65536) {
n.get_random_values(chunk)
}
}
}
Ok(())
})
})
}
fn getrandom_init() -> Result<RngSource, Error> {
// First up we need to detect if we're running in node.js or a
// browser. To do this we get ahold of the `this` object (in a bit
// of a roundabout fashion).
//
// Once we have `this` we look at its `self` property, which is
// only defined on the web (either a main window or web worker).
let this = Function::new("return this").call(&JsValue::undefined());
assert!(this != JsValue::undefined());
let this = This::from(this);
let is_browser = this.self_() != JsValue::undefined();
if !is_browser {
return Ok(RngSource::Node(node_require("crypto")))
}
// If `self` is defined then we're in a browser somehow (main window
// or web worker). Here we want to try to use
// `crypto.getRandomValues`, but if `crypto` isn't defined we assume
// we're in an older web browser and the OS RNG isn't available.
let crypto = this.crypto();
if crypto.is_undefined() {
let msg = "self.crypto is undefined";
return Err(UNAVAILABLE_ERROR) // TODO: report msg
}
// Test if `crypto.getRandomValues` is undefined as well
let crypto: BrowserCrypto = crypto.into();
if crypto.get_random_values_fn().is_undefined() {
let msg = "crypto.getRandomValues is undefined";
return Err(UNAVAILABLE_ERROR) // TODO: report msg
}
// Ok! `self.crypto.getRandomValues` is a defined value, so let's
// assume we can do browser crypto.
Ok(RngSource::Browser(crypto))
}

View File

@ -7,3 +7,101 @@
// except according to those terms.
//! Implementation for WASM via stdweb
use std::cell::RefCell;
use std::mem;
use stdweb::unstable::TryInto;
use stdweb::web::error::Error as WebError;
use super::{Error, UNAVAILABLE_ERROR, UNKNOWN_ERROR};
use super::utils::use_init;
#[derive(Clone, Debug)]
enum RngSource {
Browser,
Node
}
thread_local!(
static RNG_SOURCE: RefCell<Option<RngSource>> = RefCell::new(None);
);
pub fn getrandom_inner(mut dest: &mut [u8]) -> Result<(), Error> {
assert_eq!(mem::size_of::<usize>(), 4);
RNG_SOURCE.with(|f| {
use_init(f, getrandom_init, |source| getrandom_fill(source, dest))
})
}
fn getrandom_init() -> Result<RngSource, Error> {
let result = js! {
try {
if (
typeof self === "object" &&
typeof self.crypto === "object" &&
typeof self.crypto.getRandomValues === "function"
) {
return { success: true, ty: 1 };
}
if (typeof require("crypto").randomBytes === "function") {
return { success: true, ty: 2 };
}
return { success: false, error: new Error("not supported") };
} catch(err) {
return { success: false, error: err };
}
};
if js!{ return @{ result.as_ref() }.success } == true {
let ty = js!{ return @{ result }.ty };
if ty == 1 { Ok(RngSource::Browser) }
else if ty == 2 { Ok(RngSource::Node) }
else { unreachable!() }
} else {
let err: WebError = js!{ return @{ result }.error }.try_into().unwrap();
Err(UNAVAILABLE_ERROR) // TODO: forward err
}
}
fn getrandom_fill(source: &mut RngSource, mut dest: &mut [u8]) -> Result<(), Error> {
for chunk in dest.chunks_mut(65536) {
let len = chunk.len() as u32;
let ptr = chunk.as_mut_ptr() as i32;
let result = match source {
RngSource::Browser => js! {
try {
let array = new Uint8Array(@{ len });
self.crypto.getRandomValues(array);
HEAPU8.set(array, @{ ptr });
return { success: true };
} catch(err) {
return { success: false, error: err };
}
},
RngSource::Node => js! {
try {
let bytes = require("crypto").randomBytes(@{ len });
HEAPU8.set(new Uint8Array(bytes), @{ ptr });
return { success: true };
} catch(err) {
return { success: false, error: err };
}
}
};
if js!{ return @{ result.as_ref() }.success } != true {
let err: WebError = js!{ return @{ result }.error }.try_into().unwrap();
return Err(UNKNOWN_ERROR) // TODO: forward err
}
}
Ok(())
}

View File

@ -0,0 +1,15 @@
[package]
name = "getrandom_wasm_bindgen_test"
description = "Minimal test crate for getrandom using wasm-bindgen"
version = "0.1.0"
authors = ["The Rand Project Developers"]
publish = false
license = "MIT/Apache-2.0"
[lib]
crate-type = ["cdylib"]
[dependencies]
getrandom = { path = "../..", features = ["wasm-bindgen"] }
wasm-bindgen = "0.2"
wasm-bindgen-test = "0.2"

View File

@ -0,0 +1,5 @@
'use strict';
const getrandom_wasm_bindgen_test = require('./getrandom_wasm_bindgen_test');
console.log(getrandom_wasm_bindgen_test.test_gen());

View File

@ -0,0 +1,55 @@
// Copyright 2018 Developers of the Rand project.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// Crate to test WASM with the `wasm-bindgen` lib.
#![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk.png")]
extern crate getrandom;
extern crate wasm_bindgen;
extern crate wasm_bindgen_test;
use std::slice;
use wasm_bindgen_test::*;
use wasm_bindgen::prelude::*;
use getrandom::getrandom;
#[wasm_bindgen]
pub fn test_gen() -> i32 {
let mut int: i32 = 0;
unsafe {
let ptr = &mut int as *mut i32 as *mut u8;
let slice = slice::from_raw_parts_mut(ptr, 4);
getrandom(slice).unwrap();
}
int
}
#[wasm_bindgen_test]
fn test_call() {
let mut buf = [0u8; 0];
getrandom(&mut buf).unwrap();
}
#[wasm_bindgen_test]
fn test_diff() {
let mut v1 = [0u8; 1000];
getrandom(&mut v1).unwrap();
let mut v2 = [0u8; 1000];
getrandom(&mut v2).unwrap();
let mut n_diff_bits = 0;
for i in 0..v1.len() {
n_diff_bits += (v1[i] ^ v2[i]).count_ones();
}
// Check at least 1 bit per byte differs. p(failure) < 1e-1000 with random input.
assert!(n_diff_bits >= v1.len() as u32);
}