From f27e862479b086795674629f12141c6df544e747 Mon Sep 17 00:00:00 2001 From: Jake Douglas Date: Sun, 29 Mar 2009 16:12:07 -0700 Subject: [PATCH] Added :verify_peer support for requesting and verifying an X509 certificate from a remote SSL peer. Signed-off-by: Aman Gupta --- ext/cmain.cpp | 22 +++++++++-- ext/ed.cpp | 44 +++++++++++++++++++-- ext/ed.h | 8 +++- ext/eventmachine.h | 6 ++- ext/rubymain.cpp | 21 +++++++--- ext/ssl.cpp | 39 ++++++++++++++++++- ext/ssl.h | 6 ++- lib/em/connection.rb | 49 ++++++++++++++++++++---- tests/client.crt | 31 +++++++++++++++ tests/client.key | 51 +++++++++++++++++++++++++ tests/test_ssl_verify.rb | 82 ++++++++++++++++++++++++++++++++++++++++ 11 files changed, 333 insertions(+), 26 deletions(-) create mode 100644 tests/client.crt create mode 100644 tests/client.key create mode 100644 tests/test_ssl_verify.rb diff --git a/ext/cmain.cpp b/ext/cmain.cpp index 97f9c015a..429b42d59 100644 --- a/ext/cmain.cpp +++ b/ext/cmain.cpp @@ -302,17 +302,17 @@ extern "C" void evma_start_tls (const char *binding) evma_set_tls_parms ******************/ -extern "C" void evma_set_tls_parms (const char *binding, const char *privatekey_filename, const char *certchain_filename) +extern "C" void evma_set_tls_parms (const char *binding, const char *privatekey_filename, const char *certchain_filename, int verify_peer) { ensure_eventmachine("evma_set_tls_parms"); EventableDescriptor *ed = dynamic_cast (Bindable_t::GetObject (binding)); if (ed) - ed->SetTlsParms (privatekey_filename, certchain_filename); + ed->SetTlsParms (privatekey_filename, certchain_filename, (verify_peer == 1 ? true : false)); } -/************** +/****************** evma_get_peer_cert -**************/ +******************/ #ifdef WITH_SSL extern "C" X509 *evma_get_peer_cert (const char *binding) @@ -325,6 +325,20 @@ extern "C" X509 *evma_get_peer_cert (const char *binding) } #endif +/******************** +evma_accept_ssl_peer +********************/ + +#ifdef WITH_SSL +extern "C" void evma_accept_ssl_peer (const char *binding) +{ + ensure_eventmachine("evma_accept_ssl_peer"); + ConnectionDescriptor *cd = dynamic_cast (Bindable_t::GetObject (binding)); + if (cd) + cd->AcceptSslPeer(); +} +#endif + /***************** evma_get_peername *****************/ diff --git a/ext/ed.cpp b/ext/ed.cpp index 4668cf61f..60e6f3eb3 100644 --- a/ext/ed.cpp +++ b/ext/ed.cpp @@ -183,6 +183,8 @@ ConnectionDescriptor::ConnectionDescriptor (int sd, EventMachine_t *em): #ifdef WITH_SSL SslBox (NULL), bHandshakeSignaled (false), + bSslVerifyPeer (false), + bSslPeerAccepted(false), #endif bIsServer (false), LastIo (gCurrentLoopTime), @@ -502,8 +504,14 @@ void ConnectionDescriptor::_DispatchInboundData (const char *buffer, int size) if (EventCallback) (*EventCallback)(GetBinding().c_str(), EM_CONNECTION_READ, B, s); } + + // If our SSL handshake had a problem, shut down the connection. + if (s == -2) { + ScheduleClose(false); + return; + } + _CheckHandshakeStatus(); - // INCOMPLETE, s may indicate an SSL error that would force the connection down. _DispatchCiphertext(); } else { @@ -772,7 +780,7 @@ void ConnectionDescriptor::StartTls() if (SslBox) throw std::runtime_error ("SSL/TLS already running on connection"); - SslBox = new SslBox_t (bIsServer, PrivateKeyFilename, CertChainFilename); + SslBox = new SslBox_t (bIsServer, PrivateKeyFilename, CertChainFilename, bSslVerifyPeer, GetBinding().c_str()); _DispatchCiphertext(); #endif @@ -786,7 +794,7 @@ void ConnectionDescriptor::StartTls() ConnectionDescriptor::SetTlsParms *********************************/ -void ConnectionDescriptor::SetTlsParms (const char *privkey_filename, const char *certchain_filename) +void ConnectionDescriptor::SetTlsParms (const char *privkey_filename, const char *certchain_filename, bool verify_peer) { #ifdef WITH_SSL if (SslBox) @@ -795,6 +803,7 @@ void ConnectionDescriptor::SetTlsParms (const char *privkey_filename, const char PrivateKeyFilename = privkey_filename; if (certchain_filename && *certchain_filename) CertChainFilename = certchain_filename; + bSslVerifyPeer = verify_peer; #endif #ifdef WITHOUT_SSL @@ -817,6 +826,35 @@ X509 *ConnectionDescriptor::GetPeerCert() #endif +/*********************************** +ConnectionDescriptor::VerifySslPeer +***********************************/ + +#ifdef WITH_SSL +bool ConnectionDescriptor::VerifySslPeer(const char *cert) +{ + bSslPeerAccepted = false; + + if (EventCallback) + (*EventCallback)(GetBinding().c_str(), EM_SSL_VERIFY, cert, strlen(cert)); + + return bSslPeerAccepted; +} +#endif + + +/*********************************** +ConnectionDescriptor::AcceptSslPeer +***********************************/ + +#ifdef WITH_SSL +void ConnectionDescriptor::AcceptSslPeer() +{ + bSslPeerAccepted = true; +} +#endif + + /***************************************** ConnectionDescriptor::_DispatchCiphertext *****************************************/ diff --git a/ext/ed.h b/ext/ed.h index 524a19b31..6155d2429 100644 --- a/ext/ed.h +++ b/ext/ed.h @@ -67,7 +67,7 @@ class EventableDescriptor: public Bindable_t virtual bool GetSubprocessPid (pid_t*) {return false;} virtual void StartTls() {} - virtual void SetTlsParms (const char *privkey_filename, const char *certchain_filename) {} + virtual void SetTlsParms (const char *privkey_filename, const char *certchain_filename, bool verify_peer) {} #ifdef WITH_SSL virtual X509 *GetPeerCert() {return NULL;} @@ -167,10 +167,12 @@ class ConnectionDescriptor: public EventableDescriptor virtual int GetOutboundDataSize() {return OutboundDataSize;} virtual void StartTls(); - virtual void SetTlsParms (const char *privkey_filename, const char *certchain_filename); + virtual void SetTlsParms (const char *privkey_filename, const char *certchain_filename, bool verify_peer); #ifdef WITH_SSL virtual X509 *GetPeerCert(); + virtual bool VerifySslPeer(const char*); + virtual void AcceptSslPeer(); #endif void SetServerMode() {bIsServer = true;} @@ -208,6 +210,8 @@ class ConnectionDescriptor: public EventableDescriptor std::string CertChainFilename; std::string PrivateKeyFilename; bool bHandshakeSignaled; + bool bSslVerifyPeer; + bool bSslPeerAccepted; #endif bool bIsServer; diff --git a/ext/eventmachine.h b/ext/eventmachine.h index b2de17cfb..8ecc2d28a 100644 --- a/ext/eventmachine.h +++ b/ext/eventmachine.h @@ -33,7 +33,8 @@ extern "C" { EM_LOOPBREAK_SIGNAL = 105, EM_CONNECTION_NOTIFY_READABLE = 106, EM_CONNECTION_NOTIFY_WRITABLE = 107, - EM_SSL_HANDSHAKE_COMPLETED = 108 + EM_SSL_HANDSHAKE_COMPLETED = 108, + EM_SSL_VERIFY = 109 }; @@ -52,11 +53,12 @@ extern "C" { const char *evma_create_unix_domain_server (const char *filename); const char *evma_open_datagram_socket (const char *server, int port); const char *evma_open_keyboard(); - void evma_set_tls_parms (const char *binding, const char *privatekey_filename, const char *certchain_filenane); + void evma_set_tls_parms (const char *binding, const char *privatekey_filename, const char *certchain_filenane, int verify_peer); void evma_start_tls (const char *binding); #ifdef WITH_SSL X509 *evma_get_peer_cert (const char *binding); + void evma_accept_ssl_peer (const char *binding); #endif int evma_get_peername (const char *binding, struct sockaddr*); diff --git a/ext/rubymain.cpp b/ext/rubymain.cpp index a795dc632..2d3e6ac4e 100644 --- a/ext/rubymain.cpp +++ b/ext/rubymain.cpp @@ -43,6 +43,7 @@ static VALUE Intern_delete; static VALUE Intern_call; static VALUE Intern_receive_data; static VALUE Intern_ssl_handshake_completed; +static VALUE Intern_ssl_verify_peer; static VALUE Intern_notify_readable; static VALUE Intern_notify_writable; @@ -108,6 +109,15 @@ static void event_callback (struct em_event* e) rb_raise (EM_eConnectionNotBound, "unknown connection: %s", a1); rb_funcall (q, Intern_ssl_handshake_completed, 0); } + else if (a2 == EM_SSL_VERIFY) { + VALUE t = rb_ivar_get (EmModule, Intern_at_conns); + VALUE q = rb_hash_aref (t, rb_str_new2(a1)); + if (q == Qnil) + rb_raise (EM_eConnectionNotBound, "unknown connection: %s", a1); + VALUE r = rb_funcall (q, Intern_ssl_verify_peer, 1, rb_str_new(a3, a4)); + if (RTEST(r)) + evma_accept_ssl_peer (a1); + } else rb_funcall (EmModule, Intern_event_callback, 3, rb_str_new2(a1), (a2 << 1) | 1, rb_str_new(a3,a4)); } @@ -238,20 +248,20 @@ static VALUE t_start_tls (VALUE self, VALUE signature) t_set_tls_parms ***************/ -static VALUE t_set_tls_parms (VALUE self, VALUE signature, VALUE privkeyfile, VALUE certchainfile) +static VALUE t_set_tls_parms (VALUE self, VALUE signature, VALUE privkeyfile, VALUE certchainfile, VALUE verify_peer) { /* set_tls_parms takes a series of positional arguments for specifying such things * as private keys and certificate chains. * It's expected that the parameter list will grow as we add more supported features. * ALL of these parameters are optional, and can be specified as empty or NULL strings. */ - evma_set_tls_parms (StringValuePtr (signature), StringValuePtr (privkeyfile), StringValuePtr (certchainfile) ); + evma_set_tls_parms (StringValuePtr (signature), StringValuePtr (privkeyfile), StringValuePtr (certchainfile), (verify_peer == Qtrue ? 1 : 0)); return Qnil; } -/*********** +/*************** t_get_peer_cert -***********/ +***************/ static VALUE t_get_peer_cert (VALUE self, VALUE signature) { @@ -859,6 +869,7 @@ extern "C" void Init_rubyeventmachine() Intern_call = rb_intern ("call"); Intern_receive_data = rb_intern ("receive_data"); Intern_ssl_handshake_completed = rb_intern ("ssl_handshake_completed"); + Intern_ssl_verify_peer = rb_intern ("ssl_verify_peer"); Intern_notify_readable = rb_intern ("notify_readable"); Intern_notify_writable = rb_intern ("notify_writable"); @@ -879,7 +890,7 @@ extern "C" void Init_rubyeventmachine() rb_define_module_function (EmModule, "start_tcp_server", (VALUE(*)(...))t_start_server, 2); rb_define_module_function (EmModule, "stop_tcp_server", (VALUE(*)(...))t_stop_server, 1); rb_define_module_function (EmModule, "start_unix_server", (VALUE(*)(...))t_start_unix_server, 1); - rb_define_module_function (EmModule, "set_tls_parms", (VALUE(*)(...))t_set_tls_parms, 3); + rb_define_module_function (EmModule, "set_tls_parms", (VALUE(*)(...))t_set_tls_parms, 4); rb_define_module_function (EmModule, "start_tls", (VALUE(*)(...))t_start_tls, 1); rb_define_module_function (EmModule, "get_peer_cert", (VALUE(*)(...))t_get_peer_cert, 1); rb_define_module_function (EmModule, "send_data", (VALUE(*)(...))t_send_data, 3); diff --git a/ext/ssl.cpp b/ext/ssl.cpp index 4cf288ca3..cada3505f 100644 --- a/ext/ssl.cpp +++ b/ext/ssl.cpp @@ -208,9 +208,10 @@ SslContext_t::~SslContext_t() SslBox_t::SslBox_t ******************/ -SslBox_t::SslBox_t (bool is_server, const string &privkeyfile, const string &certchainfile): +SslBox_t::SslBox_t (bool is_server, const string &privkeyfile, const string &certchainfile, bool verify_peer, const char *binding): bIsServer (is_server), bHandshakeCompleted (false), + bVerifyPeer (verify_peer), pSSL (NULL), pbioRead (NULL), pbioWrite (NULL) @@ -232,6 +233,12 @@ SslBox_t::SslBox_t (bool is_server, const string &privkeyfile, const string &cer assert (pSSL); SSL_set_bio (pSSL, pbioRead, pbioWrite); + // Store a pointer to the binding signature in the SSL object so we can retrieve it later + SSL_set_ex_data(pSSL, 0, (void*) binding); + + if (bVerifyPeer) + SSL_set_verify(pSSL, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, ssl_verify_wrapper); + if (!bIsServer) SSL_connect (pSSL); } @@ -419,5 +426,35 @@ X509 *SslBox_t::GetPeerCert() return cert; } + +/****************** +ssl_verify_wrapper +*******************/ + +extern "C" int ssl_verify_wrapper(int preverify_ok, X509_STORE_CTX *ctx) +{ + const char *binding; + X509 *cert; + SSL *ssl; + BUF_MEM *buf; + BIO *out; + int result; + + cert = X509_STORE_CTX_get_current_cert(ctx); + ssl = (SSL*) X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); + binding = (const char*) SSL_get_ex_data(ssl, 0); + + out = BIO_new(BIO_s_mem()); + PEM_write_bio_X509(out, cert); + BIO_write(out, "\0", 1); + BIO_get_mem_ptr(out, &buf); + + ConnectionDescriptor *cd = dynamic_cast (Bindable_t::GetObject(binding)); + result = (cd->VerifySslPeer(buf->data) == true ? 1 : 0); + BUF_MEM_free(buf); + + return result; +} + #endif // WITH_SSL diff --git a/ext/ssl.h b/ext/ssl.h index 574f1e6eb..dfd673080 100644 --- a/ext/ssl.h +++ b/ext/ssl.h @@ -57,7 +57,7 @@ class SslBox_t class SslBox_t { public: - SslBox_t (bool is_server, const string &privkeyfile, const string &certchainfile); + SslBox_t (bool is_server, const string &privkeyfile, const string &certchainfile, bool verify_peer, const char *binding); virtual ~SslBox_t(); int PutPlaintext (const char*, int); @@ -75,6 +75,7 @@ class SslBox_t protected: SslContext_t *Context; + bool bVerifyPeer; bool bIsServer; bool bHandshakeCompleted; SSL *pSSL; @@ -83,6 +84,9 @@ class SslBox_t PageList OutboundQ; }; + +extern "C" int ssl_verify_wrapper(int, X509_STORE_CTX*); + #endif // WITH_SSL diff --git a/lib/em/connection.rb b/lib/em/connection.rb index deac09348..461c2bb4a 100644 --- a/lib/em/connection.rb +++ b/lib/em/connection.rb @@ -105,6 +105,15 @@ def receive_data data def ssl_handshake_completed end + # #ssl_verify_peer is called by EventMachine when :verify_peer => true has been passed to #start_tls. + # It will be called with each certificate in the certificate chain provided by the remote peer. + # The cert will be passed as a String in PEM format, the same as in #get_peer_cert. It is up to user defined + # code to perform a check on the certificates. The return value from this callback is used to accept or deny the peer. + # A return value that is not nil or false triggers acceptance. If the peer is not accepted, the connection + # will be subsequently closed. See 'tests/test_ssl_verify.rb' for a simple example. + def ssl_verify_peer(cert) + end + # EventMachine::Connection#unbind is called by the framework whenever a connection # (either a server or client connection) is closed. The close can occur because # your code intentionally closes it (see close_connection and close_connection_after_writing), @@ -220,13 +229,37 @@ def connection_completed # #start_tls takes an optional parameter hash that allows you to specify certificate # and other options to be used with this Connection object. Here are the currently-supported # options: - # :cert_chain_file : takes a String, which is interpreted as the name of a readable file in the - # local filesystem. The file is expected to contain a chain of X509 certificates in - # PEM format, with the most-resolved certificate at the top of the file, successive - # intermediate certs in the middle, and the root (or CA) cert at the bottom. # - # :private_key_file : tales a String, which is interpreted as the name of a readable file in the - # local filesystem. The file must contain a private key in PEM format. + # * :cert_chain_file : + # takes a String, which is interpreted as the name of a readable file in the + # local filesystem. The file is expected to contain a chain of X509 certificates in + # PEM format, with the most-resolved certificate at the top of the file, successive + # intermediate certs in the middle, and the root (or CA) cert at the bottom. + # + # * :private_key_file : + # takes a String, which is interpreted as the name of a readable file in the + # local filesystem. The file must contain a private key in PEM format. + # + # * :verify_peer : + # takes either true or false. Default is false. This indicates whether a server should request a + # certificate from a peer, to be verified by user code. If true, the #ssl_verify_peer callback + # on the Connection object is called with each certificate in the certificate chain provided by + # the peer. See documentation on #ssl_verify_peer for how to use this. + # + # === Usage example: + # + # require 'rubygems' + # require 'eventmachine' + # + # module Handler + # def post_init + # start_tls(:private_key_file => '/tmp/server.key', :cert_chain_file => '/tmp/server.crt', :verify_peer => false) + # end + # end + # + # EM.run { + # EM.start_server("127.0.0.1", 9999, Handler) + # } # #-- # TODO: support passing an encryption parameter, which can be string or Proc, to get a passphrase @@ -238,7 +271,7 @@ def connection_completed # behaved than the ones for raw chunks of memory. # def start_tls args={} - priv_key, cert_chain = args.values_at(:private_key_file, :cert_chain_file) + priv_key, cert_chain, verify_peer = args.values_at(:private_key_file, :cert_chain_file, :verify_peer) [priv_key, cert_chain].each do |file| next if file.nil? or file.empty? @@ -246,7 +279,7 @@ def start_tls args={} "Could not find #{file} for start_tls" unless File.exists? file end - EventMachine::set_tls_parms(@signature, priv_key || '', cert_chain || '') + EventMachine::set_tls_parms(@signature, priv_key || '', cert_chain || '', verify_peer) EventMachine::start_tls @signature end diff --git a/tests/client.crt b/tests/client.crt new file mode 100644 index 000000000..1919d976c --- /dev/null +++ b/tests/client.crt @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFRDCCAywCAQEwDQYJKoZIhvcNAQEFBQAwaDELMAkGA1UEBhMCRU0xFTATBgNV +BAgTDEV2ZW50TWFjaGluZTEVMBMGA1UEChMMRXZlbnRNYWNoaW5lMRQwEgYDVQQL +EwtEZXZlbG9wbWVudDEVMBMGA1UEAxMMRXZlbnRNYWNoaW5lMB4XDTA5MDMyOTAy +MzE0NloXDTEwMDMyOTAyMzE0NlowaDELMAkGA1UEBhMCRU0xFTATBgNVBAgTDEV2 +ZW50TWFjaGluZTEVMBMGA1UEChMMRXZlbnRNYWNoaW5lMRQwEgYDVQQLEwtEZXZl +bG9wbWVudDEVMBMGA1UEAxMMRXZlbnRNYWNoaW5lMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEAv1FSOIX1z7CQtVBFlrB0A3/V29T+22STKKmiRWYkKL5b ++hkrp9IZ5J4phZHgUVM2VDPOO2Oc2PU6dlGGZISg+UPERunTogxQKezCV0vcE9cK +OwzxCFDRvv5rK8aKMscfBLbNKocAXywuRRQmdxPiVRzbyPrl+qCr/EDLXAX3D77l +S8n2AwDg19VyI+IgFUE+Dy5e1eLoY6nV+Mq+vNXdn3ttF3t+ngac5pj5Q9h+pD5p +67baDHSnf/7cy2fa/LKrLolVHQR9G2K6cEfeM99NtcsMbkoPs4iI3FA05OVTQHXg +C8C8cRxrb9APl95I/ep65OIaCJgcdYxJ3QD3qOtQo6/NQsGnjbyiUxaEpjfqyT1N +uzWD81Q8uXGNS8yD6dDynt/lseBjyp2nfC3uQ5fY18VdIcu0MJ9pezBUKrNuhlsy +XXEZ2DXj4sY8QOvIcBqSB/zmS1nGEK55xrtkaiaNrY8fe8wRVpcPLxy+P225NFw+ +B69FJRA0Lj6Jt9BM4hV/3MSIEWwTVhuw4E02ywDYTzz1wq3ITf0tsbIPn0hXQMxD +ohhAoKioM6u+yHtqsxD0eYaAWmHTVn5oDvOSGpvCpBfWHyA7FP5UQak0fKABEAgK +iQYEnb294AXwXymJttfGTIV/Ne4tLN5dIpNma8UO8rlThlcr6xnTQDbR3gkTDRsC +AwEAATANBgkqhkiG9w0BAQUFAAOCAgEAj7J8fy1LUWoVWnrXDAC9jwJ1nI/YjoSU +6ywke3o04+nZC5S+dPnuVy+HAwsU940CoNvP6RStI/bH6JL+NIqEFmwM3M8xIEWV +MYVPkfvQUxxGvDnaY7vv93u+6Q77HV3qlhAQBHChyuXyO7TG3+WzsiT9AnBNtAP0 +4jClt5kCAQXLO/p0SFEZQ8Ru9SM8d1i73Z0VDVzs8jYWlBhiherSgbw1xK4wBOpJ +43XmjZsBSrDpiAXd07Ak3UL2GjfT7eStgebL3UIe39ThE/s/+l43bh0M6WbOBvyQ +i/rZ50kd1GvN0xnZhtv07hIJWO85FGWi7Oet8AzdUZJ17v1Md/f2vdhPVTFN9q+w +mQ6LxjackqCvaJaQfBEbqsn2Tklxk4tZuDioiQbOElT2e6vljQVJWIfNx38Ny2LM +aiXQPQu+4CI7meAh5gXM5nyJGbZvRPsxj89CqYzyHCYs5HBP3AsviBvn26ziOF+c +544VmHd9HkIv8UTC29hh+R64RlgMQQQdaXFaUrFPTs/do0k8n/c2bPc0iTdfi5Q2 +gq6Vi8q6Ay5wGgTtRRbn/mWKuCFjEh94z6pF9Xr06NX0PuEOdf+Ls9vI5vz6G0w6 +0Li7devEN7EKBY+7Mcjg918yq9i5tEiMkUgT68788t3fTC+4iUQ5fDtdrHsaOlIR +8bs/XQVNE/s= +-----END CERTIFICATE----- diff --git a/tests/client.key b/tests/client.key new file mode 100644 index 000000000..87a25311c --- /dev/null +++ b/tests/client.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/tests/test_ssl_verify.rb b/tests/test_ssl_verify.rb new file mode 100644 index 000000000..32570fa9a --- /dev/null +++ b/tests/test_ssl_verify.rb @@ -0,0 +1,82 @@ +$:.unshift "../lib" +require 'eventmachine' +require 'test/unit' + +class TestSslVerify < Test::Unit::TestCase + + def setup + $dir = File.dirname(File.expand_path(__FILE__)) + '/' + $cert_from_file = File.read($dir+'client.crt') + end + + module Client + def connection_completed + start_tls(:private_key_file => $dir+'client.key', :cert_chain_file => $dir+'client.crt') + end + + def ssl_handshake_completed + $client_handshake_completed = true + close_connection + end + + def unbind + EM.stop_event_loop + end + end + + module AcceptServer + def post_init + start_tls(:verify_peer => true) + end + + def ssl_verify_peer(cert) + $cert_from_server = cert + true + end + + def ssl_handshake_completed + $server_handshake_completed = true + end + end + + module DenyServer + def post_init + start_tls(:verify_peer => true) + end + + def ssl_verify_peer(cert) + $cert_from_server = cert + # Do not accept the peer. This should now cause the connection to shut down without the SSL handshake being completed. + false + end + + def ssl_handshake_completed + $server_handshake_completed = true + end + end + + def test_accept_server + $client_handshake_completed, $server_handshake_completed = false, false + EM.run { + EM.start_server("127.0.0.1", 16784, AcceptServer) + EM.connect("127.0.0.1", 16784, Client).instance_variable_get("@signature") + } + + assert_equal($cert_from_file, $cert_from_server) + assert($client_handshake_completed) + assert($server_handshake_completed) + end + + def test_deny_server + $client_handshake_completed, $server_handshake_completed = false, false + EM.run { + EM.start_server("127.0.0.1", 16784, DenyServer) + EM.connect("127.0.0.1", 16784, Client) + } + + assert_equal($cert_from_file, $cert_from_server) + assert(!$client_handshake_completed) + assert(!$server_handshake_completed) + end + +end