|
| 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 |
0 commit comments