open! Core open! Async include Io_intf module Tls_error = struct module Alert = struct type t = Tls.Packet.alert_type let sexp_of_t a = Sexplib.Sexp.Atom (Tls.Packet.alert_type_to_string a) end module Fail = struct type t = Tls.Engine.failure let sexp_of_t a = Sexplib.Sexp.Atom (Tls.Engine.string_of_failure a) end type t = | Tls_alert of Alert.t (** [Tls_alert] exception received from the other endpoint *) | Tls_failure of Fail.t (** [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 : string option ; recv_buf : bytes } 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, eof, `Response resp, `Data data) -> t.state <- (match eof with | None -> Active state | Some `Eof -> Eof); 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 (match alert with `Alert a -> Tls_alert a | f -> Tls_failure f); 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 (Stdlib.Bytes.sub_string t.recv_buf 0 n) | Error e, _ -> tls_error e | Eof, _ -> return `Eof) ;; let rec read t buf = let writeout res = let rlen = String.length res in let n = min (Bytes.length buf) rlen in Stdlib.Bytes.blit_string res 0 buf 0 n; t.linger <- (if n < rlen then Some (Stdlib.String.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 (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 = Bytes.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 = Bytes.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 | Ok _ as o -> o | Error () -> Or_error.error_string "no TLS state available yet") | Eof -> Or_error.error_string "TLS state is end of file" | Error _ -> Or_error.error_string "TLS state is error" ;; end