-
Notifications
You must be signed in to change notification settings - Fork 68
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #432 from torinnd/main
Async implementation, including examples
- Loading branch information
Showing
12 changed files
with
618 additions
and
0 deletions.
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,5 @@ | ||
(library | ||
(name tls_async) | ||
(public_name tls-async) | ||
(preprocess (pps ppx_jane)) | ||
(libraries async core cstruct-async mirage-crypto-rng-async 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,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)) |
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,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 |
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,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 |
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,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 |
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,6 @@ | ||
open! Core | ||
|
||
module type Fd = Io_intf.Fd | ||
module type S = Io_intf.S | ||
|
||
module Make (Fd : Fd) : S with module Fd := Fd |
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,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 |
Oops, something went wrong.