diff --git a/async/conduit_async.ml b/async/conduit_async.ml index 1ed56c9b..b319fda2 100644 --- a/async/conduit_async.ml +++ b/async/conduit_async.ml @@ -15,110 +15,8 @@ * *) -open Core -open Async +module V1 = V1 +module V2 = V2 -type +'a io = 'a Deferred.t -type ic = Reader.t -type oc = Writer.t - -type addr = [ - | `OpenSSL of Ipaddr.t * int * Conduit_async_ssl.Ssl_config.t - | `TCP of Ipaddr.t * int - | `Unix_domain_socket of string -] [@@deriving sexp] - -let connect ?interrupt dst = - match dst with - | `TCP (ip, port) -> - let endp = Host_and_port.create ~host:(Ipaddr.to_string ip) ~port in - Tcp.connect ?interrupt (Tcp.Where_to_connect.of_host_and_port endp) - >>= fun (_, rd, wr) -> return (rd,wr) - | `OpenSSL (ip, port, cfg) -> - let endp = Host_and_port.create ~host:(Ipaddr.to_string ip) ~port in - Tcp.connect ?interrupt (Tcp.Where_to_connect.of_host_and_port endp) - >>= fun (_, rd, wr) -> - Conduit_async_ssl.ssl_connect ~cfg rd wr - | `Unix_domain_socket file -> - Tcp.connect ?interrupt (Tcp.Where_to_connect.of_file file) - >>= fun (_, rd, wr) -> - return (rd,wr) - -let with_connection ?interrupt dst f = - match dst with - | `TCP (ip, port) -> - let endp = Host_and_port.create ~host:(Ipaddr.to_string ip) ~port in - Tcp.with_connection ?interrupt - (Tcp.Where_to_connect.of_host_and_port endp) - (fun _ rd wr -> f rd wr) - | `OpenSSL (ip, port, cfg) -> - let endp = Host_and_port.create ~host:(Ipaddr.to_string ip) ~port in - Tcp.with_connection ?interrupt - (Tcp.Where_to_connect.of_host_and_port endp) - begin fun _ rd wr -> - Conduit_async_ssl.ssl_connect ~cfg rd wr >>= fun (rd, wr) -> - Monitor.protect (fun () -> f rd wr) ~finally:begin fun () -> - Deferred.all_unit [ Reader.close rd ; Writer.close wr ] - end - end - | `Unix_domain_socket file -> - Tcp.with_connection ?interrupt (Tcp.Where_to_connect.of_file file) - (fun _ rd wr -> f rd wr) - -type trust_chain = [ - | `Ca_file of string - | `Ca_path of string - | `Search_file_first_then_path of - [ `File of string ] * - [ `Path of string ] -] [@@deriving sexp] - -type openssl = [ - | `OpenSSL of - [ `Crt_file_path of string ] * - [ `Key_file_path of string ] -] [@@deriving sexp] - -type requires_async_ssl = [ - | openssl - | `OpenSSL_with_trust_chain of openssl * trust_chain -] [@@deriving sexp] - -type server = [ - | `TCP - | requires_async_ssl -] [@@deriving sexp] - -let serve - ?max_connections ?backlog - ?buffer_age_limit ~on_handler_error mode where_to_listen handle_request = - let handle_client handle_request sock rd wr = - match mode with - | `TCP -> handle_request sock rd wr - | #requires_async_ssl as async_ssl -> - let (crt_file, key_file, ca_file, ca_path) = - match async_ssl with - | `OpenSSL (`Crt_file_path crt_file, `Key_file_path key_file) -> - (crt_file, key_file, None, None) - | `OpenSSL_with_trust_chain - (`OpenSSL (`Crt_file_path crt, `Key_file_path key), trust_chain) -> - let (ca_file, ca_path) = - match trust_chain with - | `Ca_file ca_file -> (Some ca_file, None) - | `Ca_path ca_path -> (None, Some ca_path) - | `Search_file_first_then_path (`File ca_file, `Path ca_path) -> - (Some ca_file, Some ca_path) - in - (crt, key, ca_file, ca_path) - in - let cfg = Conduit_async_ssl.Ssl_config.create - ?ca_file ?ca_path ~crt_file ~key_file () in - Conduit_async_ssl.ssl_listen cfg rd wr >>= fun (rd,wr) -> - Monitor.protect - (fun () -> handle_request sock rd wr) - ~finally:(fun () -> - Deferred.all_unit [ Reader.close rd ; Writer.close wr ]) - in - Tcp.Server.create ?max_connections ?backlog - ?buffer_age_limit ~on_handler_error - where_to_listen (handle_client handle_request) +[@@@deprecated "Use Conduit_async.V1"] +include V1.Conduit_async diff --git a/async/conduit_async.mli b/async/conduit_async.mli deleted file mode 100644 index 0e0e6813..00000000 --- a/async/conduit_async.mli +++ /dev/null @@ -1,67 +0,0 @@ -(* - * Copyright (c) 2012-2017 Anil Madhavapeddy - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * -*) - -(** Connection establishment using the - {{:https://github.com/janestreet/async}Async} library *) - -open Async - -type +'a io = 'a Deferred.t -type ic = Reader.t -type oc = Writer.t - -type addr = [ - | `OpenSSL of Ipaddr.t * int * Conduit_async_ssl.Ssl_config.t - | `TCP of Ipaddr.t * int - | `Unix_domain_socket of string -] [@@deriving sexp] - -val connect : ?interrupt:unit io -> addr -> (ic * oc) io -val with_connection : ?interrupt:unit io -> addr -> (ic -> oc -> unit io) -> unit io - -type trust_chain = - [ `Ca_file of string - | `Ca_path of string - | `Search_file_first_then_path of - [ `File of string ] * - [ `Path of string ] - ] [@@deriving sexp] - -type openssl = - [ `OpenSSL of - [ `Crt_file_path of string ] * - [ `Key_file_path of string ] - ] [@@deriving sexp] - -type server = [ - | openssl - | `TCP - | `OpenSSL_with_trust_chain of - (openssl * trust_chain) -] [@@deriving sexp] - -val serve : - ?max_connections:int -> - ?backlog:int -> - ?buffer_age_limit:Writer.buffer_age_limit -> - on_handler_error:[ `Call of ([< Socket.Address.t ] as 'a) -> exn -> unit - | `Ignore - | `Raise ] -> - server -> - ('a, 'b) Tcp.Where_to_listen.t -> - ('a -> ic -> oc -> unit io) -> - ('a, 'b) Tcp.Server.t io diff --git a/async/conduit_async_ssl_dummy.ml b/async/conduit_async_ssl_dummy.ml deleted file mode 100644 index 17b6dd8f..00000000 --- a/async/conduit_async_ssl_dummy.ml +++ /dev/null @@ -1,46 +0,0 @@ -(* - * Copyright (c) 2012-2017 Anil Madhavapeddy - * Copyright (c) 2014 Clark Gaebel - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * -*) - -module Ssl_config = struct - type t = [`Ssl_not_compiled_in] - - let verify_certificate _ = - failwith "Ssl not available, recompile with Async_ssl" - - let create - ?version:_ - ?options:_ - ?name:_ - ?hostname:_ - ?allowed_ciphers:_ - ?ca_file:_ - ?ca_path:_ - ?crt_file:_ - ?key_file:_ - ?session:_ - ?verify_modes:_ - ?verify:_ - () = - failwith "Ssl not available, recompile with Async_ssl" -end - -let ssl_connect ?cfg:_ _r _w = - failwith "Ssl not available, recompile with Async_ssl" - -let ssl_listen _cfg _r _w = - failwith "Ssl not available, recompile with Async_ssl" diff --git a/async/conduit_async_ssl_dummy.mli b/async/conduit_async_ssl_dummy.mli deleted file mode 100644 index 0f10f5eb..00000000 --- a/async/conduit_async_ssl_dummy.mli +++ /dev/null @@ -1,62 +0,0 @@ -(* - * Copyright (c) 2012-2017 Anil Madhavapeddy - * Copyright (c) 2014 Clark Gaebel - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * -*) - -(** TLS/SSL connection establishment using OpenSSL and Async *) -open Async - -module Ssl_config : sig - type t - - val verify_certificate : - [`Ssl_not_compiled_in] -> - bool Deferred.t - - val create : - ?version:[`Ssl_not_compiled_in] -> - ?options:[`Ssl_not_compiled_in] -> - ?name:string -> - ?hostname:string -> - ?allowed_ciphers:[ `Only of string list | `Openssl_default | `Secure ] -> - ?ca_file:string -> - ?ca_path:string -> - ?crt_file:string -> - ?key_file:string -> - ?session:[`Ssl_not_compiled_in] -> - ?verify_modes:[`Ssl_not_compiled_in] -> - ?verify:[`Ssl_not_compiled_in] -> - unit -> t -end - -(** [ssl_connect rd wr] will establish a client TLS/SSL session - over an existing pair of a [rd] {!Reader.t} and [wd] {!Writer.t} - Async connections. *) -val ssl_connect : - Ssl_config.t -> Reader.t -> Writer.t -> (Reader.t * Writer.t) Deferred.t - -(** [ssl_listen ~crt_file ~key_file rd wr] will establish a server - TLS/SSL session over an existing pair of [rd] {!Reader.t} and - [wd] {!Writer.t} Async connections. - - [version] is the version of SSL being used by the server. If not - set, it is [Ssl.Version.Tlsv1_2]. - - From [Async_ssl.Std.Ssl]: If both [ca_file] and [ca_path] are specified, - the certificates in [ca_file] will be searched before the certificates in - [ca_path].*) -val ssl_listen : - ?cfg:Ssl_config.t -> Reader.t -> Writer.t -> (Reader.t * Writer.t) Deferred.t diff --git a/async/conduit_async_ssl_real.ml b/async/conduit_async_ssl_real.ml deleted file mode 100644 index 33bff193..00000000 --- a/async/conduit_async_ssl_real.ml +++ /dev/null @@ -1,145 +0,0 @@ -(* - * Copyright (c) 2012-2017 Anil Madhavapeddy - * Copyright (c) 2014 Clark Gaebel - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * -*) - -open Core -open Async -open Async_ssl - -module Ssl_config = struct - type t = { - version : Ssl.Version.t option; - options: Ssl.Opt.t list option; - name : string option; - hostname : string option; - allowed_ciphers: [ `Only of string list | `Openssl_default | `Secure ] option; - ca_file : string option; - ca_path : string option; - crt_file : string option; - key_file : string option; - session : Ssl.Session.t option; - verify_modes:Verify_mode.t list option; - verify : (Ssl.Connection.t -> bool Deferred.t) option; - } [@@deriving sexp] - - let verify_certificate connection = - match Ssl.Connection.peer_certificate connection with - | None -> return false - | Some (Error _) -> return false - | Some (Ok _) -> return true - - let create - ?version ?options ?name ?hostname ?allowed_ciphers - ?ca_file ?ca_path ?crt_file ?key_file - ?session ?verify_modes ?verify () = - { version; options; name; hostname; allowed_ciphers; - ca_file; ca_path; crt_file; key_file; session; verify_modes; - verify} -end - -let ssl_connect ?(cfg=Ssl_config.create ()) r w = - let { Ssl_config.version; options; name; hostname; - allowed_ciphers; ca_file; ca_path; - crt_file; key_file; session; verify_modes; verify } = cfg in - let net_to_ssl = Reader.pipe r in - let ssl_to_net = Writer.pipe w in - let app_to_ssl, app_wr = Pipe.create () in - let app_rd, ssl_to_app = Pipe.create () in - let verify_connection = match verify with - | None -> Fn.const (return true) - | Some f -> f - in - Ssl.client - ?version - ?options - ?name - ?hostname - ?allowed_ciphers - ?ca_file - ?ca_path - ?crt_file - ?key_file - ?session - ?verify_modes - ~app_to_ssl - ~ssl_to_app - ~net_to_ssl - ~ssl_to_net - () - |> Deferred.Or_error.ok_exn - >>= fun conn -> - verify_connection conn >>= function - | false -> - Ssl.Connection.close conn ; - Pipe.close_read app_rd ; - Writer.close w >>= fun () -> - failwith "Connection verification failed." - | true -> - Reader.of_pipe (Info.of_string "async_conduit_ssl_reader") app_rd >>= fun app_reader -> - Writer.of_pipe (Info.of_string "async_conduit_ssl_writer") app_wr >>| fun (app_writer,_) -> - don't_wait_for begin - Deferred.all_unit [ - Writer.close_finished app_writer ; - Reader.close_finished app_reader ; - ] >>= fun () -> - Ssl.Connection.close conn ; - Pipe.close_read app_rd ; - Writer.close w ; - end ; - (app_reader, app_writer) - -let ssl_listen - { Ssl_config.version; options; name; allowed_ciphers; ca_file; ca_path; - crt_file; key_file; verify_modes ; _ } r w = - let crt_file, key_file = - match crt_file, key_file with - | Some crt_file, Some key_file -> crt_file, key_file - | _ -> invalid_arg "Conduit_async_ssl.ssl_listen: crt_file and \ - key_file must be specified in cfg." in - let net_to_ssl = Reader.pipe r in - let ssl_to_net = Writer.pipe w in - let app_to_ssl, app_wr = Pipe.create () in - let app_rd, ssl_to_app = Pipe.create () in - Ssl.server - ?version - ?options - ?name - ?allowed_ciphers - ?ca_file - ?ca_path - ~crt_file - ~key_file - ?verify_modes - ~app_to_ssl - ~ssl_to_app - ~net_to_ssl - ~ssl_to_net - () - |> Deferred.Or_error.ok_exn - >>= fun conn -> - Reader.of_pipe (Info.of_string "async_conduit_ssl_reader") app_rd >>= fun app_reader -> - Writer.of_pipe (Info.of_string "async_conduit_ssl_writer") app_wr >>| fun (app_writer,_) -> - don't_wait_for begin - Deferred.all_unit [ - Reader.close_finished app_reader; - Writer.close_finished app_writer - ] >>= fun () -> - Ssl.Connection.close conn ; - Pipe.close_read app_rd ; - Writer.close w ; - end; - (app_reader, app_writer) diff --git a/async/conduit_async_ssl_real.mli b/async/conduit_async_ssl_real.mli deleted file mode 100644 index c1fc42e3..00000000 --- a/async/conduit_async_ssl_real.mli +++ /dev/null @@ -1,63 +0,0 @@ -(* - * Copyright (c) 2012-2017 Anil Madhavapeddy - * Copyright (c) 2014 Clark Gaebel - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * -*) - -(** TLS/SSL connection establishment using OpenSSL and Async *) -open Async -open Async_ssl - -module Ssl_config : sig - type t - - val verify_certificate : - Ssl.Connection.t -> - bool Deferred.t - - val create : - ?version:Ssl.Version.t -> - ?options:Ssl.Opt.t list -> - ?name:string -> - ?hostname:string -> - ?allowed_ciphers:[ `Only of string list | `Openssl_default | `Secure ] -> - ?ca_file:string -> - ?ca_path:string -> - ?crt_file:string -> - ?key_file:string -> - ?session:Ssl.Session.t -> - ?verify_modes:Verify_mode.t list -> - ?verify:(Ssl.Connection.t -> bool Deferred.t) -> - unit -> t -end - -(** [ssl_connect rd wr] will establish a client TLS/SSL session - over an existing pair of a [rd] {!Reader.t} and [wd] {!Writer.t} - Async connections. *) -val ssl_connect : - ?cfg:Ssl_config.t -> Reader.t -> Writer.t -> (Reader.t * Writer.t) Deferred.t - -(** [ssl_listen cfg rd wr] will establish a server - TLS/SSL session over an existing pair of [rd] {!Reader.t} and - [wd] {!Writer.t} Async connections. - - [version] is the version of SSL being used by the server. If not - set, it is [Ssl.Version.Tlsv1_2]. - - From [Async_ssl.Std.Ssl]: If both [ca_file] and [ca_path] are specified, - the certificates in [ca_file] will be searched before the certificates in - [ca_path].*) -val ssl_listen : - Ssl_config.t -> Reader.t -> Writer.t -> (Reader.t * Writer.t) Deferred.t diff --git a/async/jbuild b/async/jbuild index e9dd2b3e..5a042e0d 100644 --- a/async/jbuild +++ b/async/jbuild @@ -1,14 +1,21 @@ (jbuild_version 1) + (library ((name conduit_async) (public_name conduit-async) - (wrapped false) - (modules (conduit_async conduit_async_ssl)) - (libraries (conduit async - (select conduit_async_ssl.ml from - (async_ssl -> conduit_async_ssl_real.ml) - (!async_ssl -> conduit_async_ssl_dummy.ml)) - (select conduit_async_ssl.mli from - (async_ssl -> conduit_async_ssl_real.mli) - (!async_ssl -> conduit_async_ssl_dummy.mli)) - )))) + (modules (conduit_async private_ssl v1 v2 s)) + (preprocess (pps (ppx_sexp_conv))) + (libraries + (conduit async + + (select private_ssl.ml from + (async_ssl -> private_ssl_real.ml) + (!async_ssl -> private_ssl_dummy.ml)) + + (select v1.mli from + (async_ssl -> v1_real.mli) + (!async_ssl -> v1_dummy.mli)) + + (select v2.mli from + (async_ssl -> v2_real.mli) + (!async_ssl -> v2_dummy.mli)))))) diff --git a/async/private_ssl_dummy.ml b/async/private_ssl_dummy.ml new file mode 100644 index 00000000..2b2a4047 --- /dev/null +++ b/async/private_ssl_dummy.ml @@ -0,0 +1,65 @@ +open Core + +module V1 = struct + module Ssl = struct + module Config = struct + type t = [`Ssl_not_compiled_in] [@@deriving sexp] + + let verify_certificate _ = + failwith "Ssl not available, recompile with Async_ssl" + + let create ?version:_ ?name:_ ?ca_file:_ ?ca_path:_ ?session:_ ?verify:_ + () = failwith "Ssl not available, recompile with Async_ssl" + end + + let connect _cfg _r _w = + failwith "Ssl not available, recompile with Async_ssl" + + let listen ?version:_ ?ca_file:_ ?ca_path:_ ~crt_file:_ ~key_file:_ _ _ = + failwith "Ssl not available, recompile with Async_ssl" + + type session = [`Ssl_not_compiled_in] [@@deriving sexp] + type version = [`Ssl_not_compiled_in] [@@deriving sexp] + type connection = [`Ssl_not_compiled_in] [@@deriving sexp] + end +end + +module V2 = struct + module Ssl = struct + module Config = struct + type t = [`Ssl_not_compiled_in] [@@deriving sexp] + + let verify_certificate _ = + failwith "Ssl not available, recompile with Async_ssl" + + let create + ?version:_ + ?options:_ + ?name:_ + ?hostname:_ + ?allowed_ciphers:_ + ?ca_file:_ + ?ca_path:_ + ?crt_file:_ + ?key_file:_ + ?session:_ + ?verify_modes:_ + ?verify:_ + () = + failwith "Ssl not available, recompile with Async_ssl" + end + + let connect ?cfg _r _w = + failwith "Ssl not available, recompile with Async_ssl" + + let listen ?cfg _r _w = + failwith "Ssl not available, recompile with Async_ssl" + + type version = [`Ssl_not_compiled_in] [@@deriving sexp] + type session = [`Ssl_not_compiled_in] [@@deriving sexp] + type verify = [`Ssl_not_compiled_in] [@@deriving sexp] + type connection = [`Ssl_not_compiled_in] [@@deriving sexp] + type verify_mode = [`Ssl_not_compiled_in] [@@deriving sexp] + type opt = [`Ssl_not_compiled_in] [@@deriving sexp] + end +end diff --git a/async/private_ssl_real.ml b/async/private_ssl_real.ml new file mode 100644 index 00000000..9b69734f --- /dev/null +++ b/async/private_ssl_real.ml @@ -0,0 +1,241 @@ +open Core +open Async +open Async_ssl + +let verify_certificate connection = + match Ssl.Connection.peer_certificate connection with + | None -> return false + | Some (Error _) -> return false + | Some (Ok _) -> return true + +module V1 = struct + module Ssl = struct + module Config = struct + type t = { + version : Ssl.Version.t option; + name : string option; + ca_file : string option; + ca_path : string option; + session : Ssl.Session.t option sexp_opaque; + verify : (Ssl.Connection.t -> bool Deferred.t) option; + } [@@deriving sexp] + + let verify_certificate = verify_certificate + + let create ?version ?name ?ca_file ?ca_path ?session ?verify () = + { version; name; ca_file; ca_path; session; verify} + end + + let connect cfg r w = + let {Config.version; name; ca_file; ca_path; session; verify} = cfg in + let net_to_ssl = Reader.pipe r in + let ssl_to_net = Writer.pipe w in + let app_to_ssl, app_wr = Pipe.create () in + let app_rd, ssl_to_app = Pipe.create () in + let verify_connection = match verify with + | None -> Fn.const (return true) + | Some f -> f + in + Ssl.client + ?version + ?name + ?ca_file + ?ca_path + ?session + ~app_to_ssl + ~ssl_to_app + ~net_to_ssl + ~ssl_to_net + () + |> Deferred.Or_error.ok_exn + >>= fun conn -> + verify_connection conn >>= function + | false -> + Ssl.Connection.close conn ; + Pipe.close_read app_rd ; + Writer.close w >>= fun () -> + failwith "Connection verification failed." + | true -> + Reader.of_pipe (Info.of_string "async_conduit_ssl_reader") app_rd >>= fun app_reader -> + Writer.of_pipe (Info.of_string "async_conduit_ssl_writer") app_wr >>| fun (app_writer,_) -> + don't_wait_for begin + Deferred.all_unit [ + Writer.close_finished app_writer ; + Reader.close_finished app_reader ; + ] >>= fun () -> + Ssl.Connection.close conn ; + Pipe.close_read app_rd ; + Writer.close w ; + end ; + (app_reader, app_writer) + + let listen ?(version=Ssl.Version.Tlsv1_2) ?ca_file ?ca_path ~crt_file ~key_file r w = + let net_to_ssl = Reader.pipe r in + let ssl_to_net = Writer.pipe w in + let app_to_ssl, app_wr = Pipe.create () in + let app_rd, ssl_to_app = Pipe.create () in + Ssl.server + ?ca_file + ?ca_path + ~version + ~crt_file + ~key_file + ~app_to_ssl + ~ssl_to_app + ~net_to_ssl + ~ssl_to_net + () + |> Deferred.Or_error.ok_exn + >>= fun conn -> + Reader.of_pipe (Info.of_string "async_conduit_ssl_reader") app_rd >>= fun app_reader -> + Writer.of_pipe (Info.of_string "async_conduit_ssl_writer") app_wr >>| fun (app_writer,_) -> + don't_wait_for begin + Deferred.all_unit [ + Reader.close_finished app_reader; + Writer.close_finished app_writer + ] >>= fun () -> + Ssl.Connection.close conn ; + Pipe.close_read app_rd ; + Writer.close w ; + end; + (app_reader, app_writer) + + type session = Ssl.Session.t sexp_opaque [@@deriving sexp] + type version = Ssl.Version.t [@@deriving sexp] + type connection = Ssl.Connection.t sexp_opaque [@@deriving sexp] + end +end + +module V2 = struct + module Ssl = struct + type allowed_ciphers = + [ `Only of string list | `Openssl_default | `Secure ] + [@@deriving sexp] + + module Config = struct + type t = { + version : Ssl.Version.t option; + options: Ssl.Opt.t list option; + name : string option; + hostname : string option; + allowed_ciphers: allowed_ciphers option; + ca_file : string option; + ca_path : string option; + crt_file : string option; + key_file : string option; + session : Ssl.Session.t option sexp_opaque; + verify_modes:Verify_mode.t sexp_opaque list option; + verify : (Ssl.Connection.t -> bool Deferred.t) option; + } [@@deriving sexp_of] + + let verify_certificate = verify_certificate + + let create + ?version ?options ?name ?hostname ?allowed_ciphers + ?ca_file ?ca_path ?crt_file ?key_file + ?session ?verify_modes ?verify () = + { version; options; name; hostname; allowed_ciphers; + ca_file; ca_path; crt_file; key_file; session; verify_modes; + verify} + end + + let connect ?(cfg=Config.create ()) r w = + let { Config.version; options; name; hostname; + allowed_ciphers; ca_file; ca_path; + crt_file; key_file; session; verify_modes; verify } = cfg in + let net_to_ssl = Reader.pipe r in + let ssl_to_net = Writer.pipe w in + let app_to_ssl, app_wr = Pipe.create () in + let app_rd, ssl_to_app = Pipe.create () in + let verify_connection = match verify with + | None -> Fn.const (return true) + | Some f -> f + in + Ssl.client + ?version + ?options + ?name + ?hostname + ?allowed_ciphers + ?ca_file + ?ca_path + ?crt_file + ?key_file + ?session + ?verify_modes + ~app_to_ssl + ~ssl_to_app + ~net_to_ssl + ~ssl_to_net + () + |> Deferred.Or_error.ok_exn + >>= fun conn -> + verify_connection conn >>= function + | false -> + Ssl.Connection.close conn ; + Pipe.close_read app_rd ; + Writer.close w >>= fun () -> + failwith "Connection verification failed." + | true -> + Reader.of_pipe (Info.of_string "async_conduit_ssl_reader") app_rd >>= fun app_reader -> + Writer.of_pipe (Info.of_string "async_conduit_ssl_writer") app_wr >>| fun (app_writer,_) -> + don't_wait_for begin + Deferred.all_unit [ + Writer.close_finished app_writer ; + Reader.close_finished app_reader ; + ] >>= fun () -> + Ssl.Connection.close conn ; + Pipe.close_read app_rd ; + Writer.close w ; + end ; + (app_reader, app_writer) + + let listen + { Config.version; options; name; allowed_ciphers; ca_file; ca_path; + crt_file; key_file; verify_modes ; _ } r w = + let crt_file, key_file = + match crt_file, key_file with + | Some crt_file, Some key_file -> crt_file, key_file + | _ -> invalid_arg "Conduit_async_ssl.ssl_listen: crt_file and \ + key_file must be specified in cfg." in + let net_to_ssl = Reader.pipe r in + let ssl_to_net = Writer.pipe w in + let app_to_ssl, app_wr = Pipe.create () in + let app_rd, ssl_to_app = Pipe.create () in + Ssl.server + ?version + ?options + ?name + ?allowed_ciphers + ?ca_file + ?ca_path + ~crt_file + ~key_file + ?verify_modes + ~app_to_ssl + ~ssl_to_app + ~net_to_ssl + ~ssl_to_net + () + |> Deferred.Or_error.ok_exn + >>= fun conn -> + Reader.of_pipe (Info.of_string "async_conduit_ssl_reader") app_rd >>= fun app_reader -> + Writer.of_pipe (Info.of_string "async_conduit_ssl_writer") app_wr >>| fun (app_writer,_) -> + don't_wait_for begin + Deferred.all_unit [ + Reader.close_finished app_reader; + Writer.close_finished app_writer + ] >>= fun () -> + Ssl.Connection.close conn ; + Pipe.close_read app_rd ; + Writer.close w ; + end; + (app_reader, app_writer) + + type verify_mode = Ssl.Verify_mode.t [@@deriving sexp_of] + type session = Ssl.Session.t sexp_opaque [@@deriving sexp_of] + type version = Ssl.Version.t [@@deriving sexp] + type connection = Ssl.Connection.t [@@deriving sexp_of] + type opt = Ssl.Opt.t [@@deriving sexp] + end +end diff --git a/async/s.ml b/async/s.ml new file mode 100644 index 00000000..9b7ac3d8 --- /dev/null +++ b/async/s.ml @@ -0,0 +1,173 @@ +open Async + +module type V1 = sig + type session [@@deriving sexp_of] + type ssl_conn [@@deriving sexp_of] + type ssl_version [@@deriving sexp] + + module Conduit_async : sig + module Ssl : sig + type config [@@deriving sexp] + + val verify_certificate : ssl_conn -> bool Deferred.t + + val configure + : ?version:ssl_version + -> ?name:string + -> ?ca_file:string + -> ?ca_path:string + -> ?session:session + -> ?verify:(ssl_conn -> bool Deferred.t) + -> unit + -> config + end + + type +'a io = 'a Deferred.t + type ic = Reader.t + type oc = Writer.t + + type addr = [ + | `OpenSSL of string * Ipaddr.t * int + | `OpenSSL_with_config of string * Ipaddr.t * int * Ssl.config + | `TCP of Ipaddr.t * int + | `Unix_domain_socket of string + ] [@@deriving sexp] + + val connect : ?interrupt:unit io -> addr -> (ic * oc) io + val with_connection : ?interrupt:unit io -> addr -> (ic -> oc -> unit io) -> unit io + + type trust_chain = + [ `Ca_file of string + | `Ca_path of string + | `Search_file_first_then_path of + [ `File of string ] * + [ `Path of string ] + ] [@@deriving sexp] + + type openssl = + [ `OpenSSL of + [ `Crt_file_path of string ] * + [ `Key_file_path of string ] + ] [@@deriving sexp] + + type server = [ + | openssl + | `TCP + | `OpenSSL_with_trust_chain of + (openssl * trust_chain) + ] [@@deriving sexp] + + val serve : + ?max_connections:int -> + ?backlog:int -> + ?buffer_age_limit:Writer.buffer_age_limit -> + on_handler_error:[ `Call of ([< Socket.Address.t ] as 'a) -> exn -> unit + | `Ignore + | `Raise ] -> + server -> + ('a, 'b) Tcp.Where_to_listen.t -> + ('a -> ic -> oc -> unit io) -> + ('a, 'b) Tcp.Server.t io + end + + module Conduit_async_ssl : sig + module Ssl_config = Conduit_async.Ssl + + val ssl_connect : Conduit_async.Ssl.config -> Reader.t -> Writer.t -> + (Reader.t * Writer.t) Deferred.t + + val ssl_listen + : ?version:ssl_version + -> ?ca_file:string + -> ?ca_path:string + -> crt_file:string + -> key_file:string + -> Reader.t + -> Writer.t + -> (Reader.t * Writer.t) Deferred.t + end +end + +module type V2 = sig + type allowed_ciphers = + [ `Only of string list | `Openssl_default | `Secure ] + [@@deriving sexp] + type ssl_version [@@deriving sexp] + type session [@@deriving sexp_of] + type verify_mode [@@deriving sexp_of] + type ssl_opt [@@deriving sexp] + type ssl_conn [@@deriving sexp_of] + + + module Ssl : sig + module Config : sig + type t [@@deriving sexp_of] + + val create + : ?version:ssl_version + -> ?options:ssl_opt list + -> ?name:string + -> ?hostname:string + -> ?allowed_ciphers:allowed_ciphers + -> ?ca_file:string + -> ?ca_path:string + -> ?crt_file:string + -> ?key_file:string + -> ?session:session + -> ?verify_modes:verify_mode list + -> ?verify:(ssl_conn -> bool Deferred.t) + -> unit + -> t + end + end + + type addr = [ + | `OpenSSL of Ipaddr.t * int * Ssl.Config.t + | `TCP of Ipaddr.t * int + | `Unix_domain_socket of string + ] [@@deriving sexp_of] + + val connect + : ?interrupt:unit Deferred.t + -> addr + -> (Reader.t * Writer.t) Deferred.t + + val with_connection + : ?interrupt:unit Deferred.t + -> addr + -> (Reader.t -> Writer.t -> unit Deferred.t) + -> unit Deferred.t + + type trust_chain = + [ `Ca_file of string + | `Ca_path of string + | `Search_file_first_then_path of + [ `File of string ] * + [ `Path of string ] + ] [@@deriving sexp] + + type openssl = + [ `OpenSSL of + [ `Crt_file_path of string ] * + [ `Key_file_path of string ] + ] [@@deriving sexp] + + type server = [ + | openssl + | `TCP + | `OpenSSL_with_trust_chain of + (openssl * trust_chain) + ] [@@deriving sexp] + + val serve : + ?max_connections:int -> + ?backlog:int -> + ?buffer_age_limit:Writer.buffer_age_limit -> + on_handler_error:[ `Call of ([< Socket.Address.t ] as 'a) -> exn -> unit + | `Ignore + | `Raise ] -> + server -> + ('a, 'b) Tcp.Where_to_listen.t -> + ('a -> Reader.t -> Writer.t -> unit Deferred.t) -> + ('a, 'b) Tcp.Server.t Deferred.t +end diff --git a/async/v1.ml b/async/v1.ml new file mode 100644 index 00000000..3b6ad179 --- /dev/null +++ b/async/v1.ml @@ -0,0 +1,145 @@ +open Core +open Async +open Private_ssl.V1 + +type session = Ssl.session [@@deriving sexp] +type ssl_version = Ssl.version [@@deriving sexp] +type ssl_conn = Ssl.connection [@@deriving sexp] + +module Conduit_async = struct + module Ssl = struct + include Ssl + + type nonrec config = Config.t [@@deriving sexp] + let configure = Config.create + let verify_certificate = Config.verify_certificate + end + + type oc = Writer.t + type ic = Reader.t + type 'a io = 'a Deferred.t + + type addr = [ + | `OpenSSL of string * Ipaddr.t * int + | `OpenSSL_with_config of string * Ipaddr.t * int * Ssl.config + | `TCP of Ipaddr.t * int + | `Unix_domain_socket of string + ] [@@deriving sexp] + + let connect ?interrupt dst = + match dst with + | `TCP (ip, port) -> + let endp = Host_and_port.create ~host:(Ipaddr.to_string ip) ~port in + Tcp.connect ?interrupt (Tcp.Where_to_connect.of_host_and_port endp) + >>= fun (_, rd, wr) -> return (rd,wr) + | `OpenSSL (_, ip, port) -> + let endp = Host_and_port.create ~host:(Ipaddr.to_string ip) ~port in + Tcp.connect ?interrupt (Tcp.Where_to_connect.of_host_and_port endp) + >>= fun (_, rd, wr) -> + let config = Ssl.configure () in + Ssl.connect config rd wr + | `OpenSSL_with_config (_, ip, port, config) -> + let endp = Host_and_port.create ~host:(Ipaddr.to_string ip) ~port in + Tcp.connect ?interrupt (Tcp.Where_to_connect.of_host_and_port endp) + >>= fun (_, rd, wr) -> + Ssl.connect config rd wr + | `Unix_domain_socket file -> + Tcp.connect ?interrupt (Tcp.Where_to_connect.of_file file) + >>= fun (_, rd, wr) -> + return (rd,wr) + + let with_connection ?interrupt dst f = + match dst with + | `TCP (ip, port) -> + let endp = Host_and_port.create ~host:(Ipaddr.to_string ip) ~port in + Tcp.with_connection ?interrupt + (Tcp.Where_to_connect.of_host_and_port endp) + (fun _ rd wr -> f rd wr) + | `OpenSSL (_, ip, port) -> + let config = Ssl.configure () in + let endp = Host_and_port.create ~host:(Ipaddr.to_string ip) ~port in + Tcp.with_connection ?interrupt + (Tcp.Where_to_connect.of_host_and_port endp) + begin fun _ rd wr -> + Ssl.connect config rd wr >>= fun (rd, wr) -> + Monitor.protect (fun () -> f rd wr) ~finally:begin fun () -> + Deferred.all_unit [ Reader.close rd ; Writer.close wr ] + end + end + | `OpenSSL_with_config (_, ip, port, config) -> + let endp = Host_and_port.create ~host:(Ipaddr.to_string ip) ~port in + Tcp.with_connection ?interrupt + (Tcp.Where_to_connect.of_host_and_port endp) + begin fun _ rd wr -> + Ssl.connect config rd wr >>= fun (rd, wr) -> + Monitor.protect (fun () -> f rd wr) ~finally:begin fun () -> + Deferred.all_unit [ Reader.close rd ; Writer.close wr ] + end + end + | `Unix_domain_socket file -> + Tcp.with_connection ?interrupt (Tcp.Where_to_connect.of_file file) + (fun _ rd wr -> f rd wr) + + type trust_chain = [ + | `Ca_file of string + | `Ca_path of string + | `Search_file_first_then_path of + [ `File of string ] * + [ `Path of string ] + ] [@@deriving sexp] + + type openssl = [ + | `OpenSSL of + [ `Crt_file_path of string ] * + [ `Key_file_path of string ] + ] [@@deriving sexp] + + type requires_async_ssl = [ + | openssl + | `OpenSSL_with_trust_chain of openssl * trust_chain + ] [@@deriving sexp] + + type server = [ + | `TCP + | requires_async_ssl + ] [@@deriving sexp] + + let serve + ?max_connections ?backlog + ?buffer_age_limit ~on_handler_error mode where_to_listen handle_request = + let handle_client handle_request sock rd wr = + match mode with + | `TCP -> handle_request sock rd wr + | #requires_async_ssl as async_ssl -> + let (crt_file, key_file, ca_file, ca_path) = + match async_ssl with + | `OpenSSL (`Crt_file_path crt_file, `Key_file_path key_file) -> + (crt_file, key_file, None, None) + | `OpenSSL_with_trust_chain + (`OpenSSL (`Crt_file_path crt, `Key_file_path key), trust_chain) -> + let (ca_file, ca_path) = + match trust_chain with + | `Ca_file ca_file -> (Some ca_file, None) + | `Ca_path ca_path -> (None, Some ca_path) + | `Search_file_first_then_path (`File ca_file, `Path ca_path) -> + (Some ca_file, Some ca_path) + in + (crt, key, ca_file, ca_path) + in + Ssl.listen + ?ca_file ?ca_path ~crt_file ~key_file rd wr >>= fun (rd,wr) -> + Monitor.protect + (fun () -> handle_request sock rd wr) + ~finally:(fun () -> + Deferred.all_unit [ Reader.close rd ; Writer.close wr ]) + in + Tcp.Server.create ?max_connections ?backlog + ?buffer_age_limit ~on_handler_error + where_to_listen (handle_client handle_request) +end + +module Conduit_async_ssl = struct + module Ssl_config = Conduit_async.Ssl + let ssl_connect = Ssl.connect + let ssl_listen = Ssl.listen +end diff --git a/async/v1_dummy.mli b/async/v1_dummy.mli new file mode 100644 index 00000000..5e3e01ff --- /dev/null +++ b/async/v1_dummy.mli @@ -0,0 +1,4 @@ +include S.V1 + with type session = [`Ssl_not_compiled_in] + and type ssl_version = [`Ssl_not_compiled_in] + and type ssl_conn = [`Ssl_not_compiled_in] diff --git a/async/v1_real.mli b/async/v1_real.mli new file mode 100644 index 00000000..182787d8 --- /dev/null +++ b/async/v1_real.mli @@ -0,0 +1,6 @@ +open Async_ssl + +include S.V1 + with type session = Ssl.Session.t + and type ssl_version = Ssl.Version.t + and type ssl_conn = Ssl.Connection.t diff --git a/async/v2.ml b/async/v2.ml new file mode 100644 index 00000000..5d9526bf --- /dev/null +++ b/async/v2.ml @@ -0,0 +1,116 @@ +open Core +open Async +open Private_ssl.V2 + +type addr = [ + | `OpenSSL of Ipaddr.t * int * Ssl.Config.t + | `TCP of Ipaddr.t * int + | `Unix_domain_socket of string +] [@@deriving sexp_of] + +let connect ?interrupt dst = + match dst with + | `TCP (ip, port) -> + let endp = Host_and_port.create ~host:(Ipaddr.to_string ip) ~port in + Tcp.connect ?interrupt (Tcp.Where_to_connect.of_host_and_port endp) + >>= fun (_, rd, wr) -> return (rd,wr) + | `OpenSSL (ip, port, cfg) -> + let endp = Host_and_port.create ~host:(Ipaddr.to_string ip) ~port in + Tcp.connect ?interrupt (Tcp.Where_to_connect.of_host_and_port endp) + >>= fun (_, rd, wr) -> + Ssl.connect ~cfg rd wr + | `Unix_domain_socket file -> + Tcp.connect ?interrupt (Tcp.Where_to_connect.of_file file) + >>= fun (_, rd, wr) -> + return (rd,wr) + +let with_connection ?interrupt dst f = + match dst with + | `TCP (ip, port) -> + let endp = Host_and_port.create ~host:(Ipaddr.to_string ip) ~port in + Tcp.with_connection ?interrupt + (Tcp.Where_to_connect.of_host_and_port endp) + (fun _ rd wr -> f rd wr) + | `OpenSSL (ip, port, cfg) -> + let endp = Host_and_port.create ~host:(Ipaddr.to_string ip) ~port in + Tcp.with_connection ?interrupt + (Tcp.Where_to_connect.of_host_and_port endp) + begin fun _ rd wr -> + Ssl.connect ~cfg rd wr >>= fun (rd, wr) -> + Monitor.protect (fun () -> f rd wr) ~finally:begin fun () -> + Deferred.all_unit [ Reader.close rd ; Writer.close wr ] + end + end + | `Unix_domain_socket file -> + Tcp.with_connection ?interrupt (Tcp.Where_to_connect.of_file file) + (fun _ rd wr -> f rd wr) + +type trust_chain = [ + | `Ca_file of string + | `Ca_path of string + | `Search_file_first_then_path of + [ `File of string ] * + [ `Path of string ] +] [@@deriving sexp] + +type openssl = [ + | `OpenSSL of + [ `Crt_file_path of string ] * + [ `Key_file_path of string ] +] [@@deriving sexp] + +type requires_async_ssl = [ + | openssl + | `OpenSSL_with_trust_chain of openssl * trust_chain +] [@@deriving sexp] + +type server = [ + | `TCP + | requires_async_ssl +] [@@deriving sexp] + +let serve + ?max_connections ?backlog + ?buffer_age_limit ~on_handler_error mode where_to_listen handle_request = + let handle_client handle_request sock rd wr = + match mode with + | `TCP -> handle_request sock rd wr + | #requires_async_ssl as async_ssl -> + let (crt_file, key_file, ca_file, ca_path) = + match async_ssl with + | `OpenSSL (`Crt_file_path crt_file, `Key_file_path key_file) -> + (crt_file, key_file, None, None) + | `OpenSSL_with_trust_chain + (`OpenSSL (`Crt_file_path crt, `Key_file_path key), trust_chain) -> + let (ca_file, ca_path) = + match trust_chain with + | `Ca_file ca_file -> (Some ca_file, None) + | `Ca_path ca_path -> (None, Some ca_path) + | `Search_file_first_then_path (`File ca_file, `Path ca_path) -> + (Some ca_file, Some ca_path) + in + (crt, key, ca_file, ca_path) + in + let cfg = Ssl.Config.create + ?ca_file ?ca_path ~crt_file ~key_file () in + Ssl.listen cfg rd wr >>= fun (rd,wr) -> + Monitor.protect + (fun () -> handle_request sock rd wr) + ~finally:(fun () -> + Deferred.all_unit [ Reader.close rd ; Writer.close wr ]) + in + Tcp.Server.create ?max_connections ?backlog + ?buffer_age_limit ~on_handler_error + where_to_listen (handle_client handle_request) + +type ssl_version = Ssl.version [@@deriving sexp] +type ssl_opt = Ssl.opt [@@deriving sexp] +type ssl_conn = Ssl.connection [@@deriving sexp_of] +type allowed_ciphers = + [ `Only of string list | `Openssl_default | `Secure ] +[@@deriving sexp] +type verify_mode = Ssl.verify_mode [@@deriving sexp_of] +type session = Ssl.session [@@deriving sexp_of] +module Ssl = struct + module Config = Ssl.Config +end diff --git a/async/v2_dummy.mli b/async/v2_dummy.mli new file mode 100644 index 00000000..61a80344 --- /dev/null +++ b/async/v2_dummy.mli @@ -0,0 +1,8 @@ +include S.V2 + with type session = [`Ssl_not_compiled_in] + and type ssl_version = [`Ssl_not_compiled_in] + and type ssl_conn = [`Ssl_not_compiled_in] + and type ssl_opt = [`Ssl_not_compiled_in] + and type allowed_ciphers = + [ `Only of string list | `Openssl_default | `Secure ] + diff --git a/async/v2_real.mli b/async/v2_real.mli new file mode 100644 index 00000000..80fb7467 --- /dev/null +++ b/async/v2_real.mli @@ -0,0 +1,8 @@ +open Async_ssl +include S.V2 + with type session = Ssl.Session.t + and type ssl_version = Ssl.Version.t + and type ssl_conn = Ssl.Connection.t + and type ssl_opt = Ssl.Opt.t + and type allowed_ciphers = + [ `Only of string list | `Openssl_default | `Secure ] diff --git a/conduit-async.opam b/conduit-async.opam index 1671225e..01f984cc 100644 --- a/conduit-async.opam +++ b/conduit-async.opam @@ -13,7 +13,8 @@ build: [ ] depends: [ "jbuilder" {build & >="1.0+beta9"} - "ppx_sexp_conv" {build} + "core" + "ppx_sexp_conv" "sexplib" "conduit" "async" {>= "v0.10.0"}