Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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