Skip to content

Commit f2ce295

Browse files
authored
Add the miou implementation (#503)
* Add the Miou implementation of TLS
1 parent def137a commit f2ce295

File tree

6 files changed

+807
-0
lines changed

6 files changed

+807
-0
lines changed

miou/dune

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
(library
2+
(name tls_miou_unix)
3+
(public_name tls-miou-unix)
4+
(libraries miou.unix tls))

miou/tests/dune

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
(test
2+
(name fuzz)
3+
(package tls-miou-unix)
4+
(libraries
5+
mirage-crypto-rng-miou-unix
6+
ohex
7+
rresult
8+
ptime
9+
ptime.clock.os
10+
crowbar
11+
hxd.core
12+
hxd.string
13+
tls-miou-unix)
14+
(instrumentation
15+
(backend bisect_ppx)))

miou/tests/fuzz.ml

+338
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,338 @@
1+
let rec random_path ?(tries = 10) fmt =
2+
if tries <= 0 then failwith "Impossible to generate an available random path";
3+
let res = Bytes.create 6 in
4+
for i = 0 to Bytes.length res - 1 do
5+
let chr =
6+
match Random.int (10 + 26 + 26) with
7+
| n when n < 10 -> Char.chr (Char.code '0' + n)
8+
| n when n < 10 + 26 -> Char.chr (Char.code 'a' + n - 10)
9+
| n -> Char.chr (Char.code 'A' + n - 10 - 26)
10+
in
11+
Bytes.set res i chr
12+
done;
13+
let path = Fmt.str fmt (Bytes.unsafe_to_string res) in
14+
if Sys.file_exists path then random_path ~tries:(pred tries) fmt else path
15+
16+
let unlink_if_exists path =
17+
try Unix.unlink path with Unix.Unix_error (Unix.ENOENT, _, _) -> ()
18+
19+
let bind_and_listen ?(backlog = 16) () =
20+
let tmp = random_path "socket-%s.socket" in
21+
unlink_if_exists tmp;
22+
let socket = Unix.socket ~cloexec:true Unix.PF_UNIX Unix.SOCK_STREAM 0 in
23+
let addr = Unix.ADDR_UNIX tmp in
24+
Unix.bind socket addr;
25+
Unix.listen socket backlog;
26+
(Miou_unix.of_file_descr ~non_blocking:true socket, addr, tmp)
27+
28+
module Ca = struct
29+
open Rresult
30+
31+
let prefix =
32+
X509.Distinguished_name.
33+
[ Relative_distinguished_name.singleton (CN "Fuzzer") ]
34+
35+
let cacert_dn =
36+
X509.Distinguished_name.(
37+
prefix
38+
@ [ Relative_distinguished_name.singleton (CN "Ephemeral CA for fuzzer") ])
39+
40+
let cacert_lifetime = Ptime.Span.v (365, 0L)
41+
42+
let make domain_name seed =
43+
Domain_name.of_string domain_name >>= Domain_name.host
44+
>>= fun domain_name ->
45+
let private_key =
46+
let seed = Base64.decode_exn ~pad:false seed in
47+
let g = Mirage_crypto_rng.(create ~seed (module Fortuna)) in
48+
Mirage_crypto_pk.Rsa.generate ~g ~bits:2048 ()
49+
in
50+
let valid_from = Ptime.v (Ptime_clock.now_d_ps ()) in
51+
Ptime.add_span valid_from cacert_lifetime
52+
|> Option.to_result ~none:(R.msgf "End time out of range")
53+
>>= fun valid_until ->
54+
X509.Signing_request.create cacert_dn (`RSA private_key) >>= fun ca_csr ->
55+
let extensions =
56+
let open X509.Extension in
57+
let key_id =
58+
X509.Public_key.id X509.Signing_request.((info ca_csr).public_key)
59+
in
60+
empty
61+
|> add Subject_alt_name
62+
( true,
63+
X509.General_name.(
64+
singleton DNS [ Domain_name.to_string domain_name ]) )
65+
|> add Basic_constraints (true, (false, None))
66+
|> add Key_usage
67+
(true, [ `Digital_signature; `Content_commitment; `Key_encipherment ])
68+
|> add Subject_key_id (false, key_id)
69+
in
70+
X509.Signing_request.sign ~valid_from ~valid_until ~extensions
71+
ca_csr (`RSA private_key) cacert_dn
72+
|> R.reword_error (R.msgf "%a" X509.Validation.pp_signature_error)
73+
>>= fun certificate ->
74+
let fingerprint = X509.Certificate.fingerprint `SHA256 certificate in
75+
let time () = Some (Ptime_clock.now ()) in
76+
let authenticator =
77+
X509.Authenticator.cert_fingerprint ~time ~hash:`SHA256
78+
~fingerprint
79+
in
80+
Ok (certificate, `RSA private_key, authenticator)
81+
end
82+
83+
let fuzz_coop = "fuzz.coop"
84+
let mutex = Miou.Mutex.create ()
85+
let epr fmt = Miou.Mutex.protect mutex @@ fun () -> Fmt.epr fmt
86+
87+
type operation =
88+
| Send of string
89+
| Recv of int
90+
| Shutdown of [ `read | `write ]
91+
| Close
92+
| Noop
93+
94+
module Stop = struct
95+
type t = {
96+
mutex : Miou.Mutex.t;
97+
condition : Miou.Condition.t;
98+
mutable stop : bool;
99+
}
100+
101+
let create () =
102+
let mutex = Miou.Mutex.create () in
103+
let condition = Miou.Condition.create () in
104+
{ mutex; condition; stop = false }
105+
106+
let stop t =
107+
Miou.Mutex.protect t.mutex @@ fun () ->
108+
t.stop <- true;
109+
Miou.Condition.broadcast t.condition
110+
111+
let wait t =
112+
Miou.Mutex.protect t.mutex @@ fun () ->
113+
while t.stop = false do
114+
Miou.Condition.wait t.condition t.mutex
115+
done
116+
end
117+
118+
let inhibit fn = try fn () with _exn -> ()
119+
120+
let run ~role:_ actions tls =
121+
let rec go buf tls = function
122+
| [] -> Buffer.contents buf
123+
| Noop :: actions ->
124+
Miou.yield ();
125+
go buf tls actions
126+
| Send str :: actions ->
127+
Tls_miou_unix.write tls str;
128+
go buf tls actions
129+
| Close :: actions ->
130+
Tls_miou_unix.close tls;
131+
go buf tls actions
132+
| Shutdown cmd :: actions ->
133+
Tls_miou_unix.shutdown tls (cmd :> [ `read | `write | `read_write ]);
134+
go buf tls actions
135+
| Recv len :: actions ->
136+
let tmp = Bytes.make len '\000' in
137+
Tls_miou_unix.really_read tls tmp;
138+
Buffer.add_subbytes buf tmp 0 len;
139+
go buf tls actions
140+
in
141+
let buf = Buffer.create 0x100 in
142+
try go buf tls actions with
143+
| End_of_file | Tls_miou_unix.Closed_by_peer | Tls_miou_unix.Tls_alert _
144+
| Tls_miou_unix.Tls_failure _ ->
145+
inhibit (fun () -> Miou_unix.close (Tls_miou_unix.file_descr tls));
146+
Buffer.contents buf
147+
| exn ->
148+
inhibit (fun () -> Miou_unix.close (Tls_miou_unix.file_descr tls));
149+
raise exn
150+
151+
let run_client ~to_client:actions cfg addr =
152+
let domain = Unix.domain_of_sockaddr addr in
153+
let socket = Unix.socket ~cloexec:true domain Unix.SOCK_STREAM 0 in
154+
Unix.connect socket addr;
155+
let fd = Miou_unix.of_file_descr ~non_blocking:true socket in
156+
let tls = Tls_miou_unix.client_of_fd cfg fd in
157+
let finally () =
158+
inhibit (fun () -> Unix.close socket)
159+
in
160+
Fun.protect ~finally @@ fun () -> run ~role:"client" actions tls
161+
162+
let rec cleanup orphans clients =
163+
match Miou.care orphans with
164+
| None | Some None -> clients
165+
| Some (Some prm) ->
166+
let clients = Miou.await prm :: clients in
167+
cleanup orphans clients
168+
169+
let rec terminate orphans clients =
170+
match Miou.care orphans with
171+
| None -> List.rev clients
172+
| Some None ->
173+
Miou.yield ();
174+
terminate orphans clients
175+
| Some (Some prm) ->
176+
let clients = Miou.await prm :: clients in
177+
terminate orphans clients
178+
179+
exception Stop
180+
181+
let run_server ~to_server:actions ~stop fd cfg =
182+
let rec go orphans clients =
183+
let clients = cleanup orphans clients in
184+
let accept = Miou.async @@ fun () -> Miou_unix.accept ~cloexec:true fd in
185+
let stop =
186+
Miou.async @@ fun () ->
187+
Stop.wait stop;
188+
raise Stop
189+
in
190+
match Miou.await_first [ accept; stop ] with
191+
| Error _ ->
192+
inhibit (fun () -> Miou_unix.close fd);
193+
terminate orphans clients
194+
| Ok (fd, _) ->
195+
ignore
196+
( Miou.async ~orphans @@ fun () ->
197+
match Tls_miou_unix.server_of_fd cfg fd with
198+
| tls ->
199+
let str = run ~role:"server" actions tls in
200+
inhibit (fun () -> Miou_unix.close fd); str
201+
| exception _ ->
202+
Miou_unix.close fd;
203+
String.empty );
204+
go orphans clients
205+
in
206+
go (Miou.orphans ()) []
207+
208+
let compile to_client to_server =
209+
let close_client close = function
210+
| Close -> close lor 0b1100
211+
| Shutdown `read -> close lor 0b1000
212+
| Shutdown `write -> close lor 0b0100
213+
| _ -> close
214+
in
215+
let close_server close = function
216+
| Close -> close lor 0b0011
217+
| Shutdown `read -> close lor 0b0010
218+
| Shutdown `write -> close lor 0b0001
219+
| _ -> close
220+
in
221+
let client = Buffer.create 0x100 in
222+
let server = Buffer.create 0x100 in
223+
let rec go close to_client to_server =
224+
match (close, to_client, to_server) with
225+
| _, [], _ | _, _, [] -> ()
226+
| close, ((Shutdown _ | Close) as operation) :: to_client, _ ->
227+
go (close_client close operation) to_client to_server
228+
| close, _, ((Shutdown _ | Close) as operation) :: to_server ->
229+
go (close_server close operation) to_client to_server
230+
| close, Noop :: to_client, to_server | close, to_client, Noop :: to_server
231+
->
232+
go close to_client to_server
233+
| close, Send str :: to_client, Recv n :: to_server ->
234+
assert (String.length str = n);
235+
if close land 0b0100 = 0 && close land 0b0010 = 0 then
236+
Buffer.add_string server str;
237+
if close land 0b0100 = 0 && close land 0b0010 = 0 then
238+
go close to_client to_server
239+
| close, Recv n :: to_client, Send str :: to_server ->
240+
assert (String.length str = n);
241+
if close land 0b1000 = 0 && close land 0b0001 = 0 then
242+
Buffer.add_string client str;
243+
if close land 0b1000 = 0 && close land 0b0001 = 0 then
244+
go close to_client to_server
245+
| _, Send _ :: _, Send _ :: _ | _, Recv _ :: _, Recv _ :: _ ->
246+
assert false (* GADT? *)
247+
in
248+
go 0x0 to_client to_server;
249+
(Buffer.contents client, Buffer.contents server)
250+
251+
let pp_exn ppf exn = Fmt.string ppf (Printexc.to_string exn)
252+
let pp_str ppf str = Hxd_string.pp Hxd.default ppf str
253+
254+
let run seed operations =
255+
Miou_unix.run ~domains:1 @@ fun () ->
256+
let rng = Mirage_crypto_rng_miou_unix.(initialize (module Pfortuna)) in
257+
let fd, addr, path = bind_and_listen () in
258+
let finally () = Unix.unlink path in
259+
Fun.protect ~finally @@ fun () ->
260+
let cert, pk, authenticator =
261+
Rresult.R.failwith_error_msg (Ca.make fuzz_coop seed)
262+
in
263+
let cfg_server =
264+
Result.get_ok (Tls.Config.server ~certificates:(`Single ([ cert ], pk)) ())
265+
in
266+
let cfg_client = Result.get_ok (Tls.Config.client ~authenticator ()) in
267+
let to_client, to_server = List.split operations in
268+
let stop = Stop.create () in
269+
let prm0 = Miou.async @@ fun () -> run_server ~to_server ~stop fd cfg_server in
270+
let prm1 =
271+
Miou.async @@ fun () ->
272+
let finally () = Stop.stop stop in
273+
Fun.protect ~finally @@ fun () -> run_client ~to_client cfg_client addr
274+
in
275+
let send_to_client, send_to_server = compile to_client to_server in
276+
match (Miou.await prm0, Miou.await prm1) with
277+
| Ok [ Ok send_to_server' ], Ok send_to_client' ->
278+
Crowbar.check (String.equal send_to_client send_to_client');
279+
Crowbar.check (String.equal send_to_server send_to_server');
280+
let n = String.length send_to_client in
281+
let m = String.length send_to_server in
282+
Mirage_crypto_rng_miou_unix.kill rng;
283+
epr "[%a] %db %db transmitted\n%!" Fmt.(styled `Green string) "OK" n m
284+
| a, b ->
285+
Mirage_crypto_rng_miou_unix.kill rng;
286+
Crowbar.failf "[%a] Unexpected result: %a & %a\n%!"
287+
Fmt.(styled `Red string) "ERROR"
288+
Fmt.(Dump.result ~error:pp_exn ~ok:Fmt.(Dump.list (Dump.result ~error:pp_exn ~ok:pp_str))) a
289+
Fmt.(Dump.result ~error:pp_exn ~ok:pp_str) b
290+
291+
let label name gen = Crowbar.with_printer Fmt.(const string name) gen
292+
293+
let direction =
294+
let open Crowbar in
295+
choose
296+
[
297+
label "server-to-client" (const `To_client);
298+
label "client-to-server" (const `To_server);
299+
]
300+
301+
let shutdown =
302+
let open Crowbar in
303+
choose
304+
[
305+
label "close" (const Close);
306+
label "shutdown-recv" (const (Shutdown `read));
307+
label "shutdown-send" (const (Shutdown `write));
308+
label "noop" (const Noop);
309+
]
310+
311+
let operation =
312+
let open Crowbar in
313+
map [ direction; bytes ] @@ fun direction str ->
314+
match (direction, str) with
315+
| _, "" -> (Noop, Noop)
316+
| `To_server, str -> (Send str, Recv (String.length str))
317+
| `To_client, str -> (Recv (String.length str), Send str)
318+
319+
let counter = Atomic.make 0
320+
321+
let operations =
322+
let open Crowbar in
323+
fix @@ fun m ->
324+
let continue (to_client, to_server) =
325+
if Atomic.fetch_and_add counter 1 >= 4 then const [ (Close, Close) ]
326+
else map [ m ] @@ fun ops -> (to_client, to_server) :: ops
327+
in
328+
map
329+
[ list1 operation; dynamic_bind (pair shutdown shutdown) continue ]
330+
List.rev_append
331+
332+
let seed = Crowbar.(map [ bytes ] Base64.encode_exn)
333+
334+
let () =
335+
Sys.set_signal Sys.sigpipe Sys.Signal_ignore;
336+
Crowbar.add_test ~name:"run" Crowbar.[ seed; operations ] @@ fun seed operations ->
337+
run seed operations;
338+
Atomic.set counter 0

0 commit comments

Comments
 (0)