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
|
/target
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
Cargo.lock
|
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" }
|
travis-ci = { repository = "rust-random/getrandom" }
|
||||||
appveyor = { repository = "rust-random/getrandom" }
|
appveyor = { repository = "rust-random/getrandom" }
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"tests/wasm_bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
libc = "0.2"
|
libc = "0.2"
|
||||||
|
|
||||||
|
68
src/lib.rs
68
src/lib.rs
@ -97,9 +97,20 @@
|
|||||||
|
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
|
#![cfg_attr(feature = "stdweb", recursion_limit="128")]
|
||||||
|
|
||||||
#[cfg(not(target_env = "sgx"))]
|
#[cfg(not(target_env = "sgx"))]
|
||||||
#[macro_use] extern crate std;
|
#[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(
|
#[cfg(any(
|
||||||
target_os = "android",
|
target_os = "android",
|
||||||
target_os = "netbsd",
|
target_os = "netbsd",
|
||||||
@ -108,8 +119,8 @@
|
|||||||
target_os = "redox",
|
target_os = "redox",
|
||||||
target_os = "dragonfly",
|
target_os = "dragonfly",
|
||||||
target_os = "haiku",
|
target_os = "haiku",
|
||||||
target_os = "emscripten",
|
|
||||||
target_os = "linux",
|
target_os = "linux",
|
||||||
|
target_arch = "wasm32",
|
||||||
))]
|
))]
|
||||||
mod utils;
|
mod utils;
|
||||||
mod error;
|
mod error;
|
||||||
@ -213,3 +224,58 @@ mod_use!(
|
|||||||
pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> {
|
pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> {
|
||||||
getrandom_inner(dest)
|
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.
|
// except according to those terms.
|
||||||
|
|
||||||
//! Implementation for WASM via wasm-bindgen
|
//! 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.
|
// except according to those terms.
|
||||||
|
|
||||||
//! Implementation for WASM via stdweb
|
//! 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