Skip to content

Commit

Permalink
TLS tunnel over Lwt_io.channel
Browse files Browse the repository at this point in the history
  • Loading branch information
art-w committed Aug 29, 2024
1 parent aee602d commit 7c00878
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 26 deletions.
3 changes: 3 additions & 0 deletions src/conduit-lwt-unix/conduit_lwt_tls.dummy.ml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ end
module Client = struct
let connect ?src:_ ?certificates:_ ~authenticator:_ _host _sa =
failwith "Tls not available"

let tunnel ?certificates:_ ~authenticator:_ _host _ioc =
failwith "Tls not available"
end

module Server = struct
Expand Down
7 changes: 7 additions & 0 deletions src/conduit-lwt-unix/conduit_lwt_tls.dummy.mli
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ module Client : sig
[ `host ] Domain_name.t ->
Lwt_unix.sockaddr ->
(Lwt_unix.file_descr * Lwt_io.input_channel * Lwt_io.output_channel) Lwt.t

val tunnel :
?certificates:'a ->
authenticator:X509.authenticator ->
[ `host ] Domain_name.t ->
Lwt_io.input_channel * Lwt_io.output_channel ->
(Lwt_io.input_channel * Lwt_io.output_channel) Lwt.t
end

module Server : sig
Expand Down
22 changes: 15 additions & 7 deletions src/conduit-lwt-unix/conduit_lwt_tls.real.ml
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,27 @@ module X509 = struct
end

module Client = struct
let config ?certificates authenticator =
match Tls.Config.client ~authenticator ?certificates () with
| Error (`Msg msg) -> failwith ("tls configuration problem: " ^ msg)
| Ok config -> config

let connect ?src ?certificates ~authenticator host sa =
Conduit_lwt_server.with_socket sa (fun fd ->
(match src with
| None -> Lwt.return_unit
| Some src_sa -> Lwt_unix.bind fd src_sa)
>>= fun () ->
match Tls.Config.client ~authenticator ?certificates () with
| Error (`Msg msg) -> failwith ("tls configuration problem: " ^ msg)
| Ok config ->
Lwt_unix.connect fd sa >>= fun () ->
Tls_lwt.Unix.client_of_fd config ~host fd >|= fun t ->
let ic, oc = Tls_lwt.of_t t in
(fd, ic, oc))
let config = config ?certificates authenticator in
Lwt_unix.connect fd sa >>= fun () ->
Tls_lwt.Unix.client_of_fd config ~host fd >|= fun t ->
let ic, oc = Tls_lwt.of_t t in
(fd, ic, oc))

let tunnel ?certificates ~authenticator host channels =
let config = config ?certificates authenticator in
Tls_lwt.Unix.client_of_channels config ~host channels >|= fun t ->
Tls_lwt.of_t t
end

module Server = struct
Expand Down
7 changes: 7 additions & 0 deletions src/conduit-lwt-unix/conduit_lwt_tls.real.mli
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ module Client : sig
[ `host ] Domain_name.t ->
Lwt_unix.sockaddr ->
(Lwt_unix.file_descr * Lwt_io.input_channel * Lwt_io.output_channel) Lwt.t

val tunnel :
?certificates:Tls.Config.own_cert ->
authenticator:X509.authenticator ->
[ `host ] Domain_name.t ->
Lwt_io.input_channel * Lwt_io.output_channel ->
(Lwt_io.input_channel * Lwt_io.output_channel) Lwt.t
end

module Server : sig
Expand Down
46 changes: 30 additions & 16 deletions src/conduit-lwt-unix/conduit_lwt_unix.ml
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,16 @@ let () =
(Sexplib0.Sexp.to_string (sexp_of_tls_lib !tls_library))

type +'a io = 'a Lwt.t
type ic = Lwt_io.input_channel
type oc = Lwt_io.output_channel
type ic = (Lwt_io.input_channel[@sexp.opaque]) [@@deriving sexp]
type oc = (Lwt_io.output_channel[@sexp.opaque]) [@@deriving sexp]

type client_tls_config =
[ `Hostname of string ] * [ `IP of Ipaddr_sexp.t ] * [ `Port of int ]
[@@deriving sexp]

type client =
[ `TLS of client_tls_config
| `TLS_tunnel of [ `Hostname of string ] * ic * oc
| `TLS_native of client_tls_config
| `OpenSSL of client_tls_config
| `TCP of [ `IP of Ipaddr_sexp.t ] * [ `Port of int ]
Expand Down Expand Up @@ -140,6 +141,7 @@ type vchan_flow = { domid : int; port : string } [@@deriving sexp]

type flow =
| TCP of tcp_flow
| Tunnel
| Domain_socket of domain_flow
| Vchan of vchan_flow
[@@deriving sexp]
Expand Down Expand Up @@ -262,31 +264,41 @@ let set_max_active maxactive = Conduit_lwt_server.set_max_active maxactive

(** TLS client connection functions *)

let connect_with_tls_native ~ctx (`Hostname hostname, `IP ip, `Port port) =
let sa = Unix.ADDR_INET (Ipaddr_unix.to_inet_addr ip, port) in
(match ctx.tls_own_key with
let certificates ~ctx =
match ctx.tls_own_key with
| `None -> Lwt.return_none
| `TLS (_, _, `Password _) ->
failwith "OCaml-TLS cannot handle encrypted pem files"
| `TLS (`Crt_file_path cert, `Key_file_path priv_key, `No_password) ->
Conduit_lwt_tls.X509.private_of_pems ~cert ~priv_key
>|= fun certificate -> Some (`Single certificate))
>>= fun certificates ->
let hostname =
try Domain_name.(host_exn (of_string_exn hostname))
with Invalid_argument msg ->
let s =
Printf.sprintf "couldn't convert %s to a [`host] Domain_name.t: %s"
hostname msg
in
invalid_arg s
in
>|= fun certificate -> Some (`Single certificate)

let domain_name hostname =
try Domain_name.(host_exn (of_string_exn hostname))
with Invalid_argument msg ->
let s =
Printf.sprintf "couldn't convert %s to a [`host] Domain_name.t: %s"
hostname msg
in
invalid_arg s

let connect_with_tls_native ~ctx (`Hostname hostname, `IP ip, `Port port) =
let sa = Unix.ADDR_INET (Ipaddr_unix.to_inet_addr ip, port) in
certificates ~ctx >>= fun certificates ->
let hostname = domain_name hostname in
Conduit_lwt_tls.Client.connect ?src:ctx.src ?certificates
~authenticator:ctx.tls_authenticator hostname sa
>|= fun (fd, ic, oc) ->
let flow = TCP { fd; ip; port } in
(flow, ic, oc)

let connect_with_tls_tunnel ~ctx (`Hostname hostname, ic, oc) =
certificates ~ctx >>= fun certificates ->
let hostname = domain_name hostname in
Conduit_lwt_tls.Client.tunnel ?certificates
~authenticator:ctx.tls_authenticator hostname (ic, oc)
>|= fun (ic, oc) -> (Tunnel, ic, oc)

let connect_with_openssl ~ctx (`Hostname host_addr, `IP ip, `Port port) =
let sa = Unix.ADDR_INET (Ipaddr_unix.to_inet_addr ip, port) in
let ctx_ssl =
Expand Down Expand Up @@ -331,6 +343,7 @@ let connect ~ctx (mode : client) =
let flow = Domain_socket { fd; path } in
Lwt.return (flow, ic, oc)
| `TLS c -> connect_with_default_tls ~ctx c
| `TLS_tunnel c -> connect_with_tls_tunnel ~ctx c
| `OpenSSL c -> connect_with_openssl ~ctx c
| `TLS_native c -> connect_with_tls_native ~ctx c
| `Vchan_direct _ -> failwith "Vchan_direct not available on unix"
Expand Down Expand Up @@ -416,6 +429,7 @@ let serve ?backlog ?timeout ?stop ~on_exn ~(ctx : ctx) ~(mode : server) callback

let endp_of_flow = function
| TCP { ip; port; _ } -> `TCP (ip, port)
| Tunnel -> `Unknown "TLS tunnel"
| Domain_socket { path; _ } -> `Unix_domain_socket path
| Vchan { domid; port } -> `Vchan_direct (domid, port)

Expand Down
7 changes: 5 additions & 2 deletions src/conduit-lwt-unix/conduit_lwt_unix.mli
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ type client_tls_config =

type client =
[ `TLS of client_tls_config
| `TLS_tunnel of
[ `Hostname of string ] * Lwt_io.input_channel * Lwt_io.output_channel
| `TLS_native of client_tls_config
(** Force use of native OCaml TLS stack to connect.*)
| `OpenSSL of client_tls_config
Expand Down Expand Up @@ -104,8 +106,8 @@ type server =
documentation for more. *)

type 'a io = 'a Lwt.t
type ic = Lwt_io.input_channel
type oc = Lwt_io.output_channel
type ic = (Lwt_io.input_channel[@sexp.opaque]) [@@deriving sexp]
type oc = (Lwt_io.output_channel[@sexp.opaque]) [@@deriving sexp]

type tcp_flow = private {
fd : Lwt_unix.file_descr; [@sexp.opaque]
Expand All @@ -129,6 +131,7 @@ type vchan_flow = private { domid : int; port : string } [@@deriving sexp_of]
transport method. *)
type flow = private
| TCP of tcp_flow
| Tunnel
| Domain_socket of domain_flow
| Vchan of vchan_flow
[@@deriving sexp_of]
Expand Down
2 changes: 1 addition & 1 deletion tests/conduit-lwt-unix/dune
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
(executables
(libraries lwt_ssl ssl conduit-lwt-unix lwt_log)
(names cdtest cdtest_tls exit_test))
(names cdtest cdtest_tls exit_test tls_over_tls))

(rule
(alias runtest)
Expand Down
61 changes: 61 additions & 0 deletions tests/conduit-lwt-unix/tls_over_tls.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
open Lwt.Infix

let hostname = "mirage.io"

(* To test TLS-over-TLS, the `squid` proxy can be installed locally and configured to support HTTPS:
- Generate a certificate for localhost: https://gist.github.com/cecilemuller/9492b848eb8fe46d462abeb26656c4f8
$ openssl req -x509 -nodes -new -sha256 -days 1024 -newkey rsa:2048 -keyout RootCA.key -out RootCA.pem -subj "/C=US/CN=Example-Root-CA"
$ openssl x509 -outform pem -in RootCA.pem -out RootCA.crt
$ cat > domains.ext
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
$ openssl req -new -nodes -newkey rsa:2048 -keyout localhost.key -out localhost.csr -subj "/C=US/ST=YourState/L=YourCity/O=Example-Certificates/CN=localhost.local"
$ openssl x509 -req -sha256 -days 1024 -in localhost.csr -CA RootCA.pem -CAkey RootCA.key -CAcreateserial -extfile domains.ext -out localhost.crt
- Configure squid by adding HTTPS support on port 3129 in /etc/squid/squid.conf :
https_port 3129 tls-cert=/path/to/localhost.crt tls-key=/path/to/localhost.key
*)

let proxy =
`TLS
(`Hostname "localhost", `IP (Ipaddr.of_string_exn "127.0.0.1"), `Port 3129)

let string_prefix ~prefix msg =
let len = String.length prefix in
String.length msg >= len && String.sub msg 0 len = prefix

let main () =
let ctx = Lazy.force Conduit_lwt_unix.default_ctx in
Conduit_lwt_unix.connect ~ctx proxy >>= fun (_flow, ic, oc) ->
let req =
String.concat "\r\n"
[ "CONNECT " ^ hostname ^ ":443 HTTP/1.1"; "Host: " ^ hostname; ""; "" ]
in
Lwt_io.write oc req >>= fun () ->
let rec try_read () =
Lwt_io.read ic ~count:1024 >>= fun msg ->
if msg = "" then try_read () else Lwt.return msg
in
try_read () >>= fun msg ->
assert (string_prefix ~prefix:"HTTP/1.1 200 " msg);

(* We are now connected to mirage.io:443 through the proxy *)
let client = `TLS_tunnel (`Hostname hostname, ic, oc) in
Conduit_lwt_unix.connect ~ctx client >>= fun (_flow, ic, oc) ->
let req =
String.concat "\r\n" [ "GET / HTTP/1.1"; "Host: " ^ hostname; ""; "" ]
in
Lwt_io.write oc req >>= fun () ->
Lwt_io.read ic ~count:4096 >>= fun msg ->
Lwt_io.print msg >>= fun () ->
Lwt_io.read ic ~count:4096 >>= fun msg ->
Lwt_io.print msg >>= fun () -> Lwt_io.print "\n"

let () = Lwt_main.run (main ())

0 comments on commit 7c00878

Please sign in to comment.