123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899openCoreopenPolyopenAsyncopenImportmoduleVersion=VersionmoduleOpt=OptmoduleVerify_mode=Verify_modemoduleFor_testing=structletslow_down_io_to_exhibit_truncation_bugs=reffalseendmoduletypeFfi=moduletypeofFfi__library_must_be_initializedletffi=lazy(Initialize.initialize();(moduleFfi__library_must_be_initialized:Ffi));;letsecure_ciphers=[(* from: cipherli.st *)"EECDH+AESGCM";"EDH+AESGCM";"AES256+EECDH";"AES256+EDH"];;moduleCertificate=structtypet=Ffi__library_must_be_initialized.X509.tletsubjectt=let(moduleFfi)=forceffiinletopenFfiinletsubject=X509.get_subject_nametinletcount=X509_name.entry_countsubjectinList.initcount~f:(funi->letentry=X509_name.get_entrysubjectiinletsn=X509_name_entry.get_objectentry|>ASN1_object.obj2nid|>ASN1_object.nid2sninletdata=X509_name_entry.get_dataentry|>ASN1_string.datainsn,data);;letsubject_alt_namest=let(moduleFfi)=forceffiinFfi.X509.get_subject_alt_namest;;endmoduleConnection=structtypet={ssl:Ffi__library_must_be_initialized.Ssl.t;ctx:Ffi__library_must_be_initialized.Ssl_ctx.t;client_or_server:[`Client|`Server](* The reader and writer binary IO interfaces used by SSL to exchange data without
going through a file descriptor. Strangely enough, to use SSL we _read from_ wbio
and _write to_ wbio. The names are from the perspective of the SSL library. *);rbio:Ffi__library_must_be_initialized.Bio.t;wbio:Ffi__library_must_be_initialized.Bio.t(* Reads and writes to/from C must go through a bigstring. We share it in the record
to prevent needless reallocations. *);bstr:bigstring;name:string;app_to_ssl:stringPipe.Reader.t;ssl_to_app:stringPipe.Writer.t;net_to_ssl:stringPipe.Reader.t;ssl_to_net:stringPipe.Writer.t;closed:unitOr_error.tIvar.t}[@@derivingsexp_of,fields]lettmp_rsa=letexponent=65537(* small random odd (prime?), e.g. 3, 17 or 65537 *)inMemo.general~hashable:Int.hashable(funkey_length->let(moduleFfi)=forceffiinFfi.Rsa.generate_key~key_length~exponent());;lettmp_ecdh=lazy(let(moduleFfi)=forceffiinletcurve=Ffi.Ec_key.Curve.prime256v1inFfi.Ec_key.new_by_curve_namecurve);;lettmp_dh_callback=lazy(* To ensure that the underlying libffi closure is not released pre-maturely
we create (and never free) a [Foreign.dynamic_funptr] here.
This does not leak as only 2 callbacks are ever defined. *)(let(moduleFfi)=forceffiinFfi.Ssl.Tmp_dh_callback.of_fun(fun_t_is_exportkey_length->Rfc3526.modpkey_length));;lettmp_rsa_callback=lazy(* Like [tmp_dh_callback]. *)(let(moduleFfi)=forceffiinFfi.Ssl.Tmp_rsa_callback.of_fun(fun_t_is_exportkey_length->tmp_rsakey_length));;letcreate_exn?verify_modes?(allowed_ciphers=`Secure)ctxversionclient_or_server?hostnamename~app_to_ssl~ssl_to_app~net_to_ssl~ssl_to_net=let(moduleFfi)=forceffiin(* SSL is transferred in 16 kB packets. Therefore, it makes sense for our buffers to
be the same size. *)letssl=Ffi.Ssl.create_exnctxinOption.value_maphostname~default:()~f:(funh->Ffi.Ssl.set_tlsext_host_namesslh|>Or_error.ok_exn);Ffi.Ssl.set_methodsslversion;letrbio=Ffi.Bio.create()inletwbio=Ffi.Bio.create()inletdefault_buffer_size=16*1024inletbstr=Bigstring.createdefault_buffer_sizein(* The default used to be [Verify_none] which defers the decision to abort the
connection to the caller. The caller must be careful to check that the certificate
verified correctly. To prevent mistakes, we've changed it to [Verify_peer]. *)Option.iterverify_modes~f:(Ffi.Ssl.set_verifyssl);(matchallowed_cipherswith|`Openssl_default->()|`Secure->Ffi.Ssl.set_cipher_list_exnsslsecure_ciphers|`Onlyallowed_ciphers->Ffi.Ssl.set_cipher_list_exnsslallowed_ciphers);Ffi.Ssl.set_tmp_dh_callbackssl(forcetmp_dh_callback);Ffi.Ssl.set_tmp_ecdhssl(forcetmp_ecdh);(* Ffi.Ssl.set_tmp_rsa_callback ssl (force tmp_rsa_callback); *)Ffi.Ssl.set_biossl~input:rbio~output:wbio;letclosed=Ivar.create()in{ssl;client_or_server;rbio;wbio;bstr;name;app_to_ssl;ssl_to_app;net_to_ssl;ssl_to_net;closed;ctx};;letcreate_client_exn?hostname?name:(nm="(anonymous)")?allowed_ciphers?(verify_modes=[Verify_mode.Verify_peer])ctxversion~app_to_ssl~ssl_to_app~net_to_ssl~ssl_to_net=create_exn~verify_modes?allowed_ciphersctxversion`Client?hostnamenm~app_to_ssl~ssl_to_app~net_to_ssl~ssl_to_net|>return;;letcreate_server_exn?name:(nm="(anonymous)")?verify_modes?allowed_ciphersctxversion~app_to_ssl~ssl_to_app~net_to_ssl~ssl_to_net=let(moduleFfi)=forceffiinletconnection=create_exn?verify_modes?allowed_ciphersctxversion`Servernm~app_to_ssl~ssl_to_app~net_to_ssl~ssl_to_netinOr_error.ok_exn(Ffi.Ssl.check_private_keyconnection.ssl);returnconnection;;letraise_with_ssl_errors()=let(moduleFfi)=forceffiinfailwiths~here:[%here]"Ssl_error"(Ffi.get_error_stack())[%sexp_of:stringlist];;letclosedt=Ivar.readt.closedletversiont=let(moduleFfi)=forceffiinFfi.Ssl.get_versiont.ssl;;letsession_reusedt=let(moduleFfi)=forceffiinFfi.Ssl.session_reusedt.ssl;;letpeer_certificatet=let(moduleFfi)=forceffiinmatchFfi.Ssl.get_peer_certificatet.sslwith|None->None|Somecert->(matchFfi.Ssl.get_verify_resultt.sslwith|Ok()->Some(Okcert)|Errore->Some(Errore));;letpem_peer_certificate_chaint=let(moduleFfi)=forceffiinFfi.Ssl.get_peer_certificate_chaint.ssl;;letblent=Bigstring.lengtht.bstrletbptrt=Ctypes.bigarray_startCtypes.array1t.bstr(* Called when something goes horribly wrong. This makes sure that
resources don't leak when exceptional circumstances hit.
The SSL structure itself is freed by the GC finalizer.
*)letcleanupt=ifverbosethenDebug.amf[%here]"%s: cleanup"t.name;Pipe.close_readt.app_to_ssl;Pipe.closet.ssl_to_app;Pipe.close_readt.net_to_ssl;Pipe.closet.ssl_to_net;;letcloset=cleanupt(* Write any pending data to ssl_to_net. If you bind to the returned [unit Deferred.t],
you wait until the write has completed all the way through the pipe to the end.
This drains wbio whether or not ssl_to_net is closed or not. When ssl_to_net IS
closed, we make sure to close its matching partner: app_to_ssl. *)letrecwrite_pending_to_nett=let(moduleFfi)=forceffiinlet%bind()=(* We need to pushback here to ensure that the [Bio.read] -> [Pipe.write] sequence
will be atomic. *)ifPipe.is_closedt.ssl_to_netthenreturn()elseif!For_testing.slow_down_io_to_exhibit_truncation_bugsthen(let%bind()=Clock.after(Time.Span.of_sec0.001)inPipe.pushbackt.ssl_to_net)elsePipe.pushbackt.ssl_to_netinifverbosethenDebug.amf[%here]"%s: write_pending_to_net"t.name;letamount_read=Ffi.Bio.readt.wbio~buf:(bptrt)~len:(blent)inifverbosethenDebug.amf[%here]"%s: amount_read: %i"t.nameamount_read;ifamount_read<0then(ifverbosethenDebug.amf[%here]"%s: write_pending_to_net complete"t.name;return())elseifamount_read=0thenwrite_pending_to_nettelse(letto_write=Bigstring.to_string~len:amount_readt.bstrinifnot(Pipe.is_closedt.ssl_to_net)then(ifverbosethenDebug.amf[%here]"%s: ssl_to_net <- '%s'"t.nameto_write;(* Its possible for two copies of [write_pending_to_net] to run concurrently
during session teardown.
Using [write_without_pushback] ensures that this write is atomic with the
[Bio.read] above.
We use [Pipe.pushback] at the top of the loop to allow the remote
end of the connection to throttle us. *)Pipe.write_without_pushbackt.ssl_to_netto_write)else(ifverbosethenDebug.amf[%here]"%s: closing app_to_ssl"t.name;Pipe.close_readt.app_to_ssl);write_pending_to_nett);;letflusht=ifverbosethenDebug.amf[%here]"%s: Flushing..."t.name;let%bind()=write_pending_to_nettinlet%bind_=Pipe.upstream_flushedt.ssl_to_netinifverbosethenDebug.amf[%here]"%s: Done flush."t.name;return();;(* Runs an ssl function (either ssl_read or ssl_write), possibly retrying the call if
an error was returned. *)letrecin_retry_wrapper:typea.t->f:(unit->(a,_)Result.t)->(a,_)Result.tDeferred.t=funt~f->let(moduleFfi)=forceffiinletret=f()inletmoduleE=Ffi.Ssl_errorinmatchretwith|Okx->return(Okx)|Errore->ifverbosethenDebug.amf[%here]"%s: %s"t.name(E.sexp_of_te|>Sexp.to_string);(matchewith|E.Want_read->(* [Un]intuitively enough, if SSL wants a read, we need to write out all
pending data first. *)let%bind()=flushtin(* Then, write the chunk of data from the net into the rbio and try again. *)(match%bindPipe.readt.net_to_sslwith|`Okwas_read->Ffi.Bio.writet.rbio~buf:was_read~len:(String.lengthwas_read)|>ignore;(* Should never fail. It's an 'infinite' buffer. *)in_retry_wrappert~f(* If the connection to the net died, we have to stop. Return an error,
and close its matching pipe. *)|`Eof->ifverbosethenDebug.amf[%here]"%s: closing ssl_to_app"t.name;Pipe.closet.ssl_to_app;return(Error`Stream_eof))|E.Want_write->(* If SSL requests a write, write and try again. *)let%bind()=flushtinin_retry_wrappert~f(* If the underlying SSL connection died, we get an error of 'ZeroReturn'. *)|E.Zero_return->return(Error`Session_closed)(* And of course, sometimes SSL is just broken. *)|E.Ssl_error|E.Want_connect|E.Want_accept|E.Want_X509_lookup|E.Syscall_error->raise_with_ssl_errors());;letdo_ssl_readt=let(moduleFfi)=forceffiinifverbosethenDebug.amf[%here]"%s: BEGIN do_ssl_read"t.name;letread_as_str=ref""inmatch%mapin_retry_wrappert~f:(fun()->matchFfi.Ssl.readt.ssl~buf:(bptrt)~len:(blent)with|Error_ase->e|Okamount_read->read_as_str:=Bigstring.to_string~len:amount_readt.bstr;Okamount_read)with|Ok_->ifverbosethenDebug.amf[%here]"%s: END do_ssl_read. Got: %s"t.name!read_as_str;Some!read_as_str|Error(`Stream_eof|`Session_closed)->ifverbosethenDebug.amf[%here]"%s: END do_ssl_read. Stream closed."t.name;None;;letdo_ssl_writetstr=let(moduleFfi)=forceffiinifverbosethenDebug.amf[%here]"%s: BEGIN do_ssl_write"t.name;letlen=String.lengthstrinletrecgostartidx=ifstartidx>=lenthen(ifverbosethenDebug.amf[%here]"%s: startidx >= len (startidx=%i, len=%i)"t.namestartidxlen;return())else(match%bindin_retry_wrappert~f:(fun()->letwrite_len=len-startidxinletsubstr=String.sub~pos:startidx~len:write_lenstrinifverbosethenDebug.amf[%here]"%s: trying to ssl_write '%s'"t.namesubstr;Ffi.Ssl.writet.ssl~buf:substr~len:write_len)with|Okamount_written->ifverbosethenDebug.amf[%here]"%s: wrote %i bytes"t.nameamount_written;let%bind()=write_pending_to_nettingo(startidx+amount_written)|Errore->(* should never happen *)failwiths~here:[%here]"Unexpected SSL error during write."e[%sexp_of:[`Session_closed|`Stream_eof]])ingo0;;(* Runs the net -> ssl -> app data pump until either net_to_ssl or ssl_to_app
dies *)letrecrun_reader_loopt=ifverbosethenDebug.amf[%here]"%s: BEGIN run_reader_loop"t.name;match%binddo_ssl_readtwith|None->(* we hit end of t.ssl in do_ssl_read, close ssl_to_app so the app sees the close *)return(Pipe.closet.ssl_to_app)|Somes->ifPipe.is_closedt.ssl_to_appthen(ifverbosethenDebug.amf[%here]"%s: ssl_to_app is closed; skipping write."t.name;return())else(ifverbosethenDebug.amf[%here]"%s: ssl_to_app <- '%s'"t.names;let%bind()=Pipe.writet.ssl_to_appsinrun_reader_loopt);;(* Runs the app -> ssl -> net data pump until either app_to_ssl or ssl_to_net dies. *)letrecrun_writer_loopt=match%bindPipe.readt.app_to_sslwith|`Okto_write->ifverbosethenDebug.amf[%here]"%s: app_to_ssl -> '%s'"t.nameto_write;let%bind()=do_ssl_writetto_writeinrun_writer_loopt|`Eof->let%bind()=write_pending_to_nettinifverbosethenDebug.amf[%here]"%s: closing ssl_to_net"t.name;Pipe.closet.ssl_to_net;return();;letrun_handshaket=let(moduleFfi)=forceffiinlethandshake_fn,handshake_name=matcht.client_or_serverwith|`Client->Ffi.Ssl.connect,"connect"|`Server->Ffi.Ssl.accept,"accept"inmatch%mapin_retry_wrappert~f:(fun()->ifverbosethenDebug.amf[%here]"%s: trying to %s"t.namehandshake_name;handshake_fnt.ssl)with|Ok_->ifverbosethenDebug.amf[%here]"%s: Handshake complete!"t.name|Error_->ifverbosethenDebug.amf[%here]"%s: Handshake failed!"t.name;cleanupt;;(* Run both independent data pumps at once. *)letstart_loopst=let%map()=Deferred.all_unit[run_reader_loopt;run_writer_loopt]inifverbosethenDebug.amf[%here]"%s: SSL stopped."t.name;;(* Close all pipes if exceptions leak out. This will implicitly stop
[run_reader_loop] and [run_writer_loop], since they'll just keep getting EOFs. *)letwith_cleanupt~f=let%mapresult=Deferred.Or_error.try_with~run:`Schedule~rest:`Log~name:"ssl_pipe"finResult.iter_errorresult~f:(funerror->ifverbosethenDebug.amf[%here]"%s: ERROR: %s"t.name(Error.to_string_humerror);cleanupt);result;;endmoduleSession=structmoduleState=structtypet={session:Ffi__library_must_be_initialized.Ssl_session.t(* One SSL_SESSION object must only be used with one SSL_CTX object *);ctx:Ffi__library_must_be_initialized.Ssl_ctx.t}letget~conn=let(moduleFfi)=forceffiinmatchFfi.Ssl.get1_session(Connection.sslconn)with|None->ifverbosethenDebug.amf[%here]"no session available for connection %s"(Connection.nameconn);None|Somesession->Some{session;ctx=Connection.ctxconn};;letreuset~conn=let(moduleFfi)=forceffiinifnot(phys_equalt.ctx(Connection.ctxconn))thenfailwithf"Trying to reuse %s with a different context (did you change ca_file or \
ca_path?)"(Connection.nameconn)();Ffi.Ssl.set_session(Connection.sslconn)t.session|>Or_error.ok_exn;;endtypet=State.tSet_once.tletcreate()=Set_once.create()letremembert~conn=matchSet_once.gettwith|Some_->()|None->Option.iter(State.get~conn)~f:(Set_once.set_exnt[%here]);;letreuset~conn=Option.iter(Set_once.gett)~f:(State.reuse~conn)end(* Global SSL contexts for every needed
(name, version, ca_file, ca_path, options, crt_file, key_file)
tuple. This is cached so that the same SSL_CTX object can be reused later *)letcontext_exn=Memo.general(fun(name,version,ca_file,ca_path,options,crt_file,key_file)->let(moduleFfi)=forceffiinletctx=Ffi.Ssl_ctx.create_exnversioninleterrore=failwiths~here:[%here]"Could not initialize ssl context"e[%sexp_of:Error.t]inmatch%bindmatchcrt_file,key_filewith|Somecrt_file,Somekey_file->Ffi.Ssl_ctx.use_certificate_chain_and_key_files~crt_file~key_filectx|_,_->return(Ok())with|Errore->errore|Ok()->(match%mapmatchca_file,ca_pathwith|None,None->return(Ok(Ffi.Ssl_ctx.set_default_verify_pathsctx))|_,_->Ffi.Ssl_ctx.load_verify_locationsctx?ca_file?ca_pathwith|Errore->errore|Ok()->letsession_id_context=Option.valuename~default:"default_session_id_context"inFfi.Ssl_ctx.set_session_id_contextctxsession_id_context;Ffi.Ssl_ctx.set_optionsctxoptions;ctx));;letclient?(version=Version.default)?(options=Opt.default)?name?hostname?allowed_ciphers?ca_file?ca_path?crt_file?key_file?verify_modes?session~app_to_ssl~ssl_to_app~net_to_ssl~ssl_to_net()=Deferred.Or_error.try_with~run:`Schedule~rest:`Log(fun()->let%bindcontext=context_exn(name,version,ca_file,ca_path,options,crt_file,key_file)inConnection.create_client_exn?hostname?name?verify_modes?allowed_cipherscontextversion~app_to_ssl~ssl_to_app~net_to_ssl~ssl_to_net)>>=?funconn->Option.itersession~f:(Session.reuse~conn);Connection.with_cleanupconn~f:(fun()->Connection.run_handshakeconn)>>=?fun()->Option.itersession~f:(Session.remember~conn);don't_wait_for(Connection.with_cleanupconn~f:(fun()->Connection.start_loopsconn)>>|Ivar.fillconn.closed);return(Okconn);;letserver?(version=Version.default)?(options=Opt.default)?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.try_with~run:`Schedule~rest:`Log(fun()->let%bindcontext=context_exn(name,version,ca_file,ca_path,options,Somecrt_file,Somekey_file)inConnection.create_server_exn?namecontextversion?verify_modes?allowed_ciphers~app_to_ssl~ssl_to_app~net_to_ssl~ssl_to_net)>>=?funconn->Connection.with_cleanupconn~f:(fun()->Connection.run_handshakeconn)>>=?fun()->don't_wait_for(Connection.with_cleanupconn~f:(fun()->Connection.start_loopsconn)>>|Ivar.fillconn.closed);return(Okconn);;let%test_module_=(modulestructletpipe_to_stringreader=Pipe.to_listreader>>|String.concatletcheck_closedpname=ifnot(Pipe.is_closedp)thenfailwith(name^" was left open.");;(*
The pipe names are short because there's a lot of them and it got annoying to type.
Please refer to this ascii art for an explanation.
client_out
+---------+ a ----> c +-------+ e --------+
| CLIENT | | SSL 1 | |
+---------+ b <---- d +-------+ f <----+ |
client_in | |
| |
server_out | |
+--------+ k ----> i +-------+ g ------+ |
| SERVER | | SSL 2 | |
+--------+ l <---- j +-------+ h <--------+
server_in
*)letwith_pipes~f=letfunc=finifverbosethenDebug.amf[%here]"creating pipes";letl,j=Pipe.create()inleth,e=Pipe.create()inletc,a=Pipe.create()inletb,d=Pipe.create()inletf,g=Pipe.create()inleti,k=Pipe.create()infunc~a~b~c~d~e~f~g~h~i~j~k~l;;(* Create both a client and a server, and send hello world back and forth. *)let%test_unit_=letsession=Session.create()inletcheck_versionconn=(* Since Version.default is [Sslv23], we expect to negotiate the highest allowed
protocol version, which is [Tlsv1_2] *)[%test_result:Version.t](Connection.versionconn)~expect:Version.Tlsv1_2inletcheck_session_reusedconn~expect=[%test_result:bool](Connection.session_reusedconn)~expectinletcheck_peer_certificateconn=letcert=Connection.peer_certificateconn|>Option.value_exn|>Or_error.ok_exninletvalue=letalist=Certificate.subjectcertinList.Assoc.find_exnalist~equal:String.equal"CN"in[%test_result:string]value~expect:"testbox"inletrun_test~expect_session_reused=with_pipes~f:(fun~a~b~c~d~e~f~g~h~i~j~k~l->letclient_in,client_out=b,ainletserver_in,server_out=l,kinifverbosethenDebug.amf[%here]"1";(* attach the server to ssl 2 to net *)letserver_conn=server~name:"server"(* It might be confusing that the two "don't_use_in_production"
files are used for different purposes. This is enough to test out
the functionality, but if we want to be super clear we need 5
such files in this library: ca crt, server key + crt, and client
key + crt.*)~allowed_ciphers:`Secure~ca_file:"do_not_use_in_production.crt"(* CA certificate *)~crt_file:"do_not_use_in_production.crt"(* server certificate *)~key_file:"do_not_use_in_production.key"(* server key *)~verify_modes:[Verify_mode.Verify_peer]~app_to_ssl:i~ssl_to_app:j~ssl_to_net:g~net_to_ssl:h()inletclient_conn=client~name:"client"(* Necessary to verify the self-signed server certificate. *)~allowed_ciphers:`Secure~ca_file:"do_not_use_in_production.crt"(* ca certificate *)~crt_file:"do_not_use_in_production.crt"(* client certificate *)~key_file:"do_not_use_in_production.key"(* client key *)~hostname:"does-not-matter"~session~app_to_ssl:c~ssl_to_app:d~ssl_to_net:e~net_to_ssl:f()inletclient_conn=client_conn>>|Or_error.ok_exninletserver_conn=server_conn>>|Or_error.ok_exninlet%bindclient_conn,server_conn=Deferred.bothclient_connserver_connincheck_versionclient_conn;check_versionserver_conn;ifverbosethenDebug.amf[%here]"client checking server certificate";check_peer_certificateclient_conn;ifverbosethenDebug.amf[%here]"server checking client certificate";check_peer_certificateserver_conn;ifverbosethenDebug.amf[%here]"client checking reused";check_session_reusedclient_conn~expect:expect_session_reused;ifverbosethenDebug.amf[%here]"server checking reused";check_session_reusedserver_conn~expect:expect_session_reused;ifverbosethenDebug.amf[%here]"2";Pipe.writeclient_out"hello, server."|>don't_wait_for;ifverbosethenDebug.amf[%here]"3";Pipe.closeclient_out;ifverbosethenDebug.amf[%here]"4";Pipe.writeserver_out"hello, client."|>don't_wait_for;ifverbosethenDebug.amf[%here]"5";Pipe.closeserver_out;ifverbosethenDebug.amf[%here]"6";let%bindon_server=pipe_to_stringserver_ininifverbosethenDebug.amf[%here]"7";let%bindon_client=pipe_to_stringclient_ininifverbosethenDebug.amf[%here]"8";(* check that all the pipes are closed *)check_closeda"client_in";check_closedb"client_out";check_closedc"c";check_closedd"d";check_closede"e";check_closedf"f";check_closedg"g";check_closedh"h";check_closedi"i";check_closedj"j";check_closedk"server_in";check_closedl"server_out";ifverbosethenDebug.amf[%here]"9";let%bindclient_exit_status=Connection.closedclient_conninOr_error.ok_exnclient_exit_status;let%bindserver_exit_status=Connection.closedserver_conninOr_error.ok_exnserver_exit_status;ifon_server<>"hello, server."thenfailwiths~here:[%here]"No hello world to server"on_server[%sexp_of:string];ifon_client<>"hello, client."thenfailwiths~here:[%here]"No hello world to client"on_client[%sexp_of:string];return())inletrun_twice()=ifverbosethenDebug.amf[%here]"first run";let%bind()=run_test~expect_session_reused:falseinifverbosethenDebug.amf[%here]"second run";run_test~expect_session_reused:trueinThread_safe.block_on_async_exnrun_twice;;let%bench"ssl_stress_test"=letrun_bench()=with_pipes~f:(fun~a~b~c~d~e~f~g~h~i~j~k~l->letclient_in,client_out=b,ainletserver_in,server_out=l,kin(* attach the server to ssl 2 to net *)letserver_conn=server~name:"server"~allowed_ciphers:`Secure~crt_file:"do_not_use_in_production.crt"~key_file:"do_not_use_in_production.key"~app_to_ssl:i~ssl_to_app:j~ssl_to_net:g~net_to_ssl:h()in(* attach the client to ssl 1 to net *)letclient_conn=client~name:"client"~allowed_ciphers:`Secure~app_to_ssl:c~ssl_to_app:d~ssl_to_net:e~net_to_ssl:f()inlet%bindclient_conn,server_conn=Deferred.bothclient_connserver_conninletclient_conn=Or_error.ok_exnclient_conninletserver_conn=Or_error.ok_exnserver_conninletreccyclek=ifk=0then(Pipe.closeclient_out;Pipe.closeserver_out;return())else(let%bind()=Pipe.writeclient_out"hello server"inmatch%bindPipe.readserver_inwith|`Eof->assertfalse|`Oks->let%bind()=assert(s="hello server");Pipe.writeserver_out"hello client"in(match%bindPipe.readclient_inwith|`Eof->assertfalse|`Oks->let%bind()=assert(s="hello client");return()incycle(k-1)))inlet%bind()=cycle1_000inlet%bindclient_exit_status=Connection.closedclient_conninOr_error.ok_exnclient_exit_status;let%bindserver_exit_status=Connection.closedserver_conninOr_error.ok_exnserver_exit_status;return())inThread_safe.block_on_async_exnrun_bench;;end);;