diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 000000000..be3061aba --- /dev/null +++ b/.cargo/config @@ -0,0 +1,9 @@ +# Allow normal use of "cargo run" and "cargo test" on these wasm32 platforms. +[target.wasm32-unknown-unknown] +runner = 'wasm-bindgen-test-runner' +[target.wasm32-wasi] +runner = 'wasmtime' + +# Just run on node by default (that's where emscripten is tested) +[target.'cfg(target_os = "emscripten")'] +runner = 'node' diff --git a/.clippy.toml b/.clippy.toml new file mode 100644 index 000000000..492e05c4c --- /dev/null +++ b/.clippy.toml @@ -0,0 +1 @@ +msrv = "1.34" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 000000000..3b56a943a --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,335 @@ +name: Tests + +on: + push: + branches: master + pull_request: + branches: master + schedule: + - cron: "0 12 * * 1" + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +jobs: + check-doc: + name: Docs, deadlinks, minimal dependencies + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly # Needed for -Z minimal-versions and doc_cfg + override: true + - uses: Swatinem/rust-cache@v1 + - name: Install precompiled cargo-deadlinks + run: | + export URL=$(curl -s https://api.github.com/repos/deadlinks/cargo-deadlinks/releases/latest | jq -r '.assets[] | select(.name | contains("cargo-deadlinks-linux")) | .browser_download_url') + wget -O /tmp/cargo-deadlinks $URL + chmod +x /tmp/cargo-deadlinks + mv /tmp/cargo-deadlinks ~/.cargo/bin + - name: Generate Docs + env: + RUSTDOCFLAGS: --cfg docsrs + run: cargo deadlinks -- --features=custom,std + - run: | + cargo generate-lockfile -Z minimal-versions + cargo test --features=custom,std + + main-tests: + name: Main tests + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + toolchain: [nightly, beta, stable, 1.34] + # Only Test macOS on stable to reduce macOS CI jobs + include: + - os: macos-latest + toolchain: stable + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.toolchain }} + override: true + - uses: Swatinem/rust-cache@v1 + - run: cargo test + - run: cargo test --features=std + - run: cargo test --features=custom # custom should do nothing here + - if: ${{ matrix.toolchain == 'nightly' }} + run: cargo build --benches + + linux-tests: + name: Additional Linux targets + runs-on: ubuntu-latest + strategy: + matrix: + target: [ + x86_64-unknown-linux-musl, + i686-unknown-linux-gnu, + i686-unknown-linux-musl, + ] + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + target: ${{ matrix.target }} + toolchain: stable + - uses: Swatinem/rust-cache@v1 + - name: Install multilib + # update is needed to fix the 404 error on install, see: + # https://github.com/actions/virtual-environments/issues/675 + run: | + sudo apt-get update + sudo apt-get install gcc-multilib + - run: cargo test --target=${{ matrix.target }} --features=std + + # We can only Build/Link on these targets for now. + # TODO: Run the iOS binaries in the simulator + # TODO: build/run aarch64-apple-darwin binaries on a x86_64 Mac + apple-tests: + name: Additional Apple targets + runs-on: macos-latest + strategy: + matrix: + target: [ + aarch64-apple-ios, + x86_64-apple-ios, + ] + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + target: ${{ matrix.target }} + toolchain: stable + - uses: Swatinem/rust-cache@v1 + - name: Build Tests + run: cargo test --no-run --target=${{ matrix.target }} --features=std + + windows-tests: + name: Additional Windows targets + runs-on: windows-latest + strategy: + matrix: + toolchain: [ + stable-x86_64-gnu, + stable-i686-gnu, + stable-i686-msvc, + ] + steps: + - uses: actions/checkout@v2 + - name: Install toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.toolchain }} + override: true + - uses: Swatinem/rust-cache@v1 + - run: cargo test --features=std + + cross-tests: + name: Cross Test + runs-on: ubuntu-latest + strategy: + matrix: + target: [ + aarch64-unknown-linux-gnu, + aarch64-linux-android, + mips-unknown-linux-gnu, + ] + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + target: ${{ matrix.target }} + toolchain: stable + - uses: Swatinem/rust-cache@v1 + - name: Install precompiled cross + run: | + export URL=$(curl -s https://api.github.com/repos/rust-embedded/cross/releases/latest | jq -r '.assets[] | select(.name | contains("x86_64-unknown-linux-gnu.tar.gz")) | .browser_download_url') + wget -O /tmp/binaries.tar.gz $URL + tar -C /tmp -xzf /tmp/binaries.tar.gz + mv /tmp/cross ~/.cargo/bin + - name: Test + run: cross test --no-fail-fast --target=${{ matrix.target }} --features=std + + cross-link: + name: Cross Build/Link + runs-on: ubuntu-latest + strategy: + matrix: + target: [ + x86_64-sun-solaris, + x86_64-unknown-netbsd, + ] + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + target: ${{ matrix.target }} + toolchain: stable + - uses: Swatinem/rust-cache@v1 + - name: Install precompiled cross + run: | + export URL=$(curl -s https://api.github.com/repos/rust-embedded/cross/releases/latest | jq -r '.assets[] | select(.name | contains("x86_64-unknown-linux-gnu.tar.gz")) | .browser_download_url') + wget -O /tmp/binaries.tar.gz $URL + tar -C /tmp -xzf /tmp/binaries.tar.gz + mv /tmp/cross ~/.cargo/bin + - name: Build Tests + run: cross test --no-run --target=${{ matrix.target }} --features=std + + web-tests: + name: Web tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + target: wasm32-unknown-unknown + toolchain: stable + - uses: Swatinem/rust-cache@v1 + - name: Install precompiled wasm-bindgen-test-runner + run: | + export VERSION=$(cargo metadata --format-version=1 | jq -r '.packages[] | select ( .name == "wasm-bindgen" ) | .version') + wget -O /tmp/binaries.tar.gz https://github.com/rustwasm/wasm-bindgen/releases/download/$VERSION/wasm-bindgen-$VERSION-x86_64-unknown-linux-musl.tar.gz + tar -C /tmp -xzf /tmp/binaries.tar.gz --strip-components=1 + mv /tmp/wasm-bindgen-test-runner ~/.cargo/bin + - name: Test (Node) + run: cargo test --target=wasm32-unknown-unknown --features=js + - name: Test (Firefox) + env: + GECKODRIVER: /usr/bin/geckodriver + run: cargo test --target=wasm32-unknown-unknown --features=js,test-in-browser + - name: Test (Chrome) + env: + CHROMEDRIVER: /usr/bin/chromedriver + run: cargo test --target=wasm32-unknown-unknown --features=js,test-in-browser + - name: Test (custom getrandom) + run: cargo test --target=wasm32-unknown-unknown --features=custom + + wasi-tests: + name: WASI test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + target: wasm32-wasi + toolchain: stable + - uses: Swatinem/rust-cache@v1 + - name: Install precompiled wasmtime + run: | + export URL=$(curl -s https://api.github.com/repos/bytecodealliance/wasmtime/releases/latest | jq -r '.assets[] | select(.name | contains("x86_64-linux.tar.xz")) | .browser_download_url') + wget -O /tmp/binaries.tar.xz $URL + tar -C /tmp -xf /tmp/binaries.tar.xz --strip-components=1 + mv /tmp/wasmtime ~/.cargo/bin + - run: cargo test --target wasm32-wasi + + emscripten-tests: + name: Emscripten tests + runs-on: ubuntu-latest + env: + EMSDK_VERSION: 1.39.20 # Last emsdk compatible with Rust's LLVM 11 + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + - run: rustup target add wasm32-unknown-emscripten + - run: rustup target add asmjs-unknown-emscripten + - uses: Swatinem/rust-cache@v1 + - name: Cache emsdk + id: cache-emsdk + uses: actions/cache@v2 + with: + path: ~/emsdk + key: ${{ runner.os }}-${{ env.EMSDK_VERSION }}-emsdk + - name: Install emsdk + if: steps.cache-emsdk.outputs.cache-hit != 'true' + run: | + git clone https://github.com/emscripten-core/emsdk.git ~/emsdk + cd ~/emsdk + ./emsdk install $EMSDK_VERSION + ./emsdk activate $EMSDK_VERSION + - run: echo "$HOME/emsdk/upstream/emscripten" >> $GITHUB_PATH + - name: wasm test + run: cargo test --target=wasm32-unknown-emscripten --features=std + - name: asm.js test + run: | # Debug info breaks on asm.js + RUSTFLAGS="$RUSTFLAGS -C debuginfo=0" + cargo test --target=asmjs-unknown-emscripten --features=std + + build: + name: Build only + runs-on: ubuntu-latest + strategy: + matrix: + target: [ + x86_64-unknown-freebsd, + x86_64-fuchsia, + x86_64-unknown-redox, + x86_64-fortanix-unknown-sgx, + ] + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + target: ${{ matrix.target }} + toolchain: nightly # Required to build libc for Redox + override: true + - uses: Swatinem/rust-cache@v1 + - name: Build + run: cargo build --target=${{ matrix.target }} --features=std + + build-std: + name: Build-only (build-std) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly # Required to build libcore + components: rust-src + override: true + - uses: Swatinem/rust-cache@v1 + - name: UEFI (RDRAND) + run: cargo build -Z build-std=core --features=rdrand --target=x86_64-unknown-uefi + - name: Hermit (RDRAND) + run: cargo build -Z build-std=core --features=rdrand --target=x86_64-unknown-hermit + - name: L4Re (RDRAND) + run: cargo build -Z build-std=core --features=rdrand --target=x86_64-unknown-l4re-uclibc + - name: VxWorks + run: cargo build -Z build-std=core --target=x86_64-wrs-vxworks + + clippy-fmt: + name: Clippy + rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + # https://github.com/rust-lang/rust-clippy/pull/6379 added MSRV + # support, so we need to use nightly until this is on stable. + toolchain: nightly + components: rustfmt, clippy + override: true + - uses: Swatinem/rust-cache@v1 + - name: clippy + run: cargo clippy --all --features=custom,std + - name: fmt + run: cargo fmt --all -- --check diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..435eeeee4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/target +**/*.rs.bk +Cargo.lock +*.ts +*.js +*.wasm diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..c3ca728fd --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,244 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.2.2] - 2021-01-19 +### Changed +- Forward `rustc-dep-of-std` to dependencies. [#198] +- Highlight feature-dependend functionality in documentation using the `doc_cfg` feature. [#200] + +[#198]: https://github.com/rust-random/getrandom/pull/198 +[#200]: https://github.com/rust-random/getrandom/pull/200 + +## [0.2.1] - 2021-01-03 +### Changed +- Update `cfg-if` to v1.0. [#166] +- Update `wasi` to v0.10. [#167] + +### Fixed +- Multithreaded WASM support. [#165] + +### Removed +- Windows XP support. [#177] +- Direct `stdweb` support. [#178] +- CloudABI support. [#184] + +[#165]: https://github.com/rust-random/getrandom/pull/165 +[#166]: https://github.com/rust-random/getrandom/pull/166 +[#167]: https://github.com/rust-random/getrandom/pull/167 +[#177]: https://github.com/rust-random/getrandom/pull/177 +[#178]: https://github.com/rust-random/getrandom/pull/178 +[#184]: https://github.com/rust-random/getrandom/pull/184 + +## [0.2.0] - 2020-09-10 +### Features for using getrandom on unsupported targets + +The following (off by default) Cargo features have been added: +- `"rdrand"` - use the RDRAND instruction on `no_std` `x86`/`x86_64` targets [#133] +- `"js"` - use JavaScript calls on `wasm32-unknown-unknown` [#149] + - Replaces the `stdweb` and `wasm-bindgen` features (which are removed) +- `"custom"` - allows a user to specify a custom implementation [#109] + +### Breaking Changes +- Unsupported targets no longer compile [#107] +- Change/Add `Error` constants [#120] +- Only impl `std` traits when the `"std"` Cargo feature is specified [#106] +- Remove offical support for Hermit, L4Re, and UEFI [#133] +- Remove optional `"log"` dependancy [#131] +- Update minimum supported Linux kernel to 2.6.32 [#153] +- Update MSRV to 1.34 [#159] + +[#106]: https://github.com/rust-random/getrandom/pull/106 +[#107]: https://github.com/rust-random/getrandom/pull/107 +[#109]: https://github.com/rust-random/getrandom/pull/109 +[#120]: https://github.com/rust-random/getrandom/pull/120 +[#131]: https://github.com/rust-random/getrandom/pull/131 +[#133]: https://github.com/rust-random/getrandom/pull/133 +[#149]: https://github.com/rust-random/getrandom/pull/149 +[#153]: https://github.com/rust-random/getrandom/pull/153 +[#159]: https://github.com/rust-random/getrandom/pull/159 + +## [0.1.16] - 2020-12-31 +### Changed +- Update `cfg-if` to v1.0. [#173] +- Implement `std::error::Error` for the `Error` type on additional targets. [#169] + +### Fixed +- Multithreaded WASM support. [#171] + +[#173]: https://github.com/rust-random/getrandom/pull/173 +[#171]: https://github.com/rust-random/getrandom/pull/171 +[#169]: https://github.com/rust-random/getrandom/pull/169 + +## [0.1.15] - 2020-09-10 +### Changed +- Added support for Internet Explorer 11 [#139] +- Fix Webpack require warning with `wasm-bindgen` [#137] + +[#137]: https://github.com/rust-random/getrandom/pull/137 +[#139]: https://github.com/rust-random/getrandom/pull/139 + +## [0.1.14] - 2020-01-07 +### Changed +- Remove use of spin-locks in the `use_file` module. [#125] +- Update `wasi` to v0.9. [#126] +- Do not read errno value on DragonFlyBSD to fix compilation failure. [#129] + +[#125]: https://github.com/rust-random/getrandom/pull/125 +[#126]: https://github.com/rust-random/getrandom/pull/126 +[#129]: https://github.com/rust-random/getrandom/pull/129 + +## [0.1.13] - 2019-08-25 +### Added +- VxWorks targets support. [#86] + +### Changed +- If zero-length slice is passed to the `getrandom` function, always return +`Ok(())` immediately without doing any calls to the underlying operating +system. [#104] +- Use the `kern.arandom` sysctl on NetBSD. [#115] + +### Fixed +- Bump `cfg-if` minimum version from 0.1.0 to 0.1.2. [#112] +- Typos and bad doc links. [#117] + +[#86]: https://github.com/rust-random/getrandom/pull/86 +[#104]: https://github.com/rust-random/getrandom/pull/104 +[#112]: https://github.com/rust-random/getrandom/pull/112 +[#115]: https://github.com/rust-random/getrandom/pull/115 +[#117]: https://github.com/rust-random/getrandom/pull/117 + +## [0.1.12] - 2019-08-18 +### Changed +- Update wasi dependency from v0.5 to v0.7. [#100] + +[#100]: https://github.com/rust-random/getrandom/pull/100 + +## [0.1.11] - 2019-08-25 +### Fixed +- Implement `std`-dependent traits for selected targets even if `std` +feature is disabled. (backward compatibility with v0.1.8) [#96] + +[#96]: https://github.com/rust-random/getrandom/pull/96 + +## [0.1.10] - 2019-08-18 [YANKED] +### Changed +- Use the dummy implementation on `wasm32-unknown-unknown` even with the +disabled `dummy` feature. [#90] + +### Fixed +- Fix CSP error for `wasm-bindgen`. [#92] + +[#90]: https://github.com/rust-random/getrandom/pull/90 +[#92]: https://github.com/rust-random/getrandom/pull/92 + +## [0.1.9] - 2019-08-14 [YANKED] +### Changed +- Remove `std` dependency for opening and reading files. [#58] +- Use `wasi` isntead of `libc` on WASI target. [#64] +- By default emit a compile-time error when built for an unsupported target. +This behaviour can be disabled by using the `dummy` feature. [#71] + +### Added +- Add support for UWP targets. [#69] +- Add unstable `rustc-dep-of-std` feature. [#78] + +[#58]: https://github.com/rust-random/getrandom/pull/58 +[#64]: https://github.com/rust-random/getrandom/pull/64 +[#69]: https://github.com/rust-random/getrandom/pull/69 +[#71]: https://github.com/rust-random/getrandom/pull/71 +[#78]: https://github.com/rust-random/getrandom/pull/78 + +## [0.1.8] - 2019-07-29 +### Changed +- Explicitly specify types to arguments of 'libc::syscall'. [#74] + +[#74]: https://github.com/rust-random/getrandom/pull/74 + +## [0.1.7] - 2019-07-29 +### Added +- Support for hermit and l4re. [#61] +- `Error::raw_os_error` method, `Error::INTERNAL_START` and +`Error::CUSTOM_START` constants. Use `libc` for retrieving OS error descriptions. [#54] + +### Changed +- Remove `lazy_static` dependency and use custom structures for lock-free +initialization. [#51] [#52] +- Try `getrandom()` first on FreeBSD. [#57] + +### Removed +- Bitrig support. [#56] + +### Deprecated +- `Error::UNKNOWN`, `Error::UNAVAILABLE`. [#54] + +[#51]: https://github.com/rust-random/getrandom/pull/51 +[#52]: https://github.com/rust-random/getrandom/pull/52 +[#54]: https://github.com/rust-random/getrandom/pull/54 +[#56]: https://github.com/rust-random/getrandom/pull/56 +[#57]: https://github.com/rust-random/getrandom/pull/57 +[#61]: https://github.com/rust-random/getrandom/pull/61 + +## [0.1.6] - 2019-06-30 +### Changed +- Minor change of RDRAND AMD bug handling. [#48] + +[#48]: https://github.com/rust-random/getrandom/pull/48 + +## [0.1.5] - 2019-06-29 +### Fixed +- Use shared `File` instead of shared file descriptor. [#44] +- Workaround for RDRAND hardware bug present on some AMD CPUs. [#43] + +### Changed +- Try `getentropy` and then fallback to `/dev/random` on macOS. [#38] + +[#38]: https://github.com/rust-random/getrandom/issues/38 +[#43]: https://github.com/rust-random/getrandom/pull/43 +[#44]: https://github.com/rust-random/getrandom/issues/44 + +## [0.1.4] - 2019-06-28 +### Added +- Add support for `x86_64-unknown-uefi` target by using RDRAND with CPUID +feature detection. [#30] + +### Fixed +- Fix long buffer issues on Windows and Linux. [#31] [#32] +- Check `EPERM` in addition to `ENOSYS` on Linux. [#37] + +### Changed +- Improve efficiency by sharing file descriptor across threads. [#13] +- Remove `cloudabi`, `winapi`, and `fuchsia-cprng` dependencies. [#40] +- Improve RDRAND implementation. [#24] +- Don't block during syscall detection on Linux. [#26] +- Increase consistency with libc implementation on FreeBSD. [#36] +- Apply `rustfmt`. [#39] + +[#30]: https://github.com/rust-random/getrandom/pull/30 +[#13]: https://github.com/rust-random/getrandom/issues/13 +[#40]: https://github.com/rust-random/getrandom/pull/40 +[#26]: https://github.com/rust-random/getrandom/pull/26 +[#24]: https://github.com/rust-random/getrandom/pull/24 +[#39]: https://github.com/rust-random/getrandom/pull/39 +[#36]: https://github.com/rust-random/getrandom/pull/36 +[#31]: https://github.com/rust-random/getrandom/issues/31 +[#32]: https://github.com/rust-random/getrandom/issues/32 +[#37]: https://github.com/rust-random/getrandom/issues/37 + +## [0.1.3] - 2019-05-15 +- Update for `wasm32-unknown-wasi` being renamed to `wasm32-wasi`, and for + WASI being categorized as an OS. + +## [0.1.2] - 2019-04-06 +- Add support for `wasm32-unknown-wasi` target. + +## [0.1.1] - 2019-04-05 +- Enable std functionality for CloudABI by default. + +## [0.1.0] - 2019-03-23 +Publish initial implementation. + +## [0.0.0] - 2019-01-19 +Publish an empty template library. diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 000000000..dabf016f8 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "getrandom" +version = "0.2.2" # Also update html_root_url in lib.rs when bumping this +edition = "2018" +authors = ["The Rand Project Developers"] +license = "MIT OR Apache-2.0" +description = "A small cross-platform library for retrieving random data from system source" +documentation = "https://docs.rs/getrandom" +repository = "https://github.com/rust-random/getrandom" +categories = ["os", "no-std"] +exclude = [".*"] + +[dependencies] +cfg-if = "1" + +# When built as part of libstd +compiler_builtins = { version = "0.1", optional = true } +core = { version = "1.0", optional = true, package = "rustc-std-workspace-core" } + +[target.'cfg(unix)'.dependencies] +libc = { version = "0.2.64", default-features = false } + +[target.'cfg(target_os = "wasi")'.dependencies] +wasi = "0.10" + +[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies] +wasm-bindgen = { version = "0.2.62", default-features = false, optional = true } +js-sys = { version = "0.3", optional = true } +[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dev-dependencies] +wasm-bindgen-test = "0.3.18" + +[features] +# Implement std-only traits for getrandom::Error +std = [] +# Feature to enable fallback RDRAND-based implementation on x86/x86_64 +rdrand = [] +# Feature to enable JavaScript bindings on wasm32-unknown-unknown +js = ["wasm-bindgen", "js-sys"] +# Feature to enable custom RNG implementations +custom = [] +# Unstable feature to support being a libstd dependency +rustc-dep-of-std = [ + "compiler_builtins", + "core", + "libc/rustc-dep-of-std", + "wasi/rustc-dep-of-std", +] +# Unstable/test-only feature to run wasm-bindgen tests in a browser +test-in-browser = [] + +[package.metadata.docs.rs] +features = ["std", "custom"] +rustdoc-args = ["--cfg", "docsrs"] diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 000000000..17d74680f --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 000000000..d93b5baf3 --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,26 @@ +Copyright 2018 Developers of the Rand project +Copyright (c) 2014 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 000000000..df2307b9c --- /dev/null +++ b/README.md @@ -0,0 +1,64 @@ +# getrandom + +[![Build Status]][GitHub Actions] [![Crate]][crates.io] [![Documentation]][docs.rs] [![Dependency Status]][deps.rs] [![Downloads]][crates.io] [![License]][LICENSE-MIT] + +[GitHub Actions]: https://github.com/rust-random/getrandom/actions?query=workflow:Tests+branch:master +[Build Status]: https://github.com/rust-random/getrandom/workflows/Tests/badge.svg?branch=master +[crates.io]: https://crates.io/crates/getrandom +[Crate]: https://img.shields.io/crates/v/getrandom +[docs.rs]: https://docs.rs/getrandom +[Documentation]: https://docs.rs/getrandom/badge.svg +[deps.rs]: https://deps.rs/repo/github/rust-random/getrandom +[Dependency Status]: https://deps.rs/repo/github/rust-random/getrandom/status.svg +[Downloads]: https://img.shields.io/crates/d/getrandom +[LICENSE-MIT]: https://raw.githubusercontent.com/rust-random/getrandom/master/LICENSE-MIT +[License]: https://img.shields.io/crates/l/getrandom + + +A Rust library for retrieving random data from (operating) system source. It is +assumed that system always provides high-quality cryptographically secure random +data, ideally backed by hardware entropy sources. This crate derives its name +from Linux's `getrandom` function, but is cross platform, roughly supporting +the same set of platforms as Rust's `std` lib. + +This is a low-level API. Most users should prefer using high-level random-number +library like [`rand`]. + +[`rand`]: https://crates.io/crates/rand + +## Usage + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +getrandom = "0.2" +``` + +Then invoke the `getrandom` function: + +```rust +fn get_random_buf() -> Result<[u8; 32], getrandom::Error> { + let mut buf = [0u8; 32]; + getrandom::getrandom(&mut buf)?; + Ok(buf) +} +``` + +For more information about supported targets, entropy sources, `no_std` targets, +crate features, WASM support and Custom RNGs see the +[`getrandom` documentation](https://docs.rs/getrandom/latest) and +[`getrandom::Error` documentation](https://docs.rs/getrandom/latest/getrandom/struct.Error.html). + +## Minimum Supported Rust Version + +This crate requires Rust 1.34.0 or later. + +# License + +The `getrandom` library is distributed under either of + + * [Apache License, Version 2.0](LICENSE-APACHE) + * [MIT license](LICENSE-MIT) + +at your option. diff --git a/benches/mod.rs b/benches/mod.rs new file mode 100644 index 000000000..a93e72064 --- /dev/null +++ b/benches/mod.rs @@ -0,0 +1,22 @@ +#![feature(test)] +extern crate test; + +#[bench] +fn bench_64(b: &mut test::Bencher) { + let mut buf = [0u8; 64]; + b.iter(|| { + getrandom::getrandom(&mut buf[..]).unwrap(); + test::black_box(&buf); + }); + b.bytes = buf.len() as u64; +} + +#[bench] +fn bench_65536(b: &mut test::Bencher) { + let mut buf = [0u8; 65536]; + b.iter(|| { + getrandom::getrandom(&mut buf[..]).unwrap(); + test::black_box(&buf); + }); + b.bytes = buf.len() as u64; +} diff --git a/src/bsd_arandom.rs b/src/bsd_arandom.rs new file mode 100644 index 000000000..f26f2609c --- /dev/null +++ b/src/bsd_arandom.rs @@ -0,0 +1,52 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation for FreeBSD and NetBSD +use crate::{util_libc::sys_fill_exact, Error}; +use core::ptr; + +fn kern_arnd(buf: &mut [u8]) -> libc::ssize_t { + static MIB: [libc::c_int; 2] = [libc::CTL_KERN, libc::KERN_ARND]; + let mut len = buf.len(); + let ret = unsafe { + libc::sysctl( + MIB.as_ptr(), + MIB.len() as libc::c_uint, + buf.as_mut_ptr() as *mut _, + &mut len, + ptr::null(), + 0, + ) + }; + if ret == -1 { + -1 + } else { + len as libc::ssize_t + } +} + +pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { + #[cfg(target_os = "freebsd")] + { + use crate::util_libc::Weak; + static GETRANDOM: Weak = unsafe { Weak::new("getrandom\0") }; + type GetRandomFn = + unsafe extern "C" fn(*mut u8, libc::size_t, libc::c_uint) -> libc::ssize_t; + + if let Some(fptr) = GETRANDOM.ptr() { + let func: GetRandomFn = unsafe { core::mem::transmute(fptr) }; + return sys_fill_exact(dest, |buf| unsafe { func(buf.as_mut_ptr(), buf.len(), 0) }); + } + } + // Both FreeBSD and NetBSD will only return up to 256 bytes at a time, and + // older NetBSD kernels will fail on longer buffers. + for chunk in dest.chunks_mut(256) { + sys_fill_exact(chunk, kern_arnd)? + } + Ok(()) +} diff --git a/src/custom.rs b/src/custom.rs new file mode 100644 index 000000000..0d3123c58 --- /dev/null +++ b/src/custom.rs @@ -0,0 +1,102 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! An implementation which calls out to an externally defined function. +use crate::Error; +use core::num::NonZeroU32; + +/// Register a function to be invoked by `getrandom` on unsupported targets. +/// +/// ## Writing a custom `getrandom` implementation +/// +/// The function to register must have the same signature as +/// [`getrandom::getrandom`](crate::getrandom). The function can be defined +/// wherever you want, either in root crate or a dependant crate. +/// +/// For example, if we wanted a `failure-getrandom` crate containing an +/// implementation that always fails, we would first depend on `getrandom` +/// (for the [`Error`] type) in `failure-getrandom/Cargo.toml`: +/// ```toml +/// [dependencies] +/// getrandom = "0.2" +/// ``` +/// Note that the crate containing this function does **not** need to enable the +/// `"custom"` Cargo feature. +/// +/// Next, in `failure-getrandom/src/lib.rs`, we define our function: +/// ```rust +/// use core::num::NonZeroU32; +/// use getrandom::Error; +/// +/// // Some application-specific error code +/// const MY_CUSTOM_ERROR_CODE: u32 = Error::CUSTOM_START + 42; +/// pub fn always_fail(buf: &mut [u8]) -> Result<(), Error> { +/// let code = NonZeroU32::new(MY_CUSTOM_ERROR_CODE).unwrap(); +/// Err(Error::from(code)) +/// } +/// ``` +/// +/// ## Registering a custom `getrandom` implementation +/// +/// Functions can only be registered in the root binary crate. Attempting to +/// register a function in a non-root crate will result in a linker error. +/// This is similar to +/// [`#[panic_handler]`](https://doc.rust-lang.org/nomicon/panic-handler.html) or +/// [`#[global_allocator]`](https://doc.rust-lang.org/edition-guide/rust-2018/platform-and-target-support/global-allocators.html), +/// where helper crates define handlers/allocators but only the binary crate +/// actually _uses_ the functionality. +/// +/// To register the function, we first depend on `failure-getrandom` _and_ +/// `getrandom` in `Cargo.toml`: +/// ```toml +/// [dependencies] +/// failure-getrandom = "0.1" +/// getrandom = { version = "0.2", features = ["custom"] } +/// ``` +/// +/// Then, we register the function in `src/main.rs`: +/// ```rust +/// # mod failure_getrandom { pub fn always_fail(_: &mut [u8]) -> Result<(), getrandom::Error> { unimplemented!() } } +/// use failure_getrandom::always_fail; +/// use getrandom::register_custom_getrandom; +/// +/// register_custom_getrandom!(always_fail); +/// ``` +/// +/// Now any user of `getrandom` (direct or indirect) on this target will use the +/// registered function. As noted in the +/// [top-level documentation](index.html#custom-implementations) this +/// registration only has an effect on unsupported targets. +#[macro_export] +#[cfg_attr(docsrs, doc(cfg(feature = "custom")))] +macro_rules! register_custom_getrandom { + ($path:path) => { + // We use an extern "C" function to get the guarantees of a stable ABI. + #[no_mangle] + extern "C" fn __getrandom_custom(dest: *mut u8, len: usize) -> u32 { + let f: fn(&mut [u8]) -> Result<(), ::getrandom::Error> = $path; + let slice = unsafe { ::core::slice::from_raw_parts_mut(dest, len) }; + match f(slice) { + Ok(()) => 0, + Err(e) => e.code().get(), + } + } + }; +} + +#[allow(dead_code)] +pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { + extern "C" { + fn __getrandom_custom(dest: *mut u8, len: usize) -> u32; + } + let ret = unsafe { __getrandom_custom(dest.as_mut_ptr(), dest.len()) }; + match NonZeroU32::new(ret) { + None => Ok(()), + Some(code) => Err(Error::from(code)), + } +} diff --git a/src/dragonfly.rs b/src/dragonfly.rs new file mode 100644 index 000000000..f27e90690 --- /dev/null +++ b/src/dragonfly.rs @@ -0,0 +1,26 @@ +// Copyright 2021 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation for DragonFly BSD +use crate::{ + use_file, + util_libc::{sys_fill_exact, Weak}, + Error, +}; + +pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { + static GETRANDOM: Weak = unsafe { Weak::new("getrandom\0") }; + type GetRandomFn = unsafe extern "C" fn(*mut u8, libc::size_t, libc::c_uint) -> libc::ssize_t; + + if let Some(fptr) = GETRANDOM.ptr() { + let func: GetRandomFn = unsafe { core::mem::transmute(fptr) }; + return sys_fill_exact(dest, |buf| unsafe { func(buf.as_mut_ptr(), buf.len(), 0) }); + } else { + use_file::getrandom_inner(dest) + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 000000000..48abdc11d --- /dev/null +++ b/src/error.rs @@ -0,0 +1,184 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +use core::{fmt, num::NonZeroU32}; + +/// A small and `no_std` compatible error type +/// +/// The [`Error::raw_os_error()`] will indicate if the error is from the OS, and +/// if so, which error code the OS gave the application. If such an error is +/// encountered, please consult with your system documentation. +/// +/// Internally this type is a NonZeroU32, with certain values reserved for +/// certain purposes, see [`Error::INTERNAL_START`] and [`Error::CUSTOM_START`]. +/// +/// *If this crate's `"std"` Cargo feature is enabled*, then: +/// - [`getrandom::Error`][Error] implements +/// [`std::error::Error`](https://doc.rust-lang.org/std/error/trait.Error.html) +/// - [`std::io::Error`](https://doc.rust-lang.org/std/io/struct.Error.html) implements +/// [`From`](https://doc.rust-lang.org/std/convert/trait.From.html). +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct Error(NonZeroU32); + +const fn internal_error(n: u16) -> Error { + // SAFETY: code > 0 as INTERNAL_START > 0 and adding n won't overflow a u32. + let code = Error::INTERNAL_START + (n as u32); + Error(unsafe { NonZeroU32::new_unchecked(code) }) +} + +impl Error { + /// This target/platform is not supported by `getrandom`. + pub const UNSUPPORTED: Error = internal_error(0); + /// The platform-specific `errno` returned a non-positive value. + pub const ERRNO_NOT_POSITIVE: Error = internal_error(1); + /// Call to iOS [`SecRandomCopyBytes`](https://developer.apple.com/documentation/security/1399291-secrandomcopybytes) failed. + pub const IOS_SEC_RANDOM: Error = internal_error(3); + /// Call to Windows [`RtlGenRandom`](https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom) failed. + pub const WINDOWS_RTL_GEN_RANDOM: Error = internal_error(4); + /// RDRAND instruction failed due to a hardware issue. + pub const FAILED_RDRAND: Error = internal_error(5); + /// RDRAND instruction unsupported on this target. + pub const NO_RDRAND: Error = internal_error(6); + /// The browser does not have support for `self.crypto`. + pub const WEB_CRYPTO: Error = internal_error(7); + /// The browser does not have support for `crypto.getRandomValues`. + pub const WEB_GET_RANDOM_VALUES: Error = internal_error(8); + /// On VxWorks, call to `randSecure` failed (random number generator is not yet initialized). + pub const VXWORKS_RAND_SECURE: Error = internal_error(11); + /// NodeJS does not have support for the `crypto` module. + pub const NODE_CRYPTO: Error = internal_error(12); + /// NodeJS does not have support for `crypto.randomFillSync`. + pub const NODE_RANDOM_FILL_SYNC: Error = internal_error(13); + + /// Codes below this point represent OS Errors (i.e. positive i32 values). + /// Codes at or above this point, but below [`Error::CUSTOM_START`] are + /// reserved for use by the `rand` and `getrandom` crates. + pub const INTERNAL_START: u32 = 1 << 31; + + /// Codes at or above this point can be used by users to define their own + /// custom errors. + pub const CUSTOM_START: u32 = (1 << 31) + (1 << 30); + + /// Extract the raw OS error code (if this error came from the OS) + /// + /// This method is identical to [`std::io::Error::raw_os_error()`][1], except + /// that it works in `no_std` contexts. If this method returns `None`, the + /// error value can still be formatted via the `Display` implementation. + /// + /// [1]: https://doc.rust-lang.org/std/io/struct.Error.html#method.raw_os_error + #[inline] + pub fn raw_os_error(self) -> Option { + if self.0.get() < Self::INTERNAL_START { + Some(self.0.get() as i32) + } else { + None + } + } + + /// Extract the bare error code. + /// + /// This code can either come from the underlying OS, or be a custom error. + /// Use [`Error::raw_os_error()`] to disambiguate. + #[inline] + pub const fn code(self) -> NonZeroU32 { + self.0 + } +} + +cfg_if! { + if #[cfg(unix)] { + fn os_err(errno: i32, buf: &mut [u8]) -> Option<&str> { + let buf_ptr = buf.as_mut_ptr() as *mut libc::c_char; + if unsafe { libc::strerror_r(errno, buf_ptr, buf.len()) } != 0 { + return None; + } + + // Take up to trailing null byte + let n = buf.len(); + let idx = buf.iter().position(|&b| b == 0).unwrap_or(n); + core::str::from_utf8(&buf[..idx]).ok() + } + } else if #[cfg(target_os = "wasi")] { + fn os_err(errno: i32, _buf: &mut [u8]) -> Option { + wasi::Error::from_raw_error(errno as _) + } + } else { + fn os_err(_errno: i32, _buf: &mut [u8]) -> Option<&str> { + None + } + } +} + +impl fmt::Debug for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut dbg = f.debug_struct("Error"); + if let Some(errno) = self.raw_os_error() { + dbg.field("os_error", &errno); + let mut buf = [0u8; 128]; + if let Some(err) = os_err(errno, &mut buf) { + dbg.field("description", &err); + } + } else if let Some(desc) = internal_desc(*self) { + dbg.field("internal_code", &self.0.get()); + dbg.field("description", &desc); + } else { + dbg.field("unknown_code", &self.0.get()); + } + dbg.finish() + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(errno) = self.raw_os_error() { + let mut buf = [0u8; 128]; + match os_err(errno, &mut buf) { + Some(err) => err.fmt(f), + None => write!(f, "OS Error: {}", errno), + } + } else if let Some(desc) = internal_desc(*self) { + f.write_str(desc) + } else { + write!(f, "Unknown Error: {}", self.0.get()) + } + } +} + +impl From for Error { + fn from(code: NonZeroU32) -> Self { + Self(code) + } +} + +fn internal_desc(error: Error) -> Option<&'static str> { + match error { + Error::UNSUPPORTED => Some("getrandom: this target is not supported"), + Error::ERRNO_NOT_POSITIVE => Some("errno: did not return a positive value"), + Error::IOS_SEC_RANDOM => Some("SecRandomCopyBytes: iOS Security framework failure"), + Error::WINDOWS_RTL_GEN_RANDOM => Some("RtlGenRandom: Windows system function failure"), + Error::FAILED_RDRAND => Some("RDRAND: failed multiple times: CPU issue likely"), + Error::NO_RDRAND => Some("RDRAND: instruction not supported"), + Error::WEB_CRYPTO => Some("Web API self.crypto is unavailable"), + Error::WEB_GET_RANDOM_VALUES => Some("Web API crypto.getRandomValues is unavailable"), + Error::VXWORKS_RAND_SECURE => Some("randSecure: VxWorks RNG module is not initialized"), + Error::NODE_CRYPTO => Some("Node.js crypto module is unavailable"), + Error::NODE_RANDOM_FILL_SYNC => Some("Node.js API crypto.randomFillSync is unavailable"), + _ => None, + } +} + +#[cfg(test)] +mod tests { + use super::Error; + use core::mem::size_of; + + #[test] + fn test_size() { + assert_eq!(size_of::(), 4); + assert_eq!(size_of::>(), 4); + } +} diff --git a/src/error_impls.rs b/src/error_impls.rs new file mode 100644 index 000000000..61f46d227 --- /dev/null +++ b/src/error_impls.rs @@ -0,0 +1,24 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +#![cfg_attr(docsrs, doc(cfg(feature = "std")))] +extern crate std; + +use crate::Error; +use core::convert::From; +use std::io; + +impl From for io::Error { + fn from(err: Error) -> Self { + match err.raw_os_error() { + Some(errno) => io::Error::from_raw_os_error(errno), + None => io::Error::new(io::ErrorKind::Other, err), + } + } +} + +impl std::error::Error for Error {} diff --git a/src/fuchsia.rs b/src/fuchsia.rs new file mode 100644 index 000000000..572ff5344 --- /dev/null +++ b/src/fuchsia.rs @@ -0,0 +1,20 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation for Fuchsia Zircon +use crate::Error; + +#[link(name = "zircon")] +extern "C" { + fn zx_cprng_draw(buffer: *mut u8, length: usize); +} + +pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { + unsafe { zx_cprng_draw(dest.as_mut_ptr(), dest.len()) } + Ok(()) +} diff --git a/src/ios.rs b/src/ios.rs new file mode 100644 index 000000000..5e38474fe --- /dev/null +++ b/src/ios.rs @@ -0,0 +1,26 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation for iOS +use crate::Error; +use core::{ffi::c_void, ptr::null}; + +#[link(name = "Security", kind = "framework")] +extern "C" { + fn SecRandomCopyBytes(rnd: *const c_void, count: usize, bytes: *mut u8) -> i32; +} + +pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { + // Apple's documentation guarantees kSecRandomDefault is a synonym for NULL. + let ret = unsafe { SecRandomCopyBytes(null(), dest.len(), dest.as_mut_ptr()) }; + if ret == -1 { + Err(Error::IOS_SEC_RANDOM) + } else { + Ok(()) + } +} diff --git a/src/js.rs b/src/js.rs new file mode 100644 index 000000000..d48c7d3a9 --- /dev/null +++ b/src/js.rs @@ -0,0 +1,106 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +use crate::Error; + +extern crate std; +use std::thread_local; + +use js_sys::Uint8Array; +use wasm_bindgen::prelude::*; + +// Maximum is 65536 bytes see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues +const BROWSER_CRYPTO_BUFFER_SIZE: usize = 256; + +enum RngSource { + Node(NodeCrypto), + Browser(BrowserCrypto, Uint8Array), +} + +// JsValues are always per-thread, so we initialize RngSource for each thread. +// See: https://github.com/rustwasm/wasm-bindgen/pull/955 +thread_local!( + static RNG_SOURCE: Result = getrandom_init(); +); + +pub(crate) fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { + RNG_SOURCE.with(|result| { + let source = result.as_ref().map_err(|&e| e)?; + + match source { + RngSource::Node(n) => { + if n.random_fill_sync(dest).is_err() { + return Err(Error::NODE_RANDOM_FILL_SYNC); + } + } + RngSource::Browser(crypto, buf) => { + // getRandomValues does not work with all types of WASM memory, + // so we initially write to browser memory to avoid exceptions. + for chunk in dest.chunks_mut(BROWSER_CRYPTO_BUFFER_SIZE) { + // The chunk can be smaller than buf's length, so we call to + // JS to create a smaller view of buf without allocation. + let sub_buf = buf.subarray(0, chunk.len() as u32); + + if crypto.get_random_values(&sub_buf).is_err() { + return Err(Error::WEB_GET_RANDOM_VALUES); + } + sub_buf.copy_to(chunk); + } + } + }; + Ok(()) + }) +} + +fn getrandom_init() -> Result { + if let Ok(self_) = Global::get_self() { + // If `self` is defined then we're in a browser somehow (main window + // or web worker). We get `self.crypto` (called `msCrypto` on IE), so we + // can call `crypto.getRandomValues`. If `crypto` isn't defined, we + // assume we're in an older web browser and the OS RNG isn't available. + + let crypto: BrowserCrypto = match (self_.crypto(), self_.ms_crypto()) { + (crypto, _) if !crypto.is_undefined() => crypto, + (_, crypto) if !crypto.is_undefined() => crypto, + _ => return Err(Error::WEB_CRYPTO), + }; + + let buf = Uint8Array::new_with_length(BROWSER_CRYPTO_BUFFER_SIZE as u32); + return Ok(RngSource::Browser(crypto, buf)); + } + + let crypto = MODULE.require("crypto").map_err(|_| Error::NODE_CRYPTO)?; + Ok(RngSource::Node(crypto)) +} + +#[wasm_bindgen] +extern "C" { + type Global; + #[wasm_bindgen(getter, catch, static_method_of = Global, js_class = self, js_name = self)] + fn get_self() -> Result; + + type Self_; + #[wasm_bindgen(method, getter, js_name = "msCrypto")] + fn ms_crypto(me: &Self_) -> BrowserCrypto; + #[wasm_bindgen(method, getter)] + fn crypto(me: &Self_) -> BrowserCrypto; + + type BrowserCrypto; + #[wasm_bindgen(method, js_name = getRandomValues, catch)] + fn get_random_values(me: &BrowserCrypto, buf: &Uint8Array) -> Result<(), JsValue>; + + #[wasm_bindgen(js_name = module)] + static MODULE: NodeModule; + + type NodeModule; + #[wasm_bindgen(method, catch)] + fn require(this: &NodeModule, s: &str) -> Result; + + type NodeCrypto; + #[wasm_bindgen(method, js_name = randomFillSync, catch)] + fn random_fill_sync(crypto: &NodeCrypto, buf: &mut [u8]) -> Result<(), JsValue>; +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 000000000..bcee9d0fa --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,247 @@ +// Copyright 2019 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Interface to the operating system's random number generator. +//! +//! # Supported targets +//! +//! | Target | Target Triple | Implementation +//! | ----------------- | ------------------ | -------------- +//! | Linux, Android | `*‑linux‑*` | [`getrandom`][1] system call if available, otherwise [`/dev/urandom`][2] after successfully polling `/dev/random` | +//! | Windows | `*‑windows‑*` | [`BCryptGenRandom`][3] | +//! | macOS | `*‑apple‑darwin` | [`getentropy()`][19] if available, otherwise [`/dev/random`][20] (identical to `/dev/urandom`) +//! | iOS | `*‑apple‑ios` | [`SecRandomCopyBytes`][4] +//! | FreeBSD | `*‑freebsd` | [`getrandom()`][21] if available, otherwise [`kern.arandom`][5] +//! | OpenBSD | `*‑openbsd` | [`getentropy`][6] +//! | NetBSD | `*‑netbsd` | [`kern.arandom`][7] +//! | Dragonfly BSD | `*‑dragonfly` | [`getrandom()`][22] if available, otherwise [`/dev/random`][8] +//! | Solaris, illumos | `*‑solaris`, `*‑illumos` | [`getrandom()`][9] if available, otherwise [`/dev/random`][10] +//! | Fuchsia OS | `*‑fuchsia` | [`cprng_draw`][11] +//! | Redox | `*‑redox` | [`rand:`][12] +//! | Haiku | `*‑haiku` | `/dev/random` (identical to `/dev/urandom`) +//! | SGX | `x86_64‑*‑sgx` | [RDRAND][18] +//! | VxWorks | `*‑wrs‑vxworks‑*` | `randABytes` after checking entropy pool initialization with `randSecure` +//! | Emscripten | `*‑emscripten` | `/dev/random` (identical to `/dev/urandom`) +//! | WASI | `wasm32‑wasi` | [`random_get`][17] +//! | Web Browser | `wasm32‑*‑unknown` | [`Crypto.getRandomValues()`][14], see [WebAssembly support][16] +//! | Node.js | `wasm32‑*‑unknown` | [`crypto.randomBytes`][15], see [WebAssembly support][16] +//! +//! There is no blanket implementation on `unix` targets that reads from +//! `/dev/urandom`. This ensures all supported targets are using the recommended +//! interface and respect maximum buffer sizes. +//! +//! Pull Requests that add support for new targets to `getrandom` are always welcome. +//! +//! ## Unsupported targets +//! +//! By default, `getrandom` will not compile on unsupported targets, but certain +//! features allow a user to select a "fallback" implementation if no supported +//! implementation exists. +//! +//! All of the below mechanisms only affect unsupported +//! targets. Supported targets will _always_ use their supported implementations. +//! This prevents a crate from overriding a secure source of randomness +//! (either accidentally or intentionally). +//! +//! ### RDRAND on x86 +//! +//! *If the `"rdrand"` Cargo feature is enabled*, `getrandom` will fallback to using +//! the [`RDRAND`][18] instruction to get randomness on `no_std` `x86`/`x86_64` +//! targets. This feature has no effect on other CPU architectures. +//! +//! ### WebAssembly support +//! +//! This crate fully supports the +//! [`wasm32-wasi`](https://github.com/CraneStation/wasi) and +//! [`wasm32-unknown-emscripten`](https://www.hellorust.com/setup/emscripten/) +//! targets. However, the `wasm32-unknown-unknown` target (i.e. the target used +//! by `wasm-pack`) is not automatically +//! supported since, from the target name alone, we cannot deduce which +//! JavaScript interface is in use (or if JavaScript is available at all). +//! +//! Instead, *if the `"js"` Cargo feature is enabled*, this crate will assume +//! that you are building for an environment containing JavaScript, and will +//! call the appropriate methods. Both web browser (main window and Web Workers) +//! and Node.js environments are supported, invoking the methods +//! [described above](#supported-targets) using the +//! [wasm-bindgen](https://github.com/rust-lang/rust-bindgen) toolchain. +//! +//! This feature has no effect on targets other than `wasm32-unknown-unknown`. +//! +//! ### Custom implementations +//! +//! The [`register_custom_getrandom!`] macro allows a user to mark their own +//! function as the backing implementation for [`getrandom`]. See the macro's +//! documentation for more information about writing and registering your own +//! custom implementations. +//! +//! Note that registering a custom implementation only has an effect on targets +//! that would otherwise not compile. Any supported targets (including those +//! using `"rdrand"` and `"js"` Cargo features) continue using their normal +//! implementations even if a function is registered. +//! +//! ### Indirect Dependencies +//! +//! If `getrandom` is not a direct dependency of your crate, you can still +//! enable any of the above fallback behaviors by enabling the relevant +//! feature in your root crate's `Cargo.toml`: +//! ```toml +//! [dependencies] +//! getrandom = { version = "0.2", features = ["js"] } +//! ``` +//! +//! ## Early boot +//! +//! Sometimes, early in the boot process, the OS has not collected enough +//! entropy to securely seed its RNG. This is especially common on virtual +//! machines, where standard "random" events are hard to come by. +//! +//! Some operating system interfaces always block until the RNG is securely +//! seeded. This can take anywhere from a few seconds to more than a minute. +//! A few (Linux, NetBSD and Solaris) offer a choice between blocking and +//! getting an error; in these cases, we always choose to block. +//! +//! On Linux (when the `getrandom` system call is not available), reading from +//! `/dev/urandom` never blocks, even when the OS hasn't collected enough +//! entropy yet. To avoid returning low-entropy bytes, we first poll +//! `/dev/random` and only switch to `/dev/urandom` once this has succeeded. +//! +//! ## Error handling +//! +//! We always choose failure over returning insecure "random" bytes. In general, +//! on supported platforms, failure is highly unlikely, though not impossible. +//! If an error does occur, then it is likely that it will occur on every call to +//! `getrandom`, hence after the first successful call one can be reasonably +//! confident that no errors will occur. +//! +//! [1]: http://man7.org/linux/man-pages/man2/getrandom.2.html +//! [2]: http://man7.org/linux/man-pages/man4/urandom.4.html +//! [3]: https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom +//! [4]: https://developer.apple.com/documentation/security/1399291-secrandomcopybytes?language=objc +//! [5]: https://www.freebsd.org/cgi/man.cgi?query=random&sektion=4 +//! [6]: https://man.openbsd.org/getentropy.2 +//! [7]: https://netbsd.gw.com/cgi-bin/man-cgi?sysctl+7+NetBSD-8.0 +//! [8]: https://leaf.dragonflybsd.org/cgi/web-man?command=random§ion=4 +//! [9]: https://docs.oracle.com/cd/E88353_01/html/E37841/getrandom-2.html +//! [10]: https://docs.oracle.com/cd/E86824_01/html/E54777/random-7d.html +//! [11]: https://fuchsia.dev/fuchsia-src/zircon/syscalls/cprng_draw +//! [12]: https://github.com/redox-os/randd/blob/master/src/main.rs +//! [14]: https://www.w3.org/TR/WebCryptoAPI/#Crypto-method-getRandomValues +//! [15]: https://nodejs.org/api/crypto.html#crypto_crypto_randombytes_size_callback +//! [16]: #webassembly-support +//! [17]: https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#-random_getbuf-pointeru8-buf_len-size---errno +//! [18]: https://software.intel.com/en-us/articles/intel-digital-random-number-generator-drng-software-implementation-guide +//! [19]: https://www.unix.com/man-page/mojave/2/getentropy/ +//! [20]: https://www.unix.com/man-page/mojave/4/random/ +//! [21]: https://www.freebsd.org/cgi/man.cgi?query=getrandom&manpath=FreeBSD+12.0-stable +//! [22]: https://leaf.dragonflybsd.org/cgi/web-man?command=getrandom + +#![doc( + html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk.png", + html_favicon_url = "https://www.rust-lang.org/favicon.ico", + html_root_url = "https://docs.rs/getrandom/0.2.2" +)] +#![no_std] +#![warn(rust_2018_idioms, unused_lifetimes, missing_docs)] +#![cfg_attr(docsrs, feature(doc_cfg))] + +#[macro_use] +extern crate cfg_if; + +mod error; +mod util; +// To prevent a breaking change when targets are added, we always export the +// register_custom_getrandom macro, so old Custom RNG crates continue to build. +#[cfg(feature = "custom")] +mod custom; +#[cfg(feature = "std")] +mod error_impls; + +pub use crate::error::Error; + +// System-specific implementations. +// +// These should all provide getrandom_inner with the same signature as getrandom. +cfg_if! { + if #[cfg(any(target_os = "emscripten", target_os = "haiku", + target_os = "redox"))] { + mod util_libc; + #[path = "use_file.rs"] mod imp; + } else if #[cfg(any(target_os = "android", target_os = "linux"))] { + mod util_libc; + mod use_file; + #[path = "linux_android.rs"] mod imp; + } else if #[cfg(any(target_os = "illumos", target_os = "solaris"))] { + mod util_libc; + mod use_file; + #[path = "solaris_illumos.rs"] mod imp; + } else if #[cfg(any(target_os = "freebsd", target_os = "netbsd"))] { + mod util_libc; + #[path = "bsd_arandom.rs"] mod imp; + } else if #[cfg(target_os = "dragonfly")] { + mod util_libc; + mod use_file; + #[path = "dragonfly.rs"] mod imp; + } else if #[cfg(target_os = "fuchsia")] { + #[path = "fuchsia.rs"] mod imp; + } else if #[cfg(target_os = "ios")] { + #[path = "ios.rs"] mod imp; + } else if #[cfg(target_os = "macos")] { + mod util_libc; + mod use_file; + #[path = "macos.rs"] mod imp; + } else if #[cfg(target_os = "openbsd")] { + mod util_libc; + #[path = "openbsd.rs"] mod imp; + } else if #[cfg(target_os = "wasi")] { + #[path = "wasi.rs"] mod imp; + } else if #[cfg(target_os = "vxworks")] { + mod util_libc; + #[path = "vxworks.rs"] mod imp; + } else if #[cfg(windows)] { + #[path = "windows.rs"] mod imp; + } else if #[cfg(all(target_arch = "x86_64", target_env = "sgx"))] { + #[path = "rdrand.rs"] mod imp; + } else if #[cfg(all(feature = "rdrand", + any(target_arch = "x86_64", target_arch = "x86")))] { + #[path = "rdrand.rs"] mod imp; + } else if #[cfg(all(feature = "js", + target_arch = "wasm32", target_os = "unknown"))] { + #[path = "js.rs"] mod imp; + } else if #[cfg(feature = "custom")] { + use custom as imp; + } else if #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] { + compile_error!("the wasm32-unknown-unknown target is not supported by \ + default, you may need to enable the \"js\" feature. \ + For more information see: \ + https://docs.rs/getrandom/#webassembly-support"); + } else { + compile_error!("target is not supported, for more information see: \ + https://docs.rs/getrandom/#unsupported-targets"); + } +} + +/// Fill `dest` with random bytes from the system's preferred random number +/// source. +/// +/// This function returns an error on any failure, including partial reads. We +/// make no guarantees regarding the contents of `dest` on error. If `dest` is +/// empty, `getrandom` immediately returns success, making no calls to the +/// underlying operating system. +/// +/// Blocking is possible, at least during early boot; see module documentation. +/// +/// In general, `getrandom` will be fast enough for interactive usage, though +/// significantly slower than a user-space CSPRNG; for the latter consider +/// [`rand::thread_rng`](https://docs.rs/rand/*/rand/fn.thread_rng.html). +pub fn getrandom(dest: &mut [u8]) -> Result<(), Error> { + if dest.is_empty() { + return Ok(()); + } + imp::getrandom_inner(dest) +} diff --git a/src/linux_android.rs b/src/linux_android.rs new file mode 100644 index 000000000..5508fdd06 --- /dev/null +++ b/src/linux_android.rs @@ -0,0 +1,46 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation for Linux / Android +use crate::{ + util::LazyBool, + util_libc::{last_os_error, sys_fill_exact}, + {use_file, Error}, +}; + +pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { + static HAS_GETRANDOM: LazyBool = LazyBool::new(); + if HAS_GETRANDOM.unsync_init(is_getrandom_available) { + sys_fill_exact(dest, |buf| unsafe { + getrandom(buf.as_mut_ptr() as *mut libc::c_void, buf.len(), 0) + }) + } else { + use_file::getrandom_inner(dest) + } +} + +fn is_getrandom_available() -> bool { + let res = unsafe { getrandom(core::ptr::null_mut(), 0, libc::GRND_NONBLOCK) }; + if res < 0 { + match last_os_error().raw_os_error() { + Some(libc::ENOSYS) => false, // No kernel support + Some(libc::EPERM) => false, // Blocked by seccomp + _ => true, + } + } else { + true + } +} + +unsafe fn getrandom( + buf: *mut libc::c_void, + buflen: libc::size_t, + flags: libc::c_uint, +) -> libc::ssize_t { + libc::syscall(libc::SYS_getrandom, buf, buflen, flags) as libc::ssize_t +} diff --git a/src/macos.rs b/src/macos.rs new file mode 100644 index 000000000..585a35abd --- /dev/null +++ b/src/macos.rs @@ -0,0 +1,35 @@ +// Copyright 2019 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation for macOS +use crate::{ + use_file, + util_libc::{last_os_error, Weak}, + Error, +}; +use core::mem; + +type GetEntropyFn = unsafe extern "C" fn(*mut u8, libc::size_t) -> libc::c_int; + +pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { + static GETENTROPY: Weak = unsafe { Weak::new("getentropy\0") }; + if let Some(fptr) = GETENTROPY.ptr() { + let func: GetEntropyFn = unsafe { mem::transmute(fptr) }; + for chunk in dest.chunks_mut(256) { + let ret = unsafe { func(chunk.as_mut_ptr(), chunk.len()) }; + if ret != 0 { + return Err(last_os_error()); + } + } + Ok(()) + } else { + // We fallback to reading from /dev/random instead of SecRandomCopyBytes + // to avoid high startup costs and linking the Security framework. + use_file::getrandom_inner(dest) + } +} diff --git a/src/openbsd.rs b/src/openbsd.rs new file mode 100644 index 000000000..c8d28b3d8 --- /dev/null +++ b/src/openbsd.rs @@ -0,0 +1,20 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation for OpenBSD +use crate::{util_libc::last_os_error, Error}; + +pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { + for chunk in dest.chunks_mut(256) { + let ret = unsafe { libc::getentropy(chunk.as_mut_ptr() as *mut libc::c_void, chunk.len()) }; + if ret == -1 { + return Err(last_os_error()); + } + } + Ok(()) +} diff --git a/src/rdrand.rs b/src/rdrand.rs new file mode 100644 index 000000000..1df21e5d9 --- /dev/null +++ b/src/rdrand.rs @@ -0,0 +1,97 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation for SGX using RDRAND instruction +use crate::Error; +use core::mem; + +cfg_if! { + if #[cfg(target_arch = "x86_64")] { + use core::arch::x86_64 as arch; + use arch::_rdrand64_step as rdrand_step; + } else if #[cfg(target_arch = "x86")] { + use core::arch::x86 as arch; + use arch::_rdrand32_step as rdrand_step; + } +} + +// Recommendation from "Intel® Digital Random Number Generator (DRNG) Software +// Implementation Guide" - Section 5.2.1 and "Intel® 64 and IA-32 Architectures +// Software Developer’s Manual" - Volume 1 - Section 7.3.17.1. +const RETRY_LIMIT: usize = 10; +const WORD_SIZE: usize = mem::size_of::(); + +#[target_feature(enable = "rdrand")] +unsafe fn rdrand() -> Result<[u8; WORD_SIZE], Error> { + for _ in 0..RETRY_LIMIT { + let mut el = mem::zeroed(); + if rdrand_step(&mut el) == 1 { + // AMD CPUs from families 14h to 16h (pre Ryzen) sometimes fail to + // set CF on bogus random data, so we check these values explicitly. + // See https://github.com/systemd/systemd/issues/11810#issuecomment-489727505 + // We perform this check regardless of target to guard against + // any implementation that incorrectly fails to set CF. + if el != 0 && el != !0 { + return Ok(el.to_ne_bytes()); + } + // Keep looping in case this was a false positive. + } + } + Err(Error::FAILED_RDRAND) +} + +// "rdrand" target feature requires "+rdrnd" flag, see https://github.com/rust-lang/rust/issues/49653. +#[cfg(all(target_env = "sgx", not(target_feature = "rdrand")))] +compile_error!( + "SGX targets require 'rdrand' target feature. Enable by using -C target-feature=+rdrnd." +); + +#[cfg(target_feature = "rdrand")] +fn is_rdrand_supported() -> bool { + true +} + +// TODO use is_x86_feature_detected!("rdrand") when that works in core. See: +// https://github.com/rust-lang-nursery/stdsimd/issues/464 +#[cfg(not(target_feature = "rdrand"))] +fn is_rdrand_supported() -> bool { + use crate::util::LazyBool; + + // SAFETY: All Rust x86 targets are new enough to have CPUID, and if CPUID + // is supported, CPUID leaf 1 is always supported. + const FLAG: u32 = 1 << 30; + static HAS_RDRAND: LazyBool = LazyBool::new(); + HAS_RDRAND.unsync_init(|| unsafe { (arch::__cpuid(1).ecx & FLAG) != 0 }) +} + +pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { + if !is_rdrand_supported() { + return Err(Error::NO_RDRAND); + } + + // SAFETY: After this point, rdrand is supported, so calling the rdrand + // functions is not undefined behavior. + unsafe { rdrand_exact(dest) } +} + +#[target_feature(enable = "rdrand")] +unsafe fn rdrand_exact(dest: &mut [u8]) -> Result<(), Error> { + // We use chunks_exact_mut instead of chunks_mut as it allows almost all + // calls to memcpy to be elided by the compiler. + let mut chunks = dest.chunks_exact_mut(WORD_SIZE); + for chunk in chunks.by_ref() { + chunk.copy_from_slice(&rdrand()?); + } + + let tail = chunks.into_remainder(); + let n = tail.len(); + if n > 0 { + tail.copy_from_slice(&rdrand()?[..n]); + } + Ok(()) +} diff --git a/src/solaris_illumos.rs b/src/solaris_illumos.rs new file mode 100644 index 000000000..2d1b767bb --- /dev/null +++ b/src/solaris_illumos.rs @@ -0,0 +1,47 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation for the Solaris family +//! +//! Read from `/dev/random`, with chunks of limited size (256 bytes). +//! `/dev/random` uses the Hash_DRBG with SHA512 algorithm from NIST SP 800-90A. +//! `/dev/urandom` uses the FIPS 186-2 algorithm, which is considered less +//! secure. We choose to read from `/dev/random`. +//! +//! Since Solaris 11.3 and mid-2015 illumos, the `getrandom` syscall is available. +//! To make sure we can compile on both Solaris and its derivatives, as well as +//! function, we check for the existence of getrandom(2) in libc by calling +//! libc::dlsym. +use crate::{ + use_file, + util_libc::{sys_fill_exact, Weak}, + Error, +}; +use core::mem; + +#[cfg(target_os = "illumos")] +type GetRandomFn = unsafe extern "C" fn(*mut u8, libc::size_t, libc::c_uint) -> libc::ssize_t; +#[cfg(target_os = "solaris")] +type GetRandomFn = unsafe extern "C" fn(*mut u8, libc::size_t, libc::c_uint) -> libc::c_int; + +pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { + static GETRANDOM: Weak = unsafe { Weak::new("getrandom\0") }; + if let Some(fptr) = GETRANDOM.ptr() { + let func: GetRandomFn = unsafe { mem::transmute(fptr) }; + // 256 bytes is the lowest common denominator across all the Solaris + // derived platforms for atomically obtaining random data. + for chunk in dest.chunks_mut(256) { + sys_fill_exact(chunk, |buf| unsafe { + func(buf.as_mut_ptr(), buf.len(), 0) as libc::ssize_t + })? + } + Ok(()) + } else { + use_file::getrandom_inner(dest) + } +} diff --git a/src/use_file.rs b/src/use_file.rs new file mode 100644 index 000000000..465c06971 --- /dev/null +++ b/src/use_file.rs @@ -0,0 +1,141 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementations that just need to read from a file +use crate::{ + util::LazyUsize, + util_libc::{open_readonly, sys_fill_exact}, + Error, +}; +use core::{ + cell::UnsafeCell, + sync::atomic::{AtomicUsize, Ordering::Relaxed}, +}; + +#[cfg(target_os = "redox")] +const FILE_PATH: &str = "rand:\0"; +#[cfg(any( + target_os = "dragonfly", + target_os = "emscripten", + target_os = "haiku", + target_os = "macos", + target_os = "solaris", + target_os = "illumos" +))] +const FILE_PATH: &str = "/dev/random\0"; +#[cfg(any(target_os = "android", target_os = "linux"))] +const FILE_PATH: &str = "/dev/urandom\0"; + +pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { + let fd = get_rng_fd()?; + let read = |buf: &mut [u8]| unsafe { libc::read(fd, buf.as_mut_ptr() as *mut _, buf.len()) }; + + if cfg!(target_os = "emscripten") { + // `Crypto.getRandomValues` documents `dest` should be at most 65536 bytes. + for chunk in dest.chunks_mut(65536) { + sys_fill_exact(chunk, read)?; + } + } else { + sys_fill_exact(dest, read)?; + } + Ok(()) +} + +// Returns the file descriptor for the device file used to retrieve random +// bytes. The file will be opened exactly once. All successful calls will +// return the same file descriptor. This file descriptor is never closed. +fn get_rng_fd() -> Result { + static FD: AtomicUsize = AtomicUsize::new(LazyUsize::UNINIT); + fn get_fd() -> Option { + match FD.load(Relaxed) { + LazyUsize::UNINIT => None, + val => Some(val as libc::c_int), + } + } + + // Use double-checked locking to avoid acquiring the lock if possible. + if let Some(fd) = get_fd() { + return Ok(fd); + } + + // SAFETY: We use the mutex only in this method, and we always unlock it + // before returning, making sure we don't violate the pthread_mutex_t API. + static MUTEX: Mutex = Mutex::new(); + unsafe { MUTEX.lock() }; + let _guard = DropGuard(|| unsafe { MUTEX.unlock() }); + + if let Some(fd) = get_fd() { + return Ok(fd); + } + + // On Linux, /dev/urandom might return insecure values. + #[cfg(any(target_os = "android", target_os = "linux"))] + wait_until_rng_ready()?; + + let fd = unsafe { open_readonly(FILE_PATH)? }; + // The fd always fits in a usize without conflicting with UNINIT. + debug_assert!(fd >= 0 && (fd as usize) < LazyUsize::UNINIT); + FD.store(fd as usize, Relaxed); + + Ok(fd) +} + +// Succeeds once /dev/urandom is safe to read from +#[cfg(any(target_os = "android", target_os = "linux"))] +fn wait_until_rng_ready() -> Result<(), Error> { + // Poll /dev/random to make sure it is ok to read from /dev/urandom. + let fd = unsafe { open_readonly("/dev/random\0")? }; + let mut pfd = libc::pollfd { + fd, + events: libc::POLLIN, + revents: 0, + }; + let _guard = DropGuard(|| unsafe { + libc::close(fd); + }); + + loop { + // A negative timeout means an infinite timeout. + let res = unsafe { libc::poll(&mut pfd, 1, -1) }; + if res >= 0 { + debug_assert_eq!(res, 1); // We only used one fd, and cannot timeout. + return Ok(()); + } + let err = crate::util_libc::last_os_error(); + match err.raw_os_error() { + Some(libc::EINTR) | Some(libc::EAGAIN) => continue, + _ => return Err(err), + } + } +} + +struct Mutex(UnsafeCell); + +impl Mutex { + const fn new() -> Self { + Self(UnsafeCell::new(libc::PTHREAD_MUTEX_INITIALIZER)) + } + unsafe fn lock(&self) { + let r = libc::pthread_mutex_lock(self.0.get()); + debug_assert_eq!(r, 0); + } + unsafe fn unlock(&self) { + let r = libc::pthread_mutex_unlock(self.0.get()); + debug_assert_eq!(r, 0); + } +} + +unsafe impl Sync for Mutex {} + +struct DropGuard(F); + +impl Drop for DropGuard { + fn drop(&mut self) { + self.0() + } +} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 000000000..06e23c28c --- /dev/null +++ b/src/util.rs @@ -0,0 +1,64 @@ +// Copyright 2019 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +#![allow(dead_code)] +use core::sync::atomic::{AtomicUsize, Ordering::Relaxed}; + +// This structure represents a lazily initialized static usize value. Useful +// when it is preferable to just rerun initialization instead of locking. +// Both unsync_init and sync_init will invoke an init() function until it +// succeeds, then return the cached value for future calls. +// +// Both methods support init() "failing". If the init() method returns UNINIT, +// that value will be returned as normal, but will not be cached. +// +// Users should only depend on the _value_ returned by init() functions. +// Specifically, for the following init() function: +// fn init() -> usize { +// a(); +// let v = b(); +// c(); +// v +// } +// the effects of c() or writes to shared memory will not necessarily be +// observed and additional synchronization methods with be needed. +pub struct LazyUsize(AtomicUsize); + +impl LazyUsize { + pub const fn new() -> Self { + Self(AtomicUsize::new(Self::UNINIT)) + } + + // The initialization is not completed. + pub const UNINIT: usize = usize::max_value(); + + // Runs the init() function at least once, returning the value of some run + // of init(). Multiple callers can run their init() functions in parallel. + // init() should always return the same value, if it succeeds. + pub fn unsync_init(&self, init: impl FnOnce() -> usize) -> usize { + // Relaxed ordering is fine, as we only have a single atomic variable. + let mut val = self.0.load(Relaxed); + if val == Self::UNINIT { + val = init(); + self.0.store(val, Relaxed); + } + val + } +} + +// Identical to LazyUsize except with bool instead of usize. +pub struct LazyBool(LazyUsize); + +impl LazyBool { + pub const fn new() -> Self { + Self(LazyUsize::new()) + } + + pub fn unsync_init(&self, init: impl FnOnce() -> bool) -> bool { + self.0.unsync_init(|| init() as usize) != 0 + } +} diff --git a/src/util_libc.rs b/src/util_libc.rs new file mode 100644 index 000000000..682360972 --- /dev/null +++ b/src/util_libc.rs @@ -0,0 +1,115 @@ +// Copyright 2019 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. +#![allow(dead_code)] +use crate::{util::LazyUsize, Error}; +use core::{num::NonZeroU32, ptr::NonNull}; + +cfg_if! { + if #[cfg(any(target_os = "netbsd", target_os = "openbsd", target_os = "android"))] { + use libc::__errno as errno_location; + } else if #[cfg(any(target_os = "linux", target_os = "emscripten", target_os = "redox"))] { + use libc::__errno_location as errno_location; + } else if #[cfg(any(target_os = "solaris", target_os = "illumos"))] { + use libc::___errno as errno_location; + } else if #[cfg(any(target_os = "macos", target_os = "freebsd"))] { + use libc::__error as errno_location; + } else if #[cfg(target_os = "haiku")] { + use libc::_errnop as errno_location; + } +} + +cfg_if! { + if #[cfg(target_os = "vxworks")] { + use libc::errnoGet as get_errno; + } else if #[cfg(target_os = "dragonfly")] { + // Until rust-lang/rust#29594 is stable, we cannot get the errno value + // on DragonFlyBSD. So we just return an out-of-range errno. + unsafe fn get_errno() -> libc::c_int { -1 } + } else { + unsafe fn get_errno() -> libc::c_int { *errno_location() } + } +} + +pub fn last_os_error() -> Error { + let errno = unsafe { get_errno() }; + if errno > 0 { + Error::from(NonZeroU32::new(errno as u32).unwrap()) + } else { + Error::ERRNO_NOT_POSITIVE + } +} + +// Fill a buffer by repeatedly invoking a system call. The `sys_fill` function: +// - should return -1 and set errno on failure +// - should return the number of bytes written on success +pub fn sys_fill_exact( + mut buf: &mut [u8], + sys_fill: impl Fn(&mut [u8]) -> libc::ssize_t, +) -> Result<(), Error> { + while !buf.is_empty() { + let res = sys_fill(buf); + if res < 0 { + let err = last_os_error(); + // We should try again if the call was interrupted. + if err.raw_os_error() != Some(libc::EINTR) { + return Err(err); + } + } else { + // We don't check for EOF (ret = 0) as the data we are reading + // should be an infinite stream of random bytes. + buf = &mut buf[(res as usize)..]; + } + } + Ok(()) +} + +// A "weak" binding to a C function that may or may not be present at runtime. +// Used for supporting newer OS features while still building on older systems. +// F must be a function pointer of type `unsafe extern "C" fn`. Based off of the +// weak! macro in libstd. +pub struct Weak { + name: &'static str, + addr: LazyUsize, +} + +impl Weak { + // Construct a binding to a C function with a given name. This function is + // unsafe because `name` _must_ be null terminated. + pub const unsafe fn new(name: &'static str) -> Self { + Self { + name, + addr: LazyUsize::new(), + } + } + + // Return a function pointer if present at runtime. Otherwise, return null. + pub fn ptr(&self) -> Option> { + let addr = self.addr.unsync_init(|| unsafe { + libc::dlsym(libc::RTLD_DEFAULT, self.name.as_ptr() as *const _) as usize + }); + NonNull::new(addr as *mut _) + } +} + +cfg_if! { + if #[cfg(any(target_os = "linux", target_os = "emscripten"))] { + use libc::open64 as open; + } else { + use libc::open; + } +} + +// SAFETY: path must be null terminated, FD must be manually closed. +pub unsafe fn open_readonly(path: &str) -> Result { + debug_assert_eq!(path.as_bytes().last(), Some(&0)); + let fd = open(path.as_ptr() as *const _, libc::O_RDONLY | libc::O_CLOEXEC); + if fd < 0 { + return Err(last_os_error()); + } + Ok(fd) +} diff --git a/src/vxworks.rs b/src/vxworks.rs new file mode 100644 index 000000000..6cb5d52fe --- /dev/null +++ b/src/vxworks.rs @@ -0,0 +1,34 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation for VxWorks +use crate::{util_libc::last_os_error, Error}; +use core::sync::atomic::{AtomicBool, Ordering::Relaxed}; + +pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { + static RNG_INIT: AtomicBool = AtomicBool::new(false); + while !RNG_INIT.load(Relaxed) { + let ret = unsafe { libc::randSecure() }; + if ret < 0 { + return Err(Error::VXWORKS_RAND_SECURE); + } else if ret > 0 { + RNG_INIT.store(true, Relaxed); + break; + } + unsafe { libc::usleep(10) }; + } + + // Prevent overflow of i32 + for chunk in dest.chunks_mut(i32::max_value() as usize) { + let ret = unsafe { libc::randABytes(chunk.as_mut_ptr(), chunk.len() as i32) }; + if ret != 0 { + return Err(last_os_error()); + } + } + Ok(()) +} diff --git a/src/wasi.rs b/src/wasi.rs new file mode 100644 index 000000000..4674f4396 --- /dev/null +++ b/src/wasi.rs @@ -0,0 +1,19 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation for WASI +use crate::Error; +use core::num::NonZeroU32; +use wasi::random_get; + +pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { + unsafe { random_get(dest.as_mut_ptr(), dest.len()) }.map_err(|e: wasi::Error| { + // convert wasi's Error into getrandom's NonZeroU32 error + NonZeroU32::new(e.raw_error() as u32).unwrap().into() + }) +} diff --git a/src/windows.rs b/src/windows.rs new file mode 100644 index 000000000..643badd07 --- /dev/null +++ b/src/windows.rs @@ -0,0 +1,48 @@ +// Copyright 2018 Developers of the Rand project. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use crate::Error; +use core::{ffi::c_void, num::NonZeroU32, ptr}; + +const BCRYPT_USE_SYSTEM_PREFERRED_RNG: u32 = 0x00000002; + +#[link(name = "bcrypt")] +extern "system" { + fn BCryptGenRandom( + hAlgorithm: *mut c_void, + pBuffer: *mut u8, + cbBuffer: u32, + dwFlags: u32, + ) -> u32; +} + +pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { + // Prevent overflow of u32 + for chunk in dest.chunks_mut(u32::max_value() as usize) { + let ret = unsafe { + BCryptGenRandom( + ptr::null_mut(), + chunk.as_mut_ptr(), + chunk.len() as u32, + BCRYPT_USE_SYSTEM_PREFERRED_RNG, + ) + }; + // NTSTATUS codes use the two highest bits for severity status. + if ret >> 30 == 0b11 { + // We zeroize the highest bit, so the error code will reside + // inside the range designated for OS codes. + let code = ret ^ (1 << 31); + // SAFETY: the second highest bit is always equal to one, + // so it's impossible to get zero. Unfortunately the type + // system does not have a way to express this yet. + let code = unsafe { NonZeroU32::new_unchecked(code) }; + return Err(Error::from(code)); + } + } + Ok(()) +} diff --git a/tests/common/mod.rs b/tests/common/mod.rs new file mode 100644 index 000000000..006f230d7 --- /dev/null +++ b/tests/common/mod.rs @@ -0,0 +1,66 @@ +use super::getrandom_impl; + +#[cfg(all(target_arch = "wasm32", target_os = "unknown"))] +use wasm_bindgen_test::wasm_bindgen_test as test; + +#[cfg(feature = "test-in-browser")] +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + +#[test] +fn test_zero() { + // Test that APIs are happy with zero-length requests + getrandom_impl(&mut [0u8; 0]).unwrap(); +} + +#[test] +fn test_diff() { + let mut v1 = [0u8; 1000]; + getrandom_impl(&mut v1).unwrap(); + + let mut v2 = [0u8; 1000]; + getrandom_impl(&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); +} + +#[test] +fn test_huge() { + let mut huge = [0u8; 100_000]; + getrandom_impl(&mut huge).unwrap(); +} + +// On WASM, the thread API always fails/panics +#[cfg(not(target_arch = "wasm32"))] +#[test] +fn test_multithreading() { + extern crate std; + use std::{sync::mpsc::channel, thread, vec}; + + let mut txs = vec![]; + for _ in 0..20 { + let (tx, rx) = channel(); + txs.push(tx); + + thread::spawn(move || { + // wait until all the tasks are ready to go. + rx.recv().unwrap(); + let mut v = [0u8; 1000]; + + for _ in 0..100 { + getrandom_impl(&mut v).unwrap(); + thread::yield_now(); + } + }); + } + + // start all the tasks + for tx in txs.iter() { + tx.send(()).unwrap(); + } +} diff --git a/tests/custom.rs b/tests/custom.rs new file mode 100644 index 000000000..62eae1d66 --- /dev/null +++ b/tests/custom.rs @@ -0,0 +1,50 @@ +// Test that a custom handler works on wasm32-unknown-unknown +#![cfg(all( + target_arch = "wasm32", + target_os = "unknown", + feature = "custom", + not(feature = "js") +))] + +use wasm_bindgen_test::wasm_bindgen_test as test; +#[cfg(feature = "test-in-browser")] +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + +use core::{ + num::NonZeroU32, + sync::atomic::{AtomicU8, Ordering}, +}; +use getrandom::{getrandom, register_custom_getrandom, Error}; + +fn len7_err() -> Error { + NonZeroU32::new(Error::INTERNAL_START + 7).unwrap().into() +} + +fn super_insecure_rng(buf: &mut [u8]) -> Result<(), Error> { + // Length 7 buffers return a custom error + if buf.len() == 7 { + return Err(len7_err()); + } + // Otherwise, increment an atomic counter + static COUNTER: AtomicU8 = AtomicU8::new(0); + for b in buf { + *b = COUNTER.fetch_add(1, Ordering::Relaxed); + } + Ok(()) +} + +register_custom_getrandom!(super_insecure_rng); + +#[test] +fn custom_rng_output() { + let mut buf = [0u8; 4]; + assert_eq!(getrandom(&mut buf), Ok(())); + assert_eq!(buf, [0, 1, 2, 3]); + assert_eq!(getrandom(&mut buf), Ok(())); + assert_eq!(buf, [4, 5, 6, 7]); +} + +#[test] +fn rng_err_output() { + assert_eq!(getrandom(&mut [0; 7]), Err(len7_err())); +} diff --git a/tests/normal.rs b/tests/normal.rs new file mode 100644 index 000000000..5fff13b38 --- /dev/null +++ b/tests/normal.rs @@ -0,0 +1,11 @@ +// Don't test on custom wasm32-unknown-unknown +#![cfg(not(all( + target_arch = "wasm32", + target_os = "unknown", + feature = "custom", + not(feature = "js") +)))] + +// Use the normal getrandom implementation on this architecture. +use getrandom::getrandom as getrandom_impl; +mod common; diff --git a/tests/rdrand.rs b/tests/rdrand.rs new file mode 100644 index 000000000..4ff85c47f --- /dev/null +++ b/tests/rdrand.rs @@ -0,0 +1,15 @@ +// We only test the RDRAND-based RNG source on supported architectures. +#![cfg(any(target_arch = "x86_64", target_arch = "x86"))] + +// rdrand.rs expects to be part of the getrandom main crate, so we need these +// additional imports to get rdrand.rs to compile. +use getrandom::Error; +#[macro_use] +extern crate cfg_if; +#[path = "../src/rdrand.rs"] +mod rdrand; +#[path = "../src/util.rs"] +mod util; + +use rdrand::getrandom_inner as getrandom_impl; +mod common;