127 lines
3.5 KiB
OCaml
127 lines
3.5 KiB
OCaml
|
|
(* NOTE: when modifying this file, please also check whether
|
||
|
|
rng/miou/pfortuna.ml needs to be updated. *)
|
||
|
|
|
||
|
|
open Mirage_crypto
|
||
|
|
open Mirage_crypto.Uncommon
|
||
|
|
|
||
|
|
module SHAd256 = struct
|
||
|
|
open Digestif
|
||
|
|
type t = SHA256.t
|
||
|
|
type ctx = SHA256.ctx
|
||
|
|
let empty = SHA256.empty
|
||
|
|
let get t = SHA256.(get t |> to_raw_string |> digest_string |> to_raw_string)
|
||
|
|
let digest x = SHA256.(digest_string x |> 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
|
||
|
|
|
||
|
|
(* XXX Locking!! *)
|
||
|
|
type g =
|
||
|
|
{ mutable ctr : AES.CTR.ctr
|
||
|
|
; mutable secret : string
|
||
|
|
; mutable key : AES.CTR.key
|
||
|
|
; pools : SHAd256.ctx array
|
||
|
|
; mutable pool0_size : int
|
||
|
|
; mutable reseed_count : int
|
||
|
|
; mutable last_reseed : int64
|
||
|
|
; time : (unit -> int64) option
|
||
|
|
}
|
||
|
|
|
||
|
|
let create ?time () =
|
||
|
|
let k = String.make 32 '\x00' in
|
||
|
|
{ ctr = (0L, 0L)
|
||
|
|
; secret = k
|
||
|
|
; key = AES.CTR.of_secret k
|
||
|
|
; pools = Array.make pools SHAd256.empty
|
||
|
|
; pool0_size = 0
|
||
|
|
; reseed_count = 0
|
||
|
|
; last_reseed = 0L
|
||
|
|
; time
|
||
|
|
}
|
||
|
|
|
||
|
|
let seeded ~g =
|
||
|
|
let lo, hi = g.ctr in
|
||
|
|
not (Int64.equal lo 0L && Int64.equal hi 0L)
|
||
|
|
|
||
|
|
(* XXX We might want to erase the old key. *)
|
||
|
|
let set_key ~g sec =
|
||
|
|
g.secret <- sec ;
|
||
|
|
g.key <- AES.CTR.of_secret sec
|
||
|
|
|
||
|
|
let reseedi ~g iter =
|
||
|
|
set_key ~g @@ SHAd256.digesti (fun f -> f g.secret; iter f);
|
||
|
|
g.ctr <- AES.CTR.add_ctr g.ctr 1L
|
||
|
|
|
||
|
|
let iter1 a f = f a
|
||
|
|
|
||
|
|
let reseed ~g cs = reseedi ~g (iter1 cs)
|
||
|
|
|
||
|
|
let generate_rekey ~g buf ~off len =
|
||
|
|
let b = len // block + 2 in
|
||
|
|
let n = b * block in
|
||
|
|
let r = AES.CTR.stream ~key:g.key ~ctr:g.ctr n in
|
||
|
|
Bytes.unsafe_blit_string r 0 buf off len;
|
||
|
|
let r2 = String.sub r (n - 32) 32 in
|
||
|
|
set_key ~g r2 ;
|
||
|
|
g.ctr <- AES.CTR.add_ctr g.ctr (Int64.of_int b)
|
||
|
|
|
||
|
|
let add_pool_entropy g =
|
||
|
|
if g.pool0_size > min_pool_size then
|
||
|
|
let should_reseed, now =
|
||
|
|
match g.time with
|
||
|
|
| None -> true, 0L
|
||
|
|
| Some f ->
|
||
|
|
let now = f () in
|
||
|
|
Int64.(sub now g.last_reseed > min_time_duration), now
|
||
|
|
in
|
||
|
|
if should_reseed then begin
|
||
|
|
g.reseed_count <- g.reseed_count + 1;
|
||
|
|
g.last_reseed <- now;
|
||
|
|
g.pool0_size <- 0;
|
||
|
|
reseedi ~g @@ fun add ->
|
||
|
|
for i = 0 to pools - 1 do
|
||
|
|
if g.reseed_count land ((1 lsl i) - 1) = 0 then
|
||
|
|
(SHAd256.get g.pools.(i) |> add; g.pools.(i) <- SHAd256.empty)
|
||
|
|
done
|
||
|
|
end
|
||
|
|
|
||
|
|
let generate_into ~g buf ~off len =
|
||
|
|
add_pool_entropy g;
|
||
|
|
if not (seeded ~g) then raise Rng.Unseeded_generator ;
|
||
|
|
let rec chunk off = function
|
||
|
|
| i when i <= 0 -> ()
|
||
|
|
| n ->
|
||
|
|
let n' = imin n 0x10000 in
|
||
|
|
generate_rekey ~g buf ~off n';
|
||
|
|
chunk (off + n') (n - n')
|
||
|
|
in
|
||
|
|
chunk off len
|
||
|
|
|
||
|
|
let add ~g (source, _) ~pool data =
|
||
|
|
let buf = Bytes.create 2
|
||
|
|
and pool = pool land (pools - 1)
|
||
|
|
and source = source land 0xff in
|
||
|
|
Bytes.set_uint8 buf 0 source;
|
||
|
|
Bytes.set_uint8 buf 1 (String.length data);
|
||
|
|
g.pools.(pool) <- SHAd256.feedi g.pools.(pool) (iter2 (Bytes.unsafe_to_string buf) data);
|
||
|
|
if pool = 0 then g.pool0_size <- g.pool0_size + String.length data
|
||
|
|
|
||
|
|
(* XXX
|
||
|
|
* Schneier recommends against using generator-imposed pool-seeding schedule
|
||
|
|
* but it just makes for a horrid api.
|
||
|
|
*)
|
||
|
|
let accumulate ~g source =
|
||
|
|
let pool = ref 0 in
|
||
|
|
`Acc (fun buf ->
|
||
|
|
add ~g source ~pool:!pool buf ;
|
||
|
|
incr pool)
|