Skip to content

Commit

Permalink
Add an implementation of mirage-crypto-rng-miou to initialize the RNG…
Browse files Browse the repository at this point in the history
… with Miou
  • Loading branch information
dinosaure committed Apr 24, 2024
1 parent 38bde3a commit e76ae80
Show file tree
Hide file tree
Showing 8 changed files with 285 additions and 0 deletions.
28 changes: 28 additions & 0 deletions mirage-crypto-rng-miou.opam
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
opam-version: "2.0"
homepage: "https://github.com/mirage/mirage-crypto"
dev-repo: "git+https://github.com/mirage/mirage-crypto.git"
bug-reports: "https://github.com/mirage/mirage-crypto/issues"
doc: "https://mirage.github.io/mirage-crypto/doc"
authors: ["Romain Calascibetta <romain.calascibetta@gmail.com>" ]
maintainer: "Romain Calascibetta <romain.calascibetta@gmail.com>"
license: "ISC"
synopsis: "Feed the entropy source in an miou-friendly way"

build: [ ["dune" "subst"] {dev}
["dune" "build" "-p" name "-j" jobs ]
["dune" "runtest" "-p" name "-j" jobs] {with-test} ]

depends: [
"ocaml" {>= "5.0.0"}
"dune" {>= "2.7"}
"miou"
"logs"
"mirage-crypto-rng" {=version}
"duration"
"mtime"
"ohex" {with-test & >= "0.2.0"}
]
description: """
Mirage-crypto-rng-miou feeds the entropy source for Mirage_crypto_rng-based
random number generator implementations, in an miou-friendly way.
"""
5 changes: 5 additions & 0 deletions rng/miou/dune
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
(library
(name mirage_crypto_rng_miou)
(public_name mirage-crypto-rng-miou)
(libraries miou miou.unix miou.backoff mirage-crypto mirage-crypto-rng mirage-crypto-rng.unix digestif duration mtime.clock.os logs)
(modules mirage_crypto_rng_miou pfortuna))
85 changes: 85 additions & 0 deletions rng/miou/mirage_crypto_rng_miou.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
open Mirage_crypto_rng

module Pfortuna = Pfortuna

type _ Effect.t += Spawn : (unit -> unit) -> unit Effect.t
external reraise : exn -> 'a = "%reraise"

let periodic fn delta =
let rec one () =
fn ();
Miou_unix.sleep (Duration.to_f delta);
one () in
Effect.perform (Spawn one)

let getrandom delta source =
let fn () =
let per_pool = 8 in
let size = per_pool * pools None in
let random = Mirage_crypto_rng_unix.getrandom size in
let idx = ref 0 in
let fn () = incr idx; String.sub random (per_pool * (pred !idx)) per_pool in
Entropy.feed_pools None source fn in
periodic fn delta

let getrandom_init i =
let data = Mirage_crypto_rng_unix.getrandom 128 in
Entropy.header i data

let rdrand delta =
match Entropy.cpu_rng with
| Error `Not_supported -> ()
| Ok cpu_rng -> periodic (cpu_rng None) delta

let running = Atomic.make false

let switch fn =
let orphans = Miou.orphans () in
let open Effect.Deep in
let retc = Fun.id in
let exnc = reraise in
let effc : type c. c Effect.t -> ((c, 'r) continuation -> 'r) option
= function
| Spawn fn ->
ignore (Miou.call_cc ~orphans fn);
Some (fun k -> continue k ())
| _ -> None in
match_with fn orphans { retc; exnc; effc }

let default_generator_already_set =
"Mirage_crypto_rng.default_generator has already \
been set (but not via Mirage_crypto_rng_miou). Please check \
that this is intentional"

type rng = unit Miou.t

let rec compare_and_set ?(backoff= Backoff.default) t a b =
if Atomic.compare_and_set t a b = false
then compare_and_set ~backoff:(Backoff.once backoff) t a b

let rec clean_up sleep orphans = match Miou.care orphans with
| Some None | None -> Miou_unix.sleep (Duration.to_f sleep); clean_up sleep orphans
| Some (Some prm) -> Miou.await_exn prm; clean_up sleep orphans

let initialize (type a) ?g ?(sleep= Duration.of_sec 1) (rng : a generator) =
if Atomic.compare_and_set running false true
then begin
let seed =
let init = Entropy.[ bootstrap; whirlwind_bootstrap; bootstrap; getrandom_init ] in
List.mapi (fun i fn -> fn i) init |> String.concat "" in
let rng = create ?g ~seed ~time:Mtime_clock.elapsed_ns rng in
set_default_generator rng;
Miou.call @@ fun () ->
let finally () = compare_and_set running true false in
Fun.protect ~finally @@ fun () -> switch @@ fun orphans ->
let () =
try let _ = default_generator () in
Logs.warn (fun m -> m "%s" default_generator_already_set)
with No_default_generator -> () in
rdrand sleep;
let source = Entropy.register_source "getrandom" in
getrandom (Int64.mul sleep 10L) source;
clean_up sleep orphans
end else invalid_arg ""

let kill prm = Miou.cancel prm
120 changes: 120 additions & 0 deletions rng/miou/pfortuna.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
open Mirage_crypto
open Mirage_crypto.Uncommon

module SHAd256 = struct
open Digestif
type ctx = SHA256.ctx
let empty = SHA256.empty
let get t = SHA256.(get t |> to_raw_string |> digest_string |> to_raw_string)
let digesti i = SHA256.(digesti_string i |> to_raw_string |> digest_string |> to_raw_string)
let feedi = SHA256.feedi_string
end

let block = 16

(* the minimal amount of bytes in a pool to trigger a reseed *)
let min_pool_size = 64
(* the minimal duration between two reseeds *)
let min_time_duration = 1_000_000_000L
(* number of pools *)
let pools = 32

type t =
{ ctr : AES.CTR.ctr
; secret : string
; key : AES.CTR.key
; pools : SHAd256.ctx array
; pool0_size : int
; reseed_count : int
; last_reseed : int64
; time : (unit -> int64) option
}

type g = Miou.Mutex.t * t ref

let update (m, g) fn = Miou.Mutex.protect m @@ fun () -> g := fn !g
let get (m, g) fn = Miou.Mutex.protect m @@ fun () -> fn !g

let create ?time () =
let secret = String.make 32 '\000' in
let m = Miou.Mutex.create () in
let t =
{ ctr= (0L, 0L); secret; key= AES.CTR.of_secret secret
; pools= Array.make pools SHAd256.empty
; pool0_size= 0
; reseed_count= 0
; last_reseed= 0L
; time } in
(m, { contents= t })

let seeded ~t =
let lo, hi = t.ctr in
not (Int64.equal lo 0L && Int64.equal hi 0L)

let set_key ~t secret =
{ t with secret; key= AES.CTR.of_secret secret }

let reseedi ~t iter =
let t = set_key ~t (SHAd256.digesti (fun fn -> fn t.secret; iter fn)) in
{ t with ctr= AES.CTR.add_ctr t.ctr 1L }

let iter1 a f = f a
let reseed ~t cs = reseedi ~t (iter1 cs)

let generate_rekey ~t buf ~off len =
let b = len // block* 2 in
let n = b * block in
let r = AES.CTR.stream ~key:t.key ~ctr:t.ctr n in
Bytes.unsafe_blit_string r 0 buf off len;
let r2 = String.sub r (n - 32) 32 in
let t = set_key ~t r2 in
{ t with ctr= AES.CTR.add_ctr t.ctr (Int64.of_int b) }

let add_pool_entropy t =
if t.pool0_size > min_pool_size then
let should_reseed, now = match t.time with
| None -> true, 0L
| Some fn ->
let now = fn () in
Int64.(sub now t.last_reseed > min_time_duration), now in
if should_reseed then begin
let t = { t with reseed_count= t.reseed_count + 1
; last_reseed= now
; pool0_size= 0 } in
reseedi ~t @@ fun add ->
for i = 0 to pools - 1 do
if t.reseed_count land ((1 lsl i) - 1) = 0
then (SHAd256.get t.pools.(i) |> add; t.pools.(i) <- SHAd256.empty)
done
end else t else t

let generate_into ~t buf ~off len =
let t = add_pool_entropy t in
if not (seeded ~t) then raise Mirage_crypto_rng.Unseeded_generator;
let rec chunk t off = function
| i when i <= 0 -> t
| n ->
let n' = imin n 0x10000 in
let t = generate_rekey ~t buf ~off n' in
chunk t (off + n') (n - n') in
chunk t off len

let add ~t source ~pool data =
let buf = Bytes.create 2
and pool = pool land (pools - 1)
and source = Mirage_crypto_rng.Entropy.id source land 0xff in
Bytes.set_uint8 buf 0 source;
Bytes.set_uint8 buf 1 (String.length data);
t.pools.(pool) <- SHAd256.feedi t.pools.(pool) (iter2 (Bytes.unsafe_to_string buf) data);
if pool = 0 then { t with pool0_size= t.pool0_size + String.length data } else t

let accumulate ~g source =
let pool = ref 0 in
`Acc (fun buf ->
update g @@ fun t ->
let t = add ~t source ~pool:!pool buf in
incr pool; t)

let reseed ~g cs = update g @@ fun t -> reseed ~t cs
let generate_into ~g buf ~off len = update g @@ fun t -> generate_into ~t buf ~off len
let seeded ~g = get g @@ fun t -> seeded ~t
1 change: 1 addition & 0 deletions rng/miou/pfortuna.mli
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include Mirage_crypto_rng.Generator
6 changes: 6 additions & 0 deletions tests/dune
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,9 @@
(modules test_eio_rng test_eio_entropy_collection)
(libraries mirage-crypto-rng-eio duration eio_main ohex)
(package mirage-crypto-rng-eio))

(tests
(names test_miou_rng test_miou_entropy_collection)
(modules test_miou_rng test_miou_entropy_collection)
(libraries mirage-crypto-rng-miou duration ohex)
(package mirage-crypto-rng-miou))
31 changes: 31 additions & 0 deletions tests/test_miou_entropy_collection.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module Printing_rng = struct
type g = unit

let block = 16
let create ?time:_ () = ()
let generate_into ~g:_ _buf ~off:_ _len = assert false
let seeded ~g:_ = true
let pools = 1

let reseed ~g:_ data =
Format.printf "reseeding:@.%a@.%!" (Ohex.pp_hexdump ()) data

let accumulate ~g:_ source =
let print data =
Format.printf "accumulate: (src: %a) %a@.%!"
Mirage_crypto_rng.Entropy.pp_source source Ohex.pp data
in
`Acc print
end

let () =
Miou_unix.run @@ fun () ->
let rng = Mirage_crypto_rng_miou.initialize (module Printing_rng) in
Format.printf "entropy sources: %a@,%!"
(fun ppf -> List.iter (fun x ->
Mirage_crypto_rng.Entropy.pp_source ppf x;
Format.pp_print_space ppf ()))
(Mirage_crypto_rng.Entropy.sources ());
let sleep = Duration.(of_sec 2 |> to_f) in
Miou_unix.sleep sleep;
Mirage_crypto_rng_miou.kill rng
9 changes: 9 additions & 0 deletions tests/test_miou_rng.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
let () = Miou_unix.run @@ fun () ->
let rng = Mirage_crypto_rng_miou.initialize (module Mirage_crypto_rng_miou.Pfortuna) in
let random_num = Mirage_crypto_rng.generate 32 in
assert (String.length random_num = 32);
Printf.printf "32 bit random number: %S\n%!" random_num;
let random_num = Mirage_crypto_rng.generate 16 in
assert (String.length random_num = 16);
Printf.printf "16 bit random number: %S\n%!" random_num;
Mirage_crypto_rng_miou.kill rng

0 comments on commit e76ae80

Please sign in to comment.