Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

provide new generators: urandom and getentropy #250

Merged
merged 21 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
0a67fe4
provide getrandom-into from mirage-crypto-rng-unix
hannesm Oct 28, 2024
9eff386
add pfortuna to the benchmarks
hannesm Oct 29, 2024
53da5fb
add urandom-channel to the benchmarks
hannesm Oct 29, 2024
f6bf346
Add /dev/urandom and getentropy RNG generators
hannesm Nov 27, 2024
7a60519
using in_channel requires ocaml 4.14
hannesm Nov 28, 2024
49693c9
Update rng/mirage_crypto_rng.mli
hannesm Nov 29, 2024
03d3e92
Update rng/unix/mirage_crypto_rng_unix.mli
hannesm Nov 29, 2024
3abb84a
bench/speed: use Urandom and Getentropy generators
hannesm Dec 6, 2024
0917c36
close the /dev/urandom file descriptor at exit
hannesm Dec 6, 2024
36562df
mirage-crypto-rng-unix: more documentation
hannesm Dec 6, 2024
5c885b0
adjust CI systems: no more 4.13
hannesm Dec 6, 2024
53b1898
test_entropy: fail again, but disable on arm64
hannesm Dec 6, 2024
b5262d1
revert putting miou-unix directly into bench/speed to allow ocaml 4 t…
hannesm Dec 6, 2024
9d30a60
github CI: increase versions
hannesm Dec 6, 2024
5f5f101
Update rng/unix/urandom.ml
hannesm Jan 7, 2025
aa4627a
Update rng/unix/getentropy.ml
hannesm Jan 7, 2025
520732b
use size_t for raw_getrandom
hannesm Jan 7, 2025
fafabab
Rng.generate_into: check that off and n are positive
hannesm Jan 7, 2025
5919c92
Mirage_crypto_rng.generate_into: adjust docstring; Generator.generate…
hannesm Jan 7, 2025
74e4e67
Mirage_crypto_rng_unix: use_default defaults to getentropy
hannesm Jan 7, 2025
4caa18e
Update rng/unix/mc_getrandom_stubs.c
hannesm Jan 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .cirrus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ freebsd_instance:
freebsd_task:
env:
matrix:
- OCAML_VERSION: 4.13.1
- OCAML_VERSION: 4.14.2

pkg_install_script: pkg install -y ocaml-opam gmp gmake pkgconf bash
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ jobs:
strategy:
fail-fast: false
matrix:
ocaml-version: ["4.14.2", "4.13.1"]
ocaml-version: ["4.14.2"]
operating-system: [macos-latest, ubuntu-latest]

runs-on: ${{ matrix.operating-system }}

steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Use OCaml ${{ matrix.ocaml-version }}
uses: ocaml/setup-ocaml@v2
uses: ocaml/setup-ocaml@v3
with:
opam-local-packages: |
*.opam
Expand All @@ -42,17 +42,17 @@ jobs:
strategy:
fail-fast: false
matrix:
ocaml-version: ["5.0.0"]
ocaml-version: ["5.2.1"]
operating-system: [macos-latest, ubuntu-latest]

runs-on: ${{ matrix.operating-system }}

steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Use OCaml ${{ matrix.ocaml-version }}
uses: ocaml/setup-ocaml@v2
uses: ocaml/setup-ocaml@v3
with:
opam-local-packages: |
mirage-crypto.opam
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@ jobs:
strategy:
fail-fast: false
matrix:
ocaml-version: ["4.14.2", "4.13.1"]
ocaml-version: ["4.14.2"]
operating-system: [windows-latest]

runs-on: ${{ matrix.operating-system }}

steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Use OCaml ${{ matrix.ocaml-compiler }}
uses: ocaml/setup-ocaml@v2
uses: ocaml/setup-ocaml@v3
with:
opam-repositories: |
opam-repository-mingw: https://github.com/ocaml-opam/opam-repository-mingw.git#sunset
Expand Down
18 changes: 14 additions & 4 deletions bench/speed.ml
Original file line number Diff line number Diff line change
Expand Up @@ -480,12 +480,22 @@ let benchmarks = [
throughput_into name (fun dst cs -> DES.ECB.unsafe_encrypt_into ~key cs ~src_off:0 dst ~dst_off:0 (String.length cs))) ;

bm "fortuna" (fun name ->
let open Mirage_crypto_rng.Fortuna in
let g = create () in
reseed ~g "abcd" ;
Mirage_crypto_rng_unix.initialize (module Mirage_crypto_rng.Fortuna);
throughput name (fun buf ->
let buf = Bytes.unsafe_of_string buf in
generate_into ~g buf ~off:0 (Bytes.length buf))) ;
Mirage_crypto_rng.generate_into buf ~off:0 (Bytes.length buf))) ;

bm "getentropy" (fun name ->
Mirage_crypto_rng_unix.use_getentropy ();
throughput name (fun buf ->
let buf = Bytes.unsafe_of_string buf in
Mirage_crypto_rng.generate_into buf ~off:0 (Bytes.length buf))) ;

bm "urandom" (fun name ->
Mirage_crypto_rng_unix.use_dev_urandom ();
throughput name (fun buf ->
let buf = Bytes.unsafe_of_string buf in
Mirage_crypto_rng.generate_into buf ~off:0 (Bytes.length buf))) ;
]

let help () =
Expand Down
2 changes: 1 addition & 1 deletion mirage-crypto-rng.opam
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ build: [ ["dune" "subst"] {dev}
["dune" "runtest" "-p" name "-j" jobs] {with-test} ]

depends: [
"ocaml" {>= "4.13.0"}
"ocaml" {>= "4.14.0"}
"dune" {>= "2.7"}
"dune-configurator" {>= "2.0.0"}
"duration"
Expand Down
24 changes: 22 additions & 2 deletions rng/mirage_crypto_rng.mli
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,19 @@

(** {b TL;DR} Don't forget to seed; don't maintain your own [g].

For common operations on Unix (independent of your asynchronous task
library, you can use /dev/urandom or getentropy(3) (actually getrandom(3) on
Linux, getentropy() on macOS and BSD systems, BCryptGenRandom on Windows).

Please ensure to call [Mirage_crypto_rng_unix.use_default], or
[Mirage_crypto_rng_unix.use_dev_urandom] (if you only want to use
/dev/urandom), or [Mirage_crypto_rng_unix.use_getentropy] (if you only want
to use getentropy).

For fine-grained control (doing entropy harvesting, etc.), please continue
reading the documentation below. {b Please be aware that the feeding of Fortuna
and producing random numbers is not thread-safe} (it is on Miou_unix via Pfortuna).

The RNGs here are merely the deterministic part of a full random number
generation suite. For proper operation, they need to be seeded with a
high-quality entropy source.
Expand Down Expand Up @@ -156,10 +169,13 @@ module type Generator = sig
(** Create a new, unseeded {{!g}g}. *)

val generate_into : g:g -> bytes -> off:int -> int -> unit
[@@alert unsafe "Does not do bounds checks. Use Mirage_crypto_rng.generate_into instead."]
(** [generate_into ~g buf ~off n] produces [n] uniformly distributed random
bytes into [buf] at offset [off], updating the state of [g].

@raise Invalid_argument if buffer is too small (it must be: Bytes.length buf - off >= n)
Assumes that [buf] is at least [off + n] bytes long. Also assumes that
[off] and [n] are positive integers. Caution: do not use in your
application, use [Mirage_crypto_rng.generate_into] instead.
*)

val reseed : g:g -> string -> unit
Expand Down Expand Up @@ -233,7 +249,11 @@ val generate_into : ?g:g -> bytes -> ?off:int -> int -> unit
(** [generate_into ~g buf ~off len] invokes
{{!Generator.generate_into}generate_into} on [g] or
{{!generator}default generator}. The random data is put into [buf] starting
at [off] (defaults to 0) with [len] bytes. *)
at [off] (defaults to 0) with [len] bytes.

@raise Invalid_argument if buffer is too small (it must be: [Bytes.length
buf - off >= n]) or [off] or [n] are negative.
*)

val generate : ?g:g -> int -> string
(** Invoke {!generate_into} on [g] or {{!generator}default generator} and a
Expand Down
15 changes: 13 additions & 2 deletions rng/rng.ml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ exception Unseeded_generator
exception No_default_generator

let setup_rng =
"\nTo initialize the RNG with a default generator, and set up entropy \
"\nPlease setup your default random number generator. On Unix, the best \
path is to call [Mirage_crypto_rng_unix.use_default ()].\
\nBut you can use Fortuna (or any other RNG) and setup the seeding \
(done by default in MirageOS): \
\n\
\nTo initialize the RNG with a default generator, and set up entropy \
collection and periodic reseeding as a background task, do the \
following:\
\n If you are using MirageOS, use the random device in config.ml: \
Expand Down Expand Up @@ -37,6 +42,7 @@ module type Generator = sig
val block : int
val create : ?time:(unit -> int64) -> unit -> g
val generate_into : g:g -> bytes -> off:int -> int -> unit
[@@alert unsafe "Does not do bounds checks. Use Mirage_crypto_rng.generate_into instead."]
val reseed : g:g -> string -> unit
val accumulate : g:g -> source -> [`Acc of string -> unit]
val seeded : g:g -> bool
Expand Down Expand Up @@ -68,9 +74,14 @@ let get = function Some g -> g | None -> default_generator ()
let generate_into ?(g = default_generator ()) b ?(off = 0) n =
let Generator (g, _, m) = g in
let module M = (val m) in
if off < 0 || n < 0 then
invalid_arg ("negative offset " ^ string_of_int off ^ " or length " ^
string_of_int n);
if Bytes.length b - off < n then
invalid_arg "buffer too short";
M.generate_into ~g b ~off n
begin[@alert "-unsafe"]
M.generate_into ~g b ~off n
end

let generate ?g n =
let data = Bytes.create n in
Expand Down
5 changes: 3 additions & 2 deletions rng/unix/dune
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@
(library
(name mirage_crypto_rng_unix)
(public_name mirage-crypto-rng.unix)
(modules mirage_crypto_rng_unix)
(libraries mirage-crypto-rng unix logs)
(modules mirage_crypto_rng_unix urandom getentropy)
(libraries mirage-crypto-rng unix logs threads.posix)
(foreign_stubs
(language c)
(include_dirs ../../src/native)
(names mc_getrandom_stubs))
(c_library_flags
(:include rng_c_flags.sexp)))
Expand Down
25 changes: 25 additions & 0 deletions rng/unix/getentropy.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

external getrandom_buf : bytes -> int -> int -> unit = "mc_getrandom" [@@noalloc]

type g = unit

(* The maximum value for length is GETENTROPY_MAX for `getentropy`: https://pubs.opengroup.org/onlinepubs/9799919799/functions/getentropy.html
The minimum acceptable value for GETENTROPY_MAX is 256 https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/limits.h.html

The actual implementation may be one of `getrandom`, `getentropy`, or `BCryptGenRandom`, and will internally limit the maximum bytes read in one go and loop as needed if more bytes are requested and we get a short read.
*)
let block = 256

let create ?time:_ () = ()

let generate_into ~g:_ buf ~off len =
getrandom_buf buf off len

let reseed ~g:_ _data = ()

let accumulate ~g:_ _source =
`Acc (fun _data -> ())

let seeded ~g:_ = true

let pools = 0
14 changes: 8 additions & 6 deletions rng/unix/mc_getrandom_stubs.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
# include <unistd.h>
#endif

#include "mirage_crypto.h"

#include <caml/mlvalues.h>
#include <caml/memory.h>
#include <caml/unixsupport.h>
Expand All @@ -19,7 +21,7 @@
# define getrandom(buf, len, flags) getrandom((buf), (len), (flags))
# endif

void raw_getrandom (uint8_t *data, uint32_t len) {
void raw_getrandom (uint8_t *data, size_t len) {
size_t off = 0;
ssize_t r = 0;
while (off < len) {
Expand All @@ -39,9 +41,9 @@ void raw_getrandom (uint8_t *data, uint32_t len) {
#endif
#include <sys/param.h>

void raw_getrandom (uint8_t *data, uint32_t len) {
void raw_getrandom (uint8_t *data, size_t len) {
size_t rlen = 0;
for (uint32_t i = 0; i <= len; i += 256) {
for (size_t i = 0; i <= len; i += 256) {
rlen = MIN(256, len - i);
if (getentropy(data + i, rlen) == -1) uerror("getentropy", Nothing);
}
Expand All @@ -61,7 +63,7 @@ void raw_getrandom (uint8_t *data, uint32_t len) {
#include <ntstatus.h>
#include <bcrypt.h>

void raw_getrandom(uint8_t *data, uint32_t len) {
void raw_getrandom(uint8_t *data, size_t len) {
NTSTATUS Status;
Status = BCryptGenRandom(NULL, data, len, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
if (Status != STATUS_SUCCESS)
Expand All @@ -72,7 +74,7 @@ void raw_getrandom(uint8_t *data, uint32_t len) {
#error "Retrieving random data not supported on this platform"
#endif

CAMLprim value mc_getrandom (value buf, value len) {
raw_getrandom(Bytes_val(buf), Int_val(len));
CAMLprim value mc_getrandom (value buf, value off, value len) {
raw_getrandom(_bp_uint8_off(buf, off), Long_val(len));
return Val_unit;
}
21 changes: 19 additions & 2 deletions rng/unix/mirage_crypto_rng_unix.ml
Original file line number Diff line number Diff line change
@@ -1,13 +1,30 @@
open Mirage_crypto_rng

module Urandom = Urandom

module Getentropy = Getentropy

let use_dev_urandom () =
let g = create (module Urandom) in
set_default_generator g

let use_getentropy () =
let g = create (module Getentropy) in
set_default_generator g

let use_default () = use_getentropy ()

let src = Logs.Src.create "mirage-crypto-rng.unix" ~doc:"Mirage crypto RNG Unix"
module Log = (val Logs.src_log src : Logs.LOG)

external getrandom_buf : bytes -> int -> unit = "mc_getrandom" [@@noalloc]
external getrandom_buf : bytes -> int -> int -> unit = "mc_getrandom" [@@noalloc]

let getrandom_into buf ~off ~len =
getrandom_buf buf off len

let getrandom size =
let buf = Bytes.create size in
getrandom_buf buf size;
getrandom_into buf ~off:0 ~len:size;
Bytes.unsafe_to_string buf

let getrandom_init i =
Expand Down
22 changes: 22 additions & 0 deletions rng/unix/mirage_crypto_rng_unix.mli
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,25 @@ val initialize : ?g:'a -> 'a Mirage_crypto_rng.generator -> unit

(** [getrandom size] returns a buffer of [size] filled with random bytes. *)
val getrandom : int -> string

(** A generator that opens /dev/urandom and reads from that file descriptor
data whenever random data is needed. The file descriptor is closed in
[at_exit]. *)
module Urandom : Mirage_crypto_rng.Generator

(** A generator using [getrandom(3)] on Linux, [getentropy(3)] on BSD and macOS,
and [BCryptGenRandom()] on Windows. *)
module Getentropy : Mirage_crypto_rng.Generator

(** [use_default ()] initializes the RNG [Mirage_crypto_rng.default_generator]
with a sensible default, at the moment using [Getentropy]. *)
val use_default : unit -> unit

(** [use_dev_random ()] initializes the RNG
[Mirage_crypto_rng.default_generator] with the [Urandom] generator. This
raises an exception if "/dev/urandom" cannot be opened. *)
val use_dev_urandom : unit -> unit

(** [use_getentropy ()] initializes the RNG [Mirage_crypto_rng.default_generator]
with the [Getentropy] generator. *)
val use_getentropy : unit -> unit
29 changes: 29 additions & 0 deletions rng/unix/urandom.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@

type g = In_channel.t * Mutex.t

(* The OCaml runtime always reads at least IO_BUFFER_SIZE from an input channel, which is currently 64 KiB *)
let block = 65536

let create ?time:_ () =
let ic = In_channel.open_bin "/dev/urandom"
and mutex = Mutex.create ()
in
at_exit (fun () -> In_channel.close ic);
(ic, mutex)

let generate_into ~g:(ic, m) buf ~off len =
let finally () = Mutex.unlock m in
Mutex.lock m;
Fun.protect ~finally (fun () ->
match In_channel.really_input ic buf off len with
| None -> failwith "couldn't read enough bytes from /dev/urandom"
| Some () -> ())

let reseed ~g:_ _data = ()

let accumulate ~g:_ _source =
`Acc (fun _data -> ())

let seeded ~g:_ = true

let pools = 0
4 changes: 3 additions & 1 deletion tests/dune
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@
(name test_entropy)
(modules test_entropy)
(package mirage-crypto-rng)
(libraries mirage-crypto-rng ohex))
(libraries mirage-crypto-rng ohex)
(enabled_if (<> %{architecture} "arm64")))
; see https://github.com/mirage/mirage-crypto/issues/216

(test
(name test_ec)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_ec.ml
Original file line number Diff line number Diff line change
Expand Up @@ -861,7 +861,7 @@ df f8 a0 4f d3 dd 1d f0 07 78 3a 2f 29 d6 61 61
| Error _ -> Alcotest.fail "regression failed"

let () =
Mirage_crypto_rng_unix.initialize (module Mirage_crypto_rng.Fortuna);
Mirage_crypto_rng_unix.use_default ();
Alcotest.run "EC"
[
("P256 Key exchange", key_exchange);
Expand Down
2 changes: 1 addition & 1 deletion tests/test_pk_runner.ml
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ let suite =
]

let () =
Mirage_crypto_rng_unix.initialize (module Mirage_crypto_rng.Fortuna);
Mirage_crypto_rng_unix.use_default ();
run_test_tt_main suite
Loading
Loading