Skip to content

Commit a5fec37

Browse files
dinosaurehannesm
andauthored
Add an implementation of mirage-crypto-rng-miou to initialize the RNG with Miou (#227)
* Add an implementation of mirage-crypto-rng-miou to initialize the RNG with Miou * Update GitHub actions and CirrusCI * Use the last version of Miou * Prefer to follow the user's control flow than the cancellation control flow to set correctly global variables * Use Miou_backoff instead of Backoff and avoid a conflict with the backoff package * Upgrade the PR with miou.0.2.0 and delete the pin-depends * Provide a separate executable to bench pfortuna with miou * Add a comment about the goal of Pfortuna * note to sync fortuna and pfortuna * fix opam-lint check: add digestif to dependencies of mirage-crypto-rng-miou-unix * comment out bench/miou in dune to avoid CI build failures since (package mirage-crypto-rng-miou-unix) is not supported without (public_names ..) in dune, there's no easy alternative. Marking it (optional) still results in failures with OCaml-CI * no need for (modes native), this is known as ocaml/dune#9979 --------- Co-authored-by: Hannes Mehnert <hannes@mehnert.org>
1 parent d4604c3 commit a5fec37

15 files changed

+481
-6
lines changed

.github/workflows/test.yml

+7-5
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ jobs:
2424
opam-local-packages: |
2525
*.opam
2626
!mirage-crypto-rng-eio.opam
27+
!mirage-crypto-rng-miou-unix.opam
2728
ocaml-compiler: ${{ matrix.ocaml-version }}
2829

2930
- name: Install dependencies
@@ -35,8 +36,8 @@ jobs:
3536
- name: Test
3637
run: opam exec -- dune runtest -p mirage-crypto,mirage-crypto-rng,mirage-crypto-rng-lwt,mirage-crypto-rng-mirage,mirage-crypto-pk,mirage-crypto-ec,mirage-crypto-rng-async
3738

38-
build-test-unix-eio:
39-
name : Unix (eio)
39+
build-test-ocaml-5:
40+
name : Tests with OCaml 5
4041

4142
strategy:
4243
fail-fast: false
@@ -57,13 +58,14 @@ jobs:
5758
mirage-crypto.opam
5859
mirage-crypto-rng.opam
5960
mirage-crypto-rng-eio.opam
61+
mirage-crypto-rng-miou-unix.opam
6062
ocaml-compiler: ${{ matrix.ocaml-version }}
6163

6264
- name: Install dependencies
63-
run: opam install --deps-only -t mirage-crypto mirage-crypto-rng mirage-crypto-rng-eio
65+
run: opam install --deps-only -t mirage-crypto mirage-crypto-rng mirage-crypto-rng-eio mirage-crypto-rng-miou-unix
6466

6567
- name: Build
66-
run: opam exec -- dune build -p mirage-crypto,mirage-crypto-rng,mirage-crypto-rng-eio
68+
run: opam exec -- dune build -p mirage-crypto,mirage-crypto-rng,mirage-crypto-rng-eio,mirage-crypto-rng-miou-unix
6769

6870
- name: Test
69-
run: opam exec -- dune runtest -p mirage-crypto,mirage-crypto-rng,mirage-crypto-rng-eio
71+
run: opam exec -- dune runtest -p mirage-crypto,mirage-crypto-rng,mirage-crypto-rng-eio,mirage-crypto-rng-miou-unix

.github/workflows/windows.yml

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ jobs:
2828
*.opam
2929
!mirage-crypto-rng-async.opam
3030
!mirage-crypto-rng-eio.opam
31+
!mirage-crypto-rng-miou-unix.opam
3132
ocaml-compiler: ${{ matrix.ocaml-version }}
3233

3334
- name: Install dependencies

bench/dune

+7
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,10 @@
33
(modules speed)
44
(libraries mirage-crypto mirage-crypto-rng mirage-crypto-rng.unix
55
mirage-crypto-pk mirage-crypto-ec))
6+
7+
; marking as "(optional)" leads to OCaml-CI failures
8+
; marking with "(package mirage-crypto-rng-miou-unix)" only has an effect with a "public_name"
9+
;(executables
10+
; (names miou)
11+
; (modules miou)
12+
; (libraries mirage-crypto-rng-miou-unix))

bench/miou.ml

+91
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
open Mirage_crypto
2+
3+
module Time = struct
4+
5+
let time ~n f a =
6+
let t1 = Sys.time () in
7+
for _ = 1 to n do ignore (f a) done ;
8+
let t2 = Sys.time () in
9+
(t2 -. t1)
10+
11+
let warmup () =
12+
let x = ref 0 in
13+
let rec go start =
14+
if Sys.time () -. start < 1. then begin
15+
for i = 0 to 10000 do x := !x + i done ;
16+
go start
17+
end in
18+
go (Sys.time ())
19+
20+
end
21+
22+
let burn_period = 2.0
23+
24+
let sizes = [16; 64; 256; 1024; 8192]
25+
(* let sizes = [16] *)
26+
27+
let burn f n =
28+
let buf = Mirage_crypto_rng.generate n in
29+
let (t1, i1) =
30+
let rec loop it =
31+
let t = Time.time ~n:it f buf in
32+
if t > 0.2 then (t, it) else loop (it * 10) in
33+
loop 10 in
34+
let iters = int_of_float (float i1 *. burn_period /. t1) in
35+
let time = Time.time ~n:iters f buf in
36+
(iters, time, float (n * iters) /. time)
37+
38+
let mb = 1024. *. 1024.
39+
40+
let throughput title f =
41+
Printf.printf "\n* [%s]\n%!" title ;
42+
sizes |> List.iter @@ fun size ->
43+
Gc.full_major () ;
44+
let (iters, time, bw) = burn f size in
45+
Printf.printf " % 5d: %04f MB/s (%d iters in %.03f s)\n%!"
46+
size (bw /. mb) iters time
47+
48+
let bm name f = (name, fun () -> f name)
49+
50+
let benchmarks = [
51+
bm "pfortuna" (fun name ->
52+
let open Mirage_crypto_rng_miou_unix.Pfortuna in
53+
Miou_unix.run ~domains:2 @@ fun () ->
54+
let rng = Mirage_crypto_rng_miou_unix.(initialize (module Pfortuna)) in
55+
let g = create () in
56+
reseed ~g "abcd" ;
57+
throughput name (fun buf ->
58+
let buf = Bytes.unsafe_of_string buf in
59+
generate_into ~g buf ~off:0 (Bytes.length buf));
60+
Mirage_crypto_rng_miou_unix.kill rng) ;
61+
]
62+
63+
let help () =
64+
Printf.printf "available benchmarks:\n ";
65+
List.iter (fun (n, _) -> Printf.printf "%s " n) benchmarks ;
66+
Printf.printf "\n%!"
67+
68+
let runv fs =
69+
Format.printf "accel: %a\n%!"
70+
(fun ppf -> List.iter @@ fun x ->
71+
Format.fprintf ppf "%s " @@
72+
match x with `XOR -> "XOR" | `AES -> "AES" | `GHASH -> "GHASH")
73+
accelerated;
74+
Time.warmup () ;
75+
List.iter (fun f -> f ()) fs
76+
77+
78+
let () =
79+
let seed = "abcd" in
80+
let g = Mirage_crypto_rng.(create ~seed (module Fortuna)) in
81+
Mirage_crypto_rng.set_default_generator g;
82+
match Array.to_list Sys.argv with
83+
| _::(_::_ as args) -> begin
84+
try
85+
let fs =
86+
args |> List.map @@ fun n ->
87+
snd (benchmarks |> List.find @@ fun (n1, _) -> n = n1) in
88+
runv fs
89+
with Not_found -> help ()
90+
end
91+
| _ -> help ()

mirage-crypto-rng-miou-unix.opam

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
opam-version: "2.0"
2+
homepage: "https://github.com/mirage/mirage-crypto"
3+
dev-repo: "git+https://github.com/mirage/mirage-crypto.git"
4+
bug-reports: "https://github.com/mirage/mirage-crypto/issues"
5+
doc: "https://mirage.github.io/mirage-crypto/doc"
6+
authors: ["Romain Calascibetta <romain.calascibetta@gmail.com>" ]
7+
maintainer: "Romain Calascibetta <romain.calascibetta@gmail.com>"
8+
license: "ISC"
9+
synopsis: "Feed the entropy source in an miou.unix-friendly way"
10+
11+
build: [ ["dune" "subst"] {dev}
12+
["dune" "build" "-p" name "-j" jobs ]
13+
["dune" "runtest" "-p" name "-j" jobs] {with-test} ]
14+
15+
depends: [
16+
"ocaml" {>= "5.0.0"}
17+
"dune" {>= "2.7"}
18+
"miou" {>= "0.2.0"}
19+
"logs"
20+
"mirage-crypto-rng" {=version}
21+
"duration"
22+
"mtime"
23+
"digestif" {>= "1.2.0"}
24+
"ohex" {with-test & >= "0.2.0"}
25+
]
26+
description: """
27+
Mirage-crypto-rng-miou-unix feeds the entropy source for Mirage_crypto_rng-based
28+
random number generator implementations, in an miou.unix-friendly way.
29+
"""

rng/fortuna.ml

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
(* NOTE: when modifying this file, please also check whether
2+
rng/miou/pfortuna.ml needs to be updated. *)
3+
14
open Mirage_crypto
25
open Mirage_crypto.Uncommon
36

rng/miou/dune

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
(library
2+
(name mirage_crypto_rng_miou_unix)
3+
(public_name mirage-crypto-rng-miou-unix)
4+
(libraries miou miou.unix mirage-crypto mirage-crypto-rng mirage-crypto-rng.unix digestif duration mtime.clock.os logs)
5+
(modules mirage_crypto_rng_miou_unix pfortuna))
+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
open Mirage_crypto_rng
2+
3+
module Pfortuna = Pfortuna
4+
5+
type _ Effect.t += Spawn : (unit -> unit) -> unit Effect.t
6+
external reraise : exn -> 'a = "%reraise"
7+
8+
let periodic fn delta =
9+
let rec one () =
10+
fn ();
11+
Miou_unix.sleep (Duration.to_f delta);
12+
one () in
13+
Effect.perform (Spawn one)
14+
15+
let getrandom delta source =
16+
let fn () =
17+
let per_pool = 8 in
18+
let size = per_pool * pools None in
19+
let random = Mirage_crypto_rng_unix.getrandom size in
20+
let idx = ref 0 in
21+
let fn () = incr idx; String.sub random (per_pool * (pred !idx)) per_pool in
22+
Entropy.feed_pools None source fn in
23+
periodic fn delta
24+
25+
let getrandom_init i =
26+
let data = Mirage_crypto_rng_unix.getrandom 128 in
27+
Entropy.header i data
28+
29+
let rdrand delta =
30+
match Entropy.cpu_rng with
31+
| Error `Not_supported -> ()
32+
| Ok cpu_rng -> periodic (cpu_rng None) delta
33+
34+
let running = Atomic.make false
35+
36+
let switch fn =
37+
let orphans = Miou.orphans () in
38+
let open Effect.Deep in
39+
let retc = Fun.id in
40+
let exnc = reraise in
41+
let effc : type c. c Effect.t -> ((c, 'r) continuation -> 'r) option
42+
= function
43+
| Spawn fn ->
44+
ignore (Miou.async ~orphans fn);
45+
Some (fun k -> continue k ())
46+
| _ -> None in
47+
match_with fn orphans { retc; exnc; effc }
48+
49+
let default_generator_already_set =
50+
"Mirage_crypto_rng.default_generator has already \
51+
been set (but not via Mirage_crypto_rng_miou). Please check \
52+
that this is intentional"
53+
54+
let miou_generator_already_launched =
55+
"Mirage_crypto_rng_miou.initialize has already been launched \
56+
and a task is already seeding the RNG."
57+
58+
type rng = unit Miou.t
59+
60+
let rec compare_and_set ?(backoff= Miou_backoff.default) t a b =
61+
if Atomic.compare_and_set t a b = false
62+
then compare_and_set ~backoff:(Miou_backoff.once backoff) t a b
63+
64+
let rec clean_up sleep orphans = match Miou.care orphans with
65+
| Some None | None -> Miou_unix.sleep (Duration.to_f sleep); clean_up sleep orphans
66+
| Some (Some prm) -> Miou.await_exn prm; clean_up sleep orphans
67+
68+
let call_if_domain_available fn =
69+
let available = Miou.Domain.available () in
70+
let current = (Stdlib.Domain.self () :> int) in
71+
if current = 0 && available > 0
72+
|| current <> 0 && available > 1
73+
then Miou.call fn
74+
else Miou.async fn
75+
76+
let initialize (type a) ?g ?(sleep= Duration.of_sec 1) (rng : a generator) =
77+
if Atomic.compare_and_set running false true
78+
then begin
79+
let seed =
80+
let init = Entropy.[ bootstrap; whirlwind_bootstrap; bootstrap; getrandom_init ] in
81+
List.mapi (fun i fn -> fn i) init |> String.concat "" in
82+
let () =
83+
try let _ = default_generator () in
84+
Logs.warn (fun m -> m "%s" default_generator_already_set)
85+
with No_default_generator -> () in
86+
let rng = create ?g ~seed ~time:Mtime_clock.elapsed_ns rng in
87+
set_default_generator rng;
88+
call_if_domain_available @@ fun () -> switch @@ fun orphans ->
89+
rdrand sleep;
90+
let source = Entropy.register_source "getrandom" in
91+
getrandom (Int64.mul sleep 10L) source;
92+
clean_up sleep orphans
93+
end else invalid_arg miou_generator_already_launched
94+
95+
let kill prm =
96+
Miou.cancel prm;
97+
compare_and_set running true false;
98+
unset_default_generator ()
+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
(** {b RNG} seeding on {b Miou_unix}.
2+
3+
This module initializes a RNG with [getrandom()], and CPU RNG. On BSD system
4+
(FreeBSD, OpenBSD, MacOS) [getentropy()] is used instead of [getrandom()].
5+
On Windows 10 or higher, [BCryptGenRandom()] is used with the default RNG.
6+
Windows 8 or lower are not supported by this library.
7+
*)
8+
9+
module Pfortuna : Mirage_crypto_rng.Generator
10+
(** {b Pfortuna}, a {b domain-safe} CSPRNG
11+
{{: https://www.schneier.com/fortuna.html} proposed} by Schneier. *)
12+
13+
type rng
14+
(** Type of tasks seeding the RNG. *)
15+
16+
val initialize : ?g:'a -> ?sleep:int64 -> 'a Mirage_crypto_rng.generator -> rng
17+
(** [initialize ?g ?sleep (module Generator)] will allow the RNG to operate in a
18+
returned task. This task periodically launches sub-tasks that seed the
19+
engine (using [getrandom()], [getentropy()] or [BCryptGenRandom()] depending
20+
on the system). These sub-tasks must be cleaned periodically (in seconds)
21+
according to the [sleep] parameter given (defaults to 1 second).
22+
23+
The user must then {!val:kill} the returned task at the end of the program
24+
to be sure to clean everything. Otherwise, Miou will complain with the
25+
exception [Still_has_children].
26+
27+
We strongly recommend using {!module:Pfortuna} as an RNG engine rather than
28+
{!module:Mirage_crypto_rng.Fortuna}. The engine is launched in parallel with
29+
the other tasks if at least one domain is available. To ensure that there is
30+
no compromise in the values generated by a {i data-race}, [Pfortuna] is an
31+
{b domain-safe} implementation of Fortuna.
32+
33+
The user cannot make any subsequent calls to [initialize]. In other words,
34+
you can only initialise a single {!type:rng} task. You must {!val:kill} the
35+
returned {!type:rng} if you want to re-initialise the RNG.
36+
37+
A basic usage of [mirage-crypto-rng-miou-unix] is:
38+
{[
39+
let () = Miou_unix.run @@ fun () ->
40+
let rng = Mirage_crypto_rng_miou_unix.(initialize (module Pfortuna)) in
41+
let str = Mirage_crypto_rng.generate 16 in
42+
Format.printf "random: %S\n%!" str;
43+
Mirage_crypto_rng_miou_unix.kill rng
44+
]} *)
45+
46+
val kill : rng -> unit
47+
(** [kill rng] terminates the {i background} task which seeds the RNG. *)

0 commit comments

Comments
 (0)