Skip to content

Commit

Permalink
Merge pull request #432 from torinnd/main
Browse files Browse the repository at this point in the history
Async implementation, including examples
  • Loading branch information
hannesm authored Jun 4, 2021
2 parents 3886962 + 5dec4c1 commit 75e2c8e
Show file tree
Hide file tree
Showing 12 changed files with 618 additions and 0 deletions.
5 changes: 5 additions & 0 deletions async/dune
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
(library
(name tls_async)
(public_name tls-async)
(preprocess (pps ppx_jane))
(libraries async core cstruct-async mirage-crypto-rng-async tls))
11 changes: 11 additions & 0 deletions async/examples/dune
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
(executable
(name test_client)
(modules test_client)
(preprocess (pps ppx_jane))
(libraries async core tls-async))

(executable
(name test_server)
(modules test_server)
(preprocess (pps ppx_jane))
(libraries async core tls-async))
30 changes: 30 additions & 0 deletions async/examples/test_client.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
open! Core
open! Async
open Deferred.Or_error.Let_syntax

let config = Tls.Config.client ~authenticator:(fun ~host:_ _ -> Ok None) ()

let test_client () =
let host = "127.0.0.1" in
let port = 8443 in
let hnp = Host_and_port.create ~host ~port in
let%bind (_ : Tls_async.Session.t), rd, wr =
Tls_async.connect config (Tcp.Where_to_connect.of_host_and_port hnp) ~host:(Some host)
in
let req =
String.concat
~sep:"\r\n"
[ "GET / HTTP/1.1"; "Host: " ^ host; "Connection: close"; ""; "" ]
in
Writer.write wr req;
let%bind () = Writer.flushed wr |> Deferred.ok in
let%bind () =
match%map Reader.read_line rd |> Deferred.ok with
| `Ok str -> print_endline str
| `Eof -> print_endline "Eof reached"
in
Writer.close wr |> Deferred.ok
;;

let cmd = Command.async_or_error ~summary:"test client" (Command.Param.return test_client)
let () = Command.run cmd
74 changes: 74 additions & 0 deletions async/examples/test_server.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
open! Core
open! Async

let server_cert = "./certificates/server.pem"
let server_key = "./certificates/server.key"

module X509_async = struct
let lift_of_result_msg : ('a, [< `Msg of string ]) result -> 'a Or_error.t =
Result.map_error ~f:(fun (`Msg message) -> Error.of_string message)
;;

let x509_of_pem pem =
Cstruct.of_string pem |> X509.Certificate.decode_pem_multiple |> lift_of_result_msg
;;

let certs_of_pems ca_file = Reader.file_contents ca_file >>| x509_of_pem

let private_of_pems ~cert ~priv_key =
let open Deferred.Or_error.Let_syntax in
let%bind certs = certs_of_pems cert in
let%map priv_key =
let%bind priv =
Reader.file_contents priv_key |> Deferred.ok >>| Cstruct.of_string
in
X509.Private_key.decode_pem priv |> lift_of_result_msg |> Deferred.return
in
certs, priv_key
;;
end

let serve_tls port handler =
let%bind certificate, priv_key =
X509_async.private_of_pems ~cert:server_cert ~priv_key:server_key
|> Deferred.Or_error.ok_exn
in
let config =
Tls.Config.(
server
~version:(`TLS_1_0, `TLS_1_2)
~certificates:(`Single (certificate, priv_key))
~ciphers:Ciphers.supported
())
in
let where_to_listen = Tcp.Where_to_listen.of_port port in
let on_handler_error = `Ignore in
Tls_async.listen ~on_handler_error config where_to_listen handler
;;

let test_server port =
let handler (_ : Socket.Address.Inet.t) (_ : Tls_async.Session.t) rd wr =
let pipe = Reader.pipe rd in
let rec read_from_pipe () =
(match%map Pipe.read pipe with
| `Ok line -> Writer.write wr line
| `Eof -> ())
>>= read_from_pipe
in
read_from_pipe ()
in
serve_tls port handler
;;

let cmd =
let open Command.Let_syntax in
Command.async
~summary:"test server"
(let%map_open port = anon ("PORT" %: int) in
fun () ->
let open Deferred.Let_syntax in
let%bind server = test_server port in
Tcp.Server.close_finished server)
;;

let () = Command.run cmd
193 changes: 193 additions & 0 deletions async/io.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
open! Core
open! Async
include Io_intf

module Tls_error = struct
type t =
| Tls_alert of Tls.Packet.alert_type
(** [Tls_alert] exception received from the other endpoint *)
| Tls_failure of Tls.Engine.failure
(** [Tls_failure] exception while processing incoming data *)
| Connection_closed
| Connection_not_ready
| Unexpected_eof
| Unable_to_renegotiate
| Unable_to_update_key
[@@deriving sexp_of]
end

module Make (Fd : Fd) : S with module Fd := Fd = struct
open Deferred.Or_error.Let_syntax

module State = struct
type t =
| Active of Tls.Engine.state
| Eof
| Error of Tls_error.t
end

type t =
{ fd : Fd.t
; mutable state : State.t
; mutable linger : Cstruct.t option
; recv_buf : Cstruct.t
}

let tls_error = Fn.compose Deferred.Or_error.error_s Tls_error.sexp_of_t

let rec read_react t =
let handle tls buf =
match Tls.Engine.handle_tls tls buf with
| Ok (state, `Response resp, `Data data) ->
t.state
<- (match state with
| `Ok tls -> Active tls
| `Eof -> Eof
| `Alert a -> Error (Tls_alert a));
let%map () =
match resp with
| None -> return ()
| Some resp -> Fd.write_full t.fd resp
in
`Ok data
| Error (alert, `Response resp) ->
t.state <- Error (Tls_failure alert);
let%bind () = Fd.write_full t.fd resp in
read_react t
in
match t.state with
| Error e -> tls_error e
| Eof -> return `Eof
| Active _ ->
let%bind n = Fd.read t.fd t.recv_buf in
(match t.state, n with
| Active _, `Eof ->
t.state <- Eof;
return `Eof
| Active tls, `Ok n -> handle tls (Cstruct.sub t.recv_buf 0 n)
| Error e, _ -> tls_error e
| Eof, _ -> return `Eof)
;;

let rec read t buf =
let writeout res =
let open Cstruct in
let rlen = len res in
let n = min (len buf) rlen in
blit res 0 buf 0 n;
t.linger <- (if n < rlen then Some (sub res n (rlen - n)) else None);
return n
in
match t.linger with
| Some res -> writeout res
| None ->
(match%bind read_react t with
| `Eof -> return 0
| `Ok None -> read t buf
| `Ok (Some res) -> writeout res)
;;

let writev t css =
match t.state with
| Error err -> tls_error err
| Eof -> tls_error Connection_closed
| Active tls ->
(match Tls.Engine.send_application_data tls css with
| Some (tls, tlsdata) ->
t.state <- Active tls;
Fd.write_full t.fd tlsdata
| None -> tls_error Connection_not_ready)
;;

(*
* XXX bad XXX
* This is a point that should particularly be protected from concurrent r/w.
* Doing this before a `t` is returned is safe; redoing it during rekeying is
* not, as the API client already sees the `t` and can mistakenly interleave
* writes while this is in progress.
* *)
let rec drain_handshake t =
let push_linger t mcs =
match mcs, t.linger with
| None, _ -> ()
| scs, None -> t.linger <- scs
| Some cs, Some l -> t.linger <- Some (Cstruct.append l cs)
in
match t.state with
| Active tls when not (Tls.Engine.handshake_in_progress tls) -> return t
| _ ->
(match%bind read_react t with
| `Eof -> tls_error Unexpected_eof
| `Ok cs ->
push_linger t cs;
drain_handshake t)
;;

let reneg ?authenticator ?acceptable_cas ?cert ?(drop = true) t =
match t.state with
| Error err -> tls_error err
| Eof -> tls_error Connection_closed
| Active tls ->
(match Tls.Engine.reneg ?authenticator ?acceptable_cas ?cert tls with
| None -> tls_error Unable_to_renegotiate
| Some (tls', buf) ->
if drop then t.linger <- None;
t.state <- Active tls';
let%bind () = Fd.write_full t.fd buf in
let%bind _ = drain_handshake t in
return ())
;;

let key_update ?request t =
match t.state with
| Error err -> tls_error err
| Eof -> tls_error Connection_closed
| Active tls ->
(match Tls.Engine.key_update ?request tls with
| Error _ -> tls_error Unable_to_update_key
| Ok (tls', buf) ->
t.state <- Active tls';
Fd.write_full t.fd buf)
;;

let close_tls t =
match t.state with
| Active tls ->
let _, buf = Tls.Engine.send_close_notify tls in
t.state <- Eof;
Fd.write_full t.fd buf
| _ -> return ()
;;

let server_of_fd config fd =
drain_handshake
{ state = Active (Tls.Engine.server config)
; fd
; linger = None
; recv_buf = Cstruct.create 4096
}
;;

let client_of_fd config ?host fd =
let config' =
match host with
| None -> config
| Some host -> Tls.Config.peer config host
in
let t = { state = Eof; fd; linger = None; recv_buf = Cstruct.create 4096 } in
let tls, init = Tls.Engine.client config' in
let t = { t with state = Active tls } in
let%bind () = Fd.write_full t.fd init in
drain_handshake t
;;

let epoch t =
match t.state with
| Active tls ->
(match Tls.Engine.epoch tls with
| `InitialEpoch -> assert false (* can never occur! *)
| `Epoch data -> Ok data)
| Eof -> Or_error.error_string "TLS state is end of file"
| Error _ -> Or_error.error_string "TLS state is error"
;;
end
6 changes: 6 additions & 0 deletions async/io.mli
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
open! Core

module type Fd = Io_intf.Fd
module type S = Io_intf.S

module Make (Fd : Fd) : S with module Fd := Fd
60 changes: 60 additions & 0 deletions async/io_intf.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
open! Core
open! Async

module type Fd = sig
type t

val read : t -> Cstruct.t -> [ `Ok of int | `Eof ] Deferred.Or_error.t
val write_full : t -> Cstruct.t -> unit Deferred.Or_error.t
end

module type S = sig
module Fd : Fd

(** Abstract type of a session *)
type t

(** {2 Constructors} *)

(** [server_of_fd server fd] is [t], after server-side TLS
handshake of [fd] using [server] configuration. *)
val server_of_fd : Tls.Config.server -> Fd.t -> t Deferred.Or_error.t

(** [client_of_fd client ~host fd] is [t], after client-side
TLS handshake of [fd] using [client] configuration and [host]. *)
val client_of_fd : Tls.Config.client -> ?host:string -> Fd.t -> t Deferred.Or_error.t

(** {2 Common stream operations} *)

(** [read t buffer] is [length], the number of bytes read into
[buffer]. *)
val read : t -> Cstruct.t -> int Deferred.Or_error.t

(** [writev t buffers] writes the [buffers] to the session. *)
val writev : t -> Cstruct.t list -> unit Deferred.Or_error.t

(** [close t] closes the TLS session by sending a close notify to the peer. *)
val close_tls : t -> unit Deferred.Or_error.t

(** [reneg ~authenticator ~acceptable_cas ~cert ~drop t] renegotiates the
session, and blocks until the renegotiation finished. Optionally, a new
[authenticator] and [acceptable_cas] can be used. The own certificate can
be adjusted by [cert]. If [drop] is [true] (the default),
application data received before the renegotiation finished is dropped. *)
val reneg
: ?authenticator:X509.Authenticator.t
-> ?acceptable_cas:X509.Distinguished_name.t list
-> ?cert:Tls.Config.own_cert
-> ?drop:bool
-> t
-> unit Deferred.Or_error.t

(** [key_update ~request t] updates the traffic key and requests a traffic key
update from the peer if [request] is provided and [true] (the default).
This is only supported in TLS 1.3. *)
val key_update : ?request:bool -> t -> unit Deferred.Or_error.t

(** [epoch t] returns [epoch], which contains information of the
active session. *)
val epoch : t -> Tls.Core.epoch_data Or_error.t
end
Loading

0 comments on commit 75e2c8e

Please sign in to comment.