Implement on WASM via stdweb and bindgen; add tests
This commit is contained in:
parent
5607368c93
commit
51303b5bb8
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,6 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
*.ts
|
||||
*.js
|
||||
*.wasm
|
||||
|
@ -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"
|
||||
|
||||
|
68
src/lib.rs
68
src/lib.rs
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
||||
|
15
tests/wasm_bindgen/Cargo.toml
Normal file
15
tests/wasm_bindgen/Cargo.toml
Normal 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"
|
5
tests/wasm_bindgen/js/index.js
Normal file
5
tests/wasm_bindgen/js/index.js
Normal 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());
|
55
tests/wasm_bindgen/src/lib.rs
Normal file
55
tests/wasm_bindgen/src/lib.rs
Normal 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);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user