Skip to content

Commit e76ae80

Browse files
committed
Add an implementation of mirage-crypto-rng-miou to initialize the RNG with Miou
1 parent 38bde3a commit e76ae80

8 files changed

+285
-0
lines changed

mirage-crypto-rng-miou.opam

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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-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"
19+
"logs"
20+
"mirage-crypto-rng" {=version}
21+
"duration"
22+
"mtime"
23+
"ohex" {with-test & >= "0.2.0"}
24+
]
25+
description: """
26+
Mirage-crypto-rng-miou feeds the entropy source for Mirage_crypto_rng-based
27+
random number generator implementations, in an miou-friendly way.
28+
"""

rng/miou/dune

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
(library
2+
(name mirage_crypto_rng_miou)
3+
(public_name mirage-crypto-rng-miou)
4+
(libraries miou miou.unix miou.backoff mirage-crypto mirage-crypto-rng mirage-crypto-rng.unix digestif duration mtime.clock.os logs)
5+
(modules mirage_crypto_rng_miou pfortuna))

rng/miou/mirage_crypto_rng_miou.ml

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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.call_cc ~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+
type rng = unit Miou.t
55+
56+
let rec compare_and_set ?(backoff= Backoff.default) t a b =
57+
if Atomic.compare_and_set t a b = false
58+
then compare_and_set ~backoff:(Backoff.once backoff) t a b
59+
60+
let rec clean_up sleep orphans = match Miou.care orphans with
61+
| Some None | None -> Miou_unix.sleep (Duration.to_f sleep); clean_up sleep orphans
62+
| Some (Some prm) -> Miou.await_exn prm; clean_up sleep orphans
63+
64+
let initialize (type a) ?g ?(sleep= Duration.of_sec 1) (rng : a generator) =
65+
if Atomic.compare_and_set running false true
66+
then begin
67+
let seed =
68+
let init = Entropy.[ bootstrap; whirlwind_bootstrap; bootstrap; getrandom_init ] in
69+
List.mapi (fun i fn -> fn i) init |> String.concat "" in
70+
let rng = create ?g ~seed ~time:Mtime_clock.elapsed_ns rng in
71+
set_default_generator rng;
72+
Miou.call @@ fun () ->
73+
let finally () = compare_and_set running true false in
74+
Fun.protect ~finally @@ fun () -> switch @@ fun orphans ->
75+
let () =
76+
try let _ = default_generator () in
77+
Logs.warn (fun m -> m "%s" default_generator_already_set)
78+
with No_default_generator -> () in
79+
rdrand sleep;
80+
let source = Entropy.register_source "getrandom" in
81+
getrandom (Int64.mul sleep 10L) source;
82+
clean_up sleep orphans
83+
end else invalid_arg ""
84+
85+
let kill prm = Miou.cancel prm

rng/miou/pfortuna.ml

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
open Mirage_crypto
2+
open Mirage_crypto.Uncommon
3+
4+
module SHAd256 = struct
5+
open Digestif
6+
type ctx = SHA256.ctx
7+
let empty = SHA256.empty
8+
let get t = SHA256.(get t |> to_raw_string |> digest_string |> to_raw_string)
9+
let digesti i = SHA256.(digesti_string i |> to_raw_string |> digest_string |> to_raw_string)
10+
let feedi = SHA256.feedi_string
11+
end
12+
13+
let block = 16
14+
15+
(* the minimal amount of bytes in a pool to trigger a reseed *)
16+
let min_pool_size = 64
17+
(* the minimal duration between two reseeds *)
18+
let min_time_duration = 1_000_000_000L
19+
(* number of pools *)
20+
let pools = 32
21+
22+
type t =
23+
{ ctr : AES.CTR.ctr
24+
; secret : string
25+
; key : AES.CTR.key
26+
; pools : SHAd256.ctx array
27+
; pool0_size : int
28+
; reseed_count : int
29+
; last_reseed : int64
30+
; time : (unit -> int64) option
31+
}
32+
33+
type g = Miou.Mutex.t * t ref
34+
35+
let update (m, g) fn = Miou.Mutex.protect m @@ fun () -> g := fn !g
36+
let get (m, g) fn = Miou.Mutex.protect m @@ fun () -> fn !g
37+
38+
let create ?time () =
39+
let secret = String.make 32 '\000' in
40+
let m = Miou.Mutex.create () in
41+
let t =
42+
{ ctr= (0L, 0L); secret; key= AES.CTR.of_secret secret
43+
; pools= Array.make pools SHAd256.empty
44+
; pool0_size= 0
45+
; reseed_count= 0
46+
; last_reseed= 0L
47+
; time } in
48+
(m, { contents= t })
49+
50+
let seeded ~t =
51+
let lo, hi = t.ctr in
52+
not (Int64.equal lo 0L && Int64.equal hi 0L)
53+
54+
let set_key ~t secret =
55+
{ t with secret; key= AES.CTR.of_secret secret }
56+
57+
let reseedi ~t iter =
58+
let t = set_key ~t (SHAd256.digesti (fun fn -> fn t.secret; iter fn)) in
59+
{ t with ctr= AES.CTR.add_ctr t.ctr 1L }
60+
61+
let iter1 a f = f a
62+
let reseed ~t cs = reseedi ~t (iter1 cs)
63+
64+
let generate_rekey ~t buf ~off len =
65+
let b = len // block* 2 in
66+
let n = b * block in
67+
let r = AES.CTR.stream ~key:t.key ~ctr:t.ctr n in
68+
Bytes.unsafe_blit_string r 0 buf off len;
69+
let r2 = String.sub r (n - 32) 32 in
70+
let t = set_key ~t r2 in
71+
{ t with ctr= AES.CTR.add_ctr t.ctr (Int64.of_int b) }
72+
73+
let add_pool_entropy t =
74+
if t.pool0_size > min_pool_size then
75+
let should_reseed, now = match t.time with
76+
| None -> true, 0L
77+
| Some fn ->
78+
let now = fn () in
79+
Int64.(sub now t.last_reseed > min_time_duration), now in
80+
if should_reseed then begin
81+
let t = { t with reseed_count= t.reseed_count + 1
82+
; last_reseed= now
83+
; pool0_size= 0 } in
84+
reseedi ~t @@ fun add ->
85+
for i = 0 to pools - 1 do
86+
if t.reseed_count land ((1 lsl i) - 1) = 0
87+
then (SHAd256.get t.pools.(i) |> add; t.pools.(i) <- SHAd256.empty)
88+
done
89+
end else t else t
90+
91+
let generate_into ~t buf ~off len =
92+
let t = add_pool_entropy t in
93+
if not (seeded ~t) then raise Mirage_crypto_rng.Unseeded_generator;
94+
let rec chunk t off = function
95+
| i when i <= 0 -> t
96+
| n ->
97+
let n' = imin n 0x10000 in
98+
let t = generate_rekey ~t buf ~off n' in
99+
chunk t (off + n') (n - n') in
100+
chunk t off len
101+
102+
let add ~t source ~pool data =
103+
let buf = Bytes.create 2
104+
and pool = pool land (pools - 1)
105+
and source = Mirage_crypto_rng.Entropy.id source land 0xff in
106+
Bytes.set_uint8 buf 0 source;
107+
Bytes.set_uint8 buf 1 (String.length data);
108+
t.pools.(pool) <- SHAd256.feedi t.pools.(pool) (iter2 (Bytes.unsafe_to_string buf) data);
109+
if pool = 0 then { t with pool0_size= t.pool0_size + String.length data } else t
110+
111+
let accumulate ~g source =
112+
let pool = ref 0 in
113+
`Acc (fun buf ->
114+
update g @@ fun t ->
115+
let t = add ~t source ~pool:!pool buf in
116+
incr pool; t)
117+
118+
let reseed ~g cs = update g @@ fun t -> reseed ~t cs
119+
let generate_into ~g buf ~off len = update g @@ fun t -> generate_into ~t buf ~off len
120+
let seeded ~g = get g @@ fun t -> seeded ~t

rng/miou/pfortuna.mli

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include Mirage_crypto_rng.Generator

tests/dune

+6
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,9 @@
6565
(modules test_eio_rng test_eio_entropy_collection)
6666
(libraries mirage-crypto-rng-eio duration eio_main ohex)
6767
(package mirage-crypto-rng-eio))
68+
69+
(tests
70+
(names test_miou_rng test_miou_entropy_collection)
71+
(modules test_miou_rng test_miou_entropy_collection)
72+
(libraries mirage-crypto-rng-miou duration ohex)
73+
(package mirage-crypto-rng-miou))

tests/test_miou_entropy_collection.ml

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
module Printing_rng = struct
2+
type g = unit
3+
4+
let block = 16
5+
let create ?time:_ () = ()
6+
let generate_into ~g:_ _buf ~off:_ _len = assert false
7+
let seeded ~g:_ = true
8+
let pools = 1
9+
10+
let reseed ~g:_ data =
11+
Format.printf "reseeding:@.%a@.%!" (Ohex.pp_hexdump ()) data
12+
13+
let accumulate ~g:_ source =
14+
let print data =
15+
Format.printf "accumulate: (src: %a) %a@.%!"
16+
Mirage_crypto_rng.Entropy.pp_source source Ohex.pp data
17+
in
18+
`Acc print
19+
end
20+
21+
let () =
22+
Miou_unix.run @@ fun () ->
23+
let rng = Mirage_crypto_rng_miou.initialize (module Printing_rng) in
24+
Format.printf "entropy sources: %a@,%!"
25+
(fun ppf -> List.iter (fun x ->
26+
Mirage_crypto_rng.Entropy.pp_source ppf x;
27+
Format.pp_print_space ppf ()))
28+
(Mirage_crypto_rng.Entropy.sources ());
29+
let sleep = Duration.(of_sec 2 |> to_f) in
30+
Miou_unix.sleep sleep;
31+
Mirage_crypto_rng_miou.kill rng

tests/test_miou_rng.ml

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
let () = Miou_unix.run @@ fun () ->
2+
let rng = Mirage_crypto_rng_miou.initialize (module Mirage_crypto_rng_miou.Pfortuna) in
3+
let random_num = Mirage_crypto_rng.generate 32 in
4+
assert (String.length random_num = 32);
5+
Printf.printf "32 bit random number: %S\n%!" random_num;
6+
let random_num = Mirage_crypto_rng.generate 16 in
7+
assert (String.length random_num = 16);
8+
Printf.printf "16 bit random number: %S\n%!" random_num;
9+
Mirage_crypto_rng_miou.kill rng

0 commit comments

Comments
 (0)