-
Notifications
You must be signed in to change notification settings - Fork 68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add the miou implementation #503
Merged
Merged
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
f37f2bd
Add the Miou implementation of TLS
dinosaure be4075d
Rename tls-miou tls-miou-unix
dinosaure 17080ba
Remove Cstruct.t from the miou implementation
dinosaure a1ae0d9
Upgrade the PR with miou.0.2.0
dinosaure 452d2f0
Fix the fuzzer about the Miou implementation
dinosaure 53152e4
Apply @hannesm's suggestions from code review
dinosaure f1f1d9b
The fuzzer about miou is a part of the tls-miou-unix package
dinosaure ade7032
Fix leaked file-descriptor on the Miou fuzzer
dinosaure ff20648
Show the unexpected error on our fuzzer
dinosaure 77ea165
Close the file-descriptor on the client side in any situations
dinosaure 89c51b1
adapt to API change (Tls.Config.{client,server} may result in an error)
hannesm File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
(library | ||
(name tls_miou_unix) | ||
(public_name tls-miou-unix) | ||
(libraries miou.unix tls)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
(test | ||
(name fuzz) | ||
(package tls-miou-unix) | ||
(libraries | ||
mirage-crypto-rng-miou-unix | ||
ohex | ||
rresult | ||
ptime | ||
ptime.clock.os | ||
crowbar | ||
hxd.core | ||
hxd.string | ||
tls-miou-unix) | ||
(instrumentation | ||
(backend bisect_ppx))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,338 @@ | ||
let rec random_path ?(tries = 10) fmt = | ||
if tries <= 0 then failwith "Impossible to generate an available random path"; | ||
let res = Bytes.create 6 in | ||
for i = 0 to Bytes.length res - 1 do | ||
let chr = | ||
match Random.int (10 + 26 + 26) with | ||
| n when n < 10 -> Char.chr (Char.code '0' + n) | ||
| n when n < 10 + 26 -> Char.chr (Char.code 'a' + n - 10) | ||
| n -> Char.chr (Char.code 'A' + n - 10 - 26) | ||
in | ||
Bytes.set res i chr | ||
done; | ||
let path = Fmt.str fmt (Bytes.unsafe_to_string res) in | ||
if Sys.file_exists path then random_path ~tries:(pred tries) fmt else path | ||
|
||
let unlink_if_exists path = | ||
try Unix.unlink path with Unix.Unix_error (Unix.ENOENT, _, _) -> () | ||
|
||
let bind_and_listen ?(backlog = 16) () = | ||
let tmp = random_path "socket-%s.socket" in | ||
unlink_if_exists tmp; | ||
let socket = Unix.socket ~cloexec:true Unix.PF_UNIX Unix.SOCK_STREAM 0 in | ||
let addr = Unix.ADDR_UNIX tmp in | ||
Unix.bind socket addr; | ||
Unix.listen socket backlog; | ||
(Miou_unix.of_file_descr ~non_blocking:true socket, addr, tmp) | ||
|
||
module Ca = struct | ||
open Rresult | ||
|
||
let prefix = | ||
X509.Distinguished_name. | ||
[ Relative_distinguished_name.singleton (CN "Fuzzer") ] | ||
|
||
let cacert_dn = | ||
X509.Distinguished_name.( | ||
prefix | ||
@ [ Relative_distinguished_name.singleton (CN "Ephemeral CA for fuzzer") ]) | ||
|
||
let cacert_lifetime = Ptime.Span.v (365, 0L) | ||
|
||
let make domain_name seed = | ||
Domain_name.of_string domain_name >>= Domain_name.host | ||
>>= fun domain_name -> | ||
let private_key = | ||
let seed = Base64.decode_exn ~pad:false seed in | ||
let g = Mirage_crypto_rng.(create ~seed (module Fortuna)) in | ||
Mirage_crypto_pk.Rsa.generate ~g ~bits:2048 () | ||
in | ||
let valid_from = Ptime.v (Ptime_clock.now_d_ps ()) in | ||
Ptime.add_span valid_from cacert_lifetime | ||
|> Option.to_result ~none:(R.msgf "End time out of range") | ||
>>= fun valid_until -> | ||
X509.Signing_request.create cacert_dn (`RSA private_key) >>= fun ca_csr -> | ||
let extensions = | ||
let open X509.Extension in | ||
let key_id = | ||
X509.Public_key.id X509.Signing_request.((info ca_csr).public_key) | ||
in | ||
empty | ||
|> add Subject_alt_name | ||
( true, | ||
X509.General_name.( | ||
singleton DNS [ Domain_name.to_string domain_name ]) ) | ||
|> add Basic_constraints (true, (false, None)) | ||
|> add Key_usage | ||
(true, [ `Digital_signature; `Content_commitment; `Key_encipherment ]) | ||
|> add Subject_key_id (false, key_id) | ||
in | ||
X509.Signing_request.sign ~valid_from ~valid_until ~extensions | ||
ca_csr (`RSA private_key) cacert_dn | ||
|> R.reword_error (R.msgf "%a" X509.Validation.pp_signature_error) | ||
>>= fun certificate -> | ||
let fingerprint = X509.Certificate.fingerprint `SHA256 certificate in | ||
let time () = Some (Ptime_clock.now ()) in | ||
let authenticator = | ||
X509.Authenticator.cert_fingerprint ~time ~hash:`SHA256 | ||
~fingerprint | ||
in | ||
Ok (certificate, `RSA private_key, authenticator) | ||
end | ||
|
||
let fuzz_coop = "fuzz.coop" | ||
let mutex = Miou.Mutex.create () | ||
let epr fmt = Miou.Mutex.protect mutex @@ fun () -> Fmt.epr fmt | ||
|
||
type operation = | ||
| Send of string | ||
| Recv of int | ||
| Shutdown of [ `read | `write ] | ||
| Close | ||
| Noop | ||
|
||
module Stop = struct | ||
type t = { | ||
mutex : Miou.Mutex.t; | ||
condition : Miou.Condition.t; | ||
mutable stop : bool; | ||
} | ||
|
||
let create () = | ||
let mutex = Miou.Mutex.create () in | ||
let condition = Miou.Condition.create () in | ||
{ mutex; condition; stop = false } | ||
|
||
let stop t = | ||
Miou.Mutex.protect t.mutex @@ fun () -> | ||
t.stop <- true; | ||
Miou.Condition.broadcast t.condition | ||
|
||
let wait t = | ||
Miou.Mutex.protect t.mutex @@ fun () -> | ||
while t.stop = false do | ||
Miou.Condition.wait t.condition t.mutex | ||
done | ||
end | ||
|
||
let inhibit fn = try fn () with _exn -> () | ||
|
||
let run ~role:_ actions tls = | ||
let rec go buf tls = function | ||
| [] -> Buffer.contents buf | ||
| Noop :: actions -> | ||
Miou.yield (); | ||
go buf tls actions | ||
| Send str :: actions -> | ||
Tls_miou_unix.write tls str; | ||
go buf tls actions | ||
| Close :: actions -> | ||
Tls_miou_unix.close tls; | ||
go buf tls actions | ||
| Shutdown cmd :: actions -> | ||
Tls_miou_unix.shutdown tls (cmd :> [ `read | `write | `read_write ]); | ||
go buf tls actions | ||
| Recv len :: actions -> | ||
let tmp = Bytes.make len '\000' in | ||
Tls_miou_unix.really_read tls tmp; | ||
Buffer.add_subbytes buf tmp 0 len; | ||
go buf tls actions | ||
in | ||
let buf = Buffer.create 0x100 in | ||
try go buf tls actions with | ||
| End_of_file | Tls_miou_unix.Closed_by_peer | Tls_miou_unix.Tls_alert _ | ||
| Tls_miou_unix.Tls_failure _ -> | ||
inhibit (fun () -> Miou_unix.close (Tls_miou_unix.file_descr tls)); | ||
Buffer.contents buf | ||
| exn -> | ||
inhibit (fun () -> Miou_unix.close (Tls_miou_unix.file_descr tls)); | ||
raise exn | ||
|
||
let run_client ~to_client:actions cfg addr = | ||
let domain = Unix.domain_of_sockaddr addr in | ||
let socket = Unix.socket ~cloexec:true domain Unix.SOCK_STREAM 0 in | ||
Unix.connect socket addr; | ||
let fd = Miou_unix.of_file_descr ~non_blocking:true socket in | ||
let tls = Tls_miou_unix.client_of_fd cfg fd in | ||
let finally () = | ||
inhibit (fun () -> Unix.close socket) | ||
in | ||
Fun.protect ~finally @@ fun () -> run ~role:"client" actions tls | ||
|
||
let rec cleanup orphans clients = | ||
match Miou.care orphans with | ||
| None | Some None -> clients | ||
| Some (Some prm) -> | ||
let clients = Miou.await prm :: clients in | ||
cleanup orphans clients | ||
|
||
let rec terminate orphans clients = | ||
match Miou.care orphans with | ||
| None -> List.rev clients | ||
| Some None -> | ||
Miou.yield (); | ||
terminate orphans clients | ||
| Some (Some prm) -> | ||
let clients = Miou.await prm :: clients in | ||
terminate orphans clients | ||
|
||
exception Stop | ||
|
||
let run_server ~to_server:actions ~stop fd cfg = | ||
let rec go orphans clients = | ||
let clients = cleanup orphans clients in | ||
let accept = Miou.async @@ fun () -> Miou_unix.accept ~cloexec:true fd in | ||
let stop = | ||
Miou.async @@ fun () -> | ||
Stop.wait stop; | ||
raise Stop | ||
in | ||
match Miou.await_first [ accept; stop ] with | ||
| Error _ -> | ||
inhibit (fun () -> Miou_unix.close fd); | ||
terminate orphans clients | ||
| Ok (fd, _) -> | ||
ignore | ||
( Miou.async ~orphans @@ fun () -> | ||
match Tls_miou_unix.server_of_fd cfg fd with | ||
| tls -> | ||
let str = run ~role:"server" actions tls in | ||
inhibit (fun () -> Miou_unix.close fd); str | ||
| exception _ -> | ||
Miou_unix.close fd; | ||
String.empty ); | ||
go orphans clients | ||
in | ||
go (Miou.orphans ()) [] | ||
|
||
let compile to_client to_server = | ||
let close_client close = function | ||
| Close -> close lor 0b1100 | ||
| Shutdown `read -> close lor 0b1000 | ||
| Shutdown `write -> close lor 0b0100 | ||
| _ -> close | ||
in | ||
let close_server close = function | ||
| Close -> close lor 0b0011 | ||
| Shutdown `read -> close lor 0b0010 | ||
| Shutdown `write -> close lor 0b0001 | ||
| _ -> close | ||
in | ||
let client = Buffer.create 0x100 in | ||
let server = Buffer.create 0x100 in | ||
let rec go close to_client to_server = | ||
match (close, to_client, to_server) with | ||
| _, [], _ | _, _, [] -> () | ||
| close, ((Shutdown _ | Close) as operation) :: to_client, _ -> | ||
go (close_client close operation) to_client to_server | ||
| close, _, ((Shutdown _ | Close) as operation) :: to_server -> | ||
go (close_server close operation) to_client to_server | ||
| close, Noop :: to_client, to_server | close, to_client, Noop :: to_server | ||
-> | ||
go close to_client to_server | ||
| close, Send str :: to_client, Recv n :: to_server -> | ||
assert (String.length str = n); | ||
if close land 0b0100 = 0 && close land 0b0010 = 0 then | ||
Buffer.add_string server str; | ||
if close land 0b0100 = 0 && close land 0b0010 = 0 then | ||
go close to_client to_server | ||
| close, Recv n :: to_client, Send str :: to_server -> | ||
assert (String.length str = n); | ||
if close land 0b1000 = 0 && close land 0b0001 = 0 then | ||
Buffer.add_string client str; | ||
if close land 0b1000 = 0 && close land 0b0001 = 0 then | ||
go close to_client to_server | ||
| _, Send _ :: _, Send _ :: _ | _, Recv _ :: _, Recv _ :: _ -> | ||
assert false (* GADT? *) | ||
in | ||
go 0x0 to_client to_server; | ||
(Buffer.contents client, Buffer.contents server) | ||
|
||
let pp_exn ppf exn = Fmt.string ppf (Printexc.to_string exn) | ||
let pp_str ppf str = Hxd_string.pp Hxd.default ppf str | ||
|
||
let run seed operations = | ||
Miou_unix.run ~domains:1 @@ fun () -> | ||
let rng = Mirage_crypto_rng_miou_unix.(initialize (module Pfortuna)) in | ||
let fd, addr, path = bind_and_listen () in | ||
let finally () = Unix.unlink path in | ||
Fun.protect ~finally @@ fun () -> | ||
let cert, pk, authenticator = | ||
Rresult.R.failwith_error_msg (Ca.make fuzz_coop seed) | ||
in | ||
let cfg_server = | ||
Result.get_ok (Tls.Config.server ~certificates:(`Single ([ cert ], pk)) ()) | ||
in | ||
let cfg_client = Result.get_ok (Tls.Config.client ~authenticator ()) in | ||
let to_client, to_server = List.split operations in | ||
let stop = Stop.create () in | ||
let prm0 = Miou.async @@ fun () -> run_server ~to_server ~stop fd cfg_server in | ||
let prm1 = | ||
Miou.async @@ fun () -> | ||
let finally () = Stop.stop stop in | ||
Fun.protect ~finally @@ fun () -> run_client ~to_client cfg_client addr | ||
in | ||
let send_to_client, send_to_server = compile to_client to_server in | ||
match (Miou.await prm0, Miou.await prm1) with | ||
| Ok [ Ok send_to_server' ], Ok send_to_client' -> | ||
Crowbar.check (String.equal send_to_client send_to_client'); | ||
Crowbar.check (String.equal send_to_server send_to_server'); | ||
let n = String.length send_to_client in | ||
let m = String.length send_to_server in | ||
Mirage_crypto_rng_miou_unix.kill rng; | ||
epr "[%a] %db %db transmitted\n%!" Fmt.(styled `Green string) "OK" n m | ||
| a, b -> | ||
Mirage_crypto_rng_miou_unix.kill rng; | ||
Crowbar.failf "[%a] Unexpected result: %a & %a\n%!" | ||
Fmt.(styled `Red string) "ERROR" | ||
Fmt.(Dump.result ~error:pp_exn ~ok:Fmt.(Dump.list (Dump.result ~error:pp_exn ~ok:pp_str))) a | ||
Fmt.(Dump.result ~error:pp_exn ~ok:pp_str) b | ||
|
||
let label name gen = Crowbar.with_printer Fmt.(const string name) gen | ||
|
||
let direction = | ||
let open Crowbar in | ||
choose | ||
[ | ||
label "server-to-client" (const `To_client); | ||
label "client-to-server" (const `To_server); | ||
] | ||
|
||
let shutdown = | ||
let open Crowbar in | ||
choose | ||
[ | ||
label "close" (const Close); | ||
label "shutdown-recv" (const (Shutdown `read)); | ||
label "shutdown-send" (const (Shutdown `write)); | ||
label "noop" (const Noop); | ||
] | ||
|
||
let operation = | ||
let open Crowbar in | ||
map [ direction; bytes ] @@ fun direction str -> | ||
match (direction, str) with | ||
| _, "" -> (Noop, Noop) | ||
| `To_server, str -> (Send str, Recv (String.length str)) | ||
| `To_client, str -> (Recv (String.length str), Send str) | ||
|
||
let counter = Atomic.make 0 | ||
|
||
let operations = | ||
let open Crowbar in | ||
fix @@ fun m -> | ||
let continue (to_client, to_server) = | ||
if Atomic.fetch_and_add counter 1 >= 4 then const [ (Close, Close) ] | ||
else map [ m ] @@ fun ops -> (to_client, to_server) :: ops | ||
in | ||
map | ||
[ list1 operation; dynamic_bind (pair shutdown shutdown) continue ] | ||
List.rev_append | ||
|
||
let seed = Crowbar.(map [ bytes ] Base64.encode_exn) | ||
|
||
let () = | ||
Sys.set_signal Sys.sigpipe Sys.Signal_ignore; | ||
Crowbar.add_test ~name:"run" Crowbar.[ seed; operations ] @@ fun seed operations -> | ||
run seed operations; | ||
Atomic.set counter 0 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure that you want to execute this executable every time, it takes a very long time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how long is "very long"? And what are the options? marking it as executable? then we can't attach it to a package without a
public_name
...There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2mins on my (good) machine.
We can set it as a simple executable if the test bother you too much (because it takes to much time).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine leaving it as test. I usually develop in ocaml 4 land where I don't have miou anyways, and I prefer to have tests that are useful enabled and running (otherwise we hit the "what was the last time that feedback.exe worked" again).