Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Additional options for SSL certificate verification #84

Open
wants to merge 1 commit into from

7 participants

@miketheiron

As part of the project I'm working on, I added a couple of extra options to eventmachine evma_set_tls_parms for dealing with SSL certificate verification, to more easily allow our thin server to act as a client to a remote server over SSL.

I added an optional private key file password parameter that can be used to unlock a password protected private key file.

I also added an optional ca certificate file parameter that can be used to register additional CAs with SSL_CTX_load_verify_locations. The remote server we connect to has a certificate issued by our company CA which is not in a standard trust hierarchy, and I thought with such relatively simply verification requirements it would be better to handle this internally within eventmachine, rather than pushing the logic up to ruby's verify_peer every time, just to do the same thing. Ruby's verify_peer method is still called in this case, but our server doesn't do any additional verification.

If the implementation of these changes is acceptable, I thought they might make a useful addition.

This is the first time I've played around with eventmachine so apologies if this isn't the right approach to solving this problem - any more elegent solutions would be welcome! but I suppose at the very least, this pull request is a good way to articulate what I'm trying to achieve, and find the right solution to it.

Any feedback/suggestions are greatly appreciated,

Mike

@jfairbairn

Hello,

Any chance this change can be merged into master? It's really useful for passphrase-protected keyfiles, and has been working for us in production for 6 months. :-)

Thanks,
James

@mbulat

I'd like to second the request. We really need to use some passphrase protected key files, and I'd rather keep the mainline eventmachine in my gemfile than a patch or fork.

Thanks,
Mike

@ibc
ibc commented

This would be really great!

@ibc
ibc commented

Hi @miketheiron, I asked in eventmachine maillist about this pull request and Aman has replied:

"I am happy to merge that pull, but it has conflicts so I cannot use the merge button on github."

Maybe it's due to oother changes in the affected files. Would it be possible to refactor this pull request based on current master branch?

@ibc
ibc commented

I will try to update the patch.

@ibc
ibc commented

Hi, I've applied this pull request into my fork (updated to master branch today 11-11-08): https://github.com/ibc/eventmachine

It compiles and so, but fails in cases in which it worked. ssl_verify_peer(cert) is called never!

I've made a script that performs the TLS certificate(s) retrieval from a server. You can get it from here: http://public.aliax.net/check-remote-tls.rb

If I try my script with not patched eventmachine I get the expected output:

~# ./check-remote-tls.rb github.com 443

<< connection_completed

<< ssl_verify_peer => cert:

#<OpenSSL::X509::Certificate subject=/C=US/O=DigiCert
Inc/OU=www.digicert.com/CN=DigiCert High Assurance EV CA-1,
issuer=/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert High
Assurance EV Root CA, serial=11608356136354358657469651816937576901,
not_before=2006-11-10 00:00:00 UTC, not_after=2021-11-10 00:00:00 UTC>

<< ssl_verify_peer => cert:

#<OpenSSL::X509::Certificate subject=/businessCategory=Private
Organization/1.3.6.1.4.1.311.60.2.1.3=US/1.3.6.1.4.1.311.60.2.1.2=California/serialNumber=C3268102/C=US/ST=California/L=San
Francisco/O=GitHub, Inc./CN=github.com, issuer=/C=US/O=DigiCert
Inc/OU=www.digicert.com/CN=DigiCert High Assurance EV CA-1,
serial=19229479553765830569163959417678365365, not_before=2011-05-27
00:00:00 UTC, not_after=2013-07-29 12:00:00 UTC>

<< ssl_handshake_completed

However if I run the script with the pull request applied, I get this:

~# ./check-remote-tls.rb github.com 443

<< connection_completed

(no more). So ssl_verify_peer(cert) is never called.

@ibc
ibc commented

Found the problem; it occurs in case I don't fill :ca_file in start_tls() !!!
Of course such field should NOT be mandatory!

@ibc
ibc commented

By looking into ext/ssl.cpp it seems that some of the features of this pull request are just for TLS client mode. This is not good, they should be valid for both TLS client and server modes.

@ibc
ibc commented

Ok, now I understand that the code in this pull request does not call ssl_verify_peer(cert) in case validation fails. I've added key password and ca_file also to SSL client and server side.

@ibc
ibc commented

IMHO there should be a new callback EM::Connection#ssl_verification_failed(Fixnum error_code, String error_string).

@thoughtless

Is there still interest in putting support for verifying SSL certs into EM? According to the current docs, "It is up to user defined code to perform a check on the certificates." However, after a discussion with some authors of em-http-request it seems that it is pretty much impossible for the user defined code to perform a proper SSL handshake.

I would love to help in any way I can on this, but most of the relevant code is in C, and my ability to use C is pretty limited.

@ibc

Nothing will change in EventMachine at C++ level, never.

@miketheiron

Hi, very sorry for not replying to this sooner, especially considering you've taken the time to investigate and comment on the patch. Annoyingly I'd used an old email address and forgot to check until recently.

This patch was a very noob attempt to integrate the features we wanted into em, and so I am more than happy to try and incorporate your very constructive feedback to make do it right.

A suggestion has been made to me from our side to perhaps split this into two separate pulls so that I'm not conflating two different changes:

  1. Support for pwd protected private key files
  2. Support for custom cafile, use of SSL_CTX_load_verify_locations to verify certificates against the cafile, and addition of the suggested EM::Connection#ssl_verification_failed(Fixnum error_code, String error_string) to make available any such errors

But I'm more than happy to break this down however you guys prefer.

Thanks for all your help, and constructive feedback, it's very much appreciated

Mike

@burke burke referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@burke burke referenced this pull request from a commit in burke/eventmachine
Burke Libbey Merged #84. This improves support for providing credentials to OpenSSL
Conflicts:
	lib/em/connection.rb
ea3f730
@burke

I'm working on improving SSL support in eventmachine. I've merged these changes and made some other improvements.

In particular, the ssl_verify_wrapper can now check against an expected CN, and passes this result (pass or fail) to ruby's ssl_verify_peer if the method arity is > 1.

I still have some work to do on documentation, testing, and such, but would appreciate any feedback anyone has.

https://github.com/burke/eventmachine/blob/ssl_improvements/ext/ssl.cpp#L433

https://github.com/burke/eventmachine/commits/ssl_improvements

@rud

Poking this zombie issue - is this completely at a stalemate? Where have everybody gone with this in their projects?

@burke

We rewrote everything in Go. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jun 1, 2010
This page is out of date. Refresh to see the latest.
View
6 ext/cmain.cpp
@@ -410,14 +410,16 @@ extern "C" void evma_start_tls (const unsigned long binding)
/******************
evma_set_tls_parms
+ * [MH] Added ca_filename - an optional certificate authority file provided to SSL_CTX_load_verify_locations for client-side server certificate verification using this list of known trusted CAs
+ * [MH] Added privatekey_pwd - an optional password used to decrypt the privatekey_filename
******************/
-extern "C" void evma_set_tls_parms (const unsigned long binding, const char *privatekey_filename, const char *certchain_filename, int verify_peer)
+extern "C" void evma_set_tls_parms (const unsigned long binding, const char *ca_filename, const char *privatekey_filename, const char *privatekey_pwd, const char *certchain_filename, int verify_peer)
{
ensure_eventmachine("evma_set_tls_parms");
EventableDescriptor *ed = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (binding));
if (ed)
- ed->SetTlsParms (privatekey_filename, certchain_filename, (verify_peer == 1 ? true : false));
+ ed->SetTlsParms (ca_filename, privatekey_filename, privatekey_pwd, certchain_filename, (verify_peer == 1 ? true : false));
}
/******************
View
11 ext/ed.cpp
@@ -1037,7 +1037,7 @@ void ConnectionDescriptor::StartTls()
if (SslBox)
throw std::runtime_error ("SSL/TLS already running on connection");
- SslBox = new SslBox_t (bIsServer, PrivateKeyFilename, CertChainFilename, bSslVerifyPeer, GetBinding());
+ SslBox = new SslBox_t (bIsServer, CAFilename, PrivateKeyFilename, PrivateKeyPwd, CertChainFilename, bSslVerifyPeer, GetBinding());
_DispatchCiphertext();
#endif
@@ -1051,13 +1051,18 @@ void ConnectionDescriptor::StartTls()
ConnectionDescriptor::SetTlsParms
*********************************/
-void ConnectionDescriptor::SetTlsParms (const char *privkey_filename, const char *certchain_filename, bool verify_peer)
+void ConnectionDescriptor::SetTlsParms (const char *ca_filename, const char *privkey_filename, const char *privkey_pwd, const char *certchain_filename, bool verify_peer)
{
#ifdef WITH_SSL
if (SslBox)
throw std::runtime_error ("call SetTlsParms before calling StartTls");
- if (privkey_filename && *privkey_filename)
+ if (privkey_filename && *privkey_filename) {
PrivateKeyFilename = privkey_filename;
+ if (privkey_pwd && *privkey_pwd)
+ PrivateKeyPwd = privkey_pwd;
+ }
+ if (ca_filename && *ca_filename)
+ CAFilename = ca_filename;
if (certchain_filename && *certchain_filename)
CertChainFilename = certchain_filename;
bSslVerifyPeer = verify_peer;
View
6 ext/ed.h
@@ -69,7 +69,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, bool verify_peer) {}
+ virtual void SetTlsParms (const char *ca_filename, const char *privkey_filename, const char *privkey_file_pwd, const char *certchain_filename, bool verify_peer) {}
#ifdef WITH_SSL
virtual X509 *GetPeerCert() {return NULL;}
@@ -182,7 +182,7 @@ class ConnectionDescriptor: public EventableDescriptor
virtual int GetOutboundDataSize() {return OutboundDataSize;}
virtual void StartTls();
- virtual void SetTlsParms (const char *privkey_filename, const char *certchain_filename, bool verify_peer);
+ virtual void SetTlsParms (const char *ca_filename, const char *privkey_filename, const char *privkey_pwd, const char *certchain_filename, bool verify_peer);
#ifdef WITH_SSL
virtual X509 *GetPeerCert();
@@ -225,7 +225,9 @@ class ConnectionDescriptor: public EventableDescriptor
#ifdef WITH_SSL
SslBox_t *SslBox;
std::string CertChainFilename;
+ std::string CAFilename;
std::string PrivateKeyFilename;
+ std::string PrivateKeyPwd;
bool bHandshakeSignaled;
bool bSslVerifyPeer;
bool bSslPeerAccepted;
View
2  ext/eventmachine.h
@@ -63,7 +63,7 @@ extern "C" {
const unsigned long evma_create_unix_domain_server (const char *filename);
const unsigned long evma_open_datagram_socket (const char *server, int port);
const unsigned long evma_open_keyboard();
- void evma_set_tls_parms (const unsigned long binding, const char *privatekey_filename, const char *certchain_filenane, int verify_peer);
+ void evma_set_tls_parms (const unsigned long binding, const char *ca_filename, const char *privatekey_filename, const char *privatekey_pwd, const char *certchain_filenane, int verify_peer);
void evma_start_tls (const unsigned long binding);
#ifdef WITH_SSL
View
9 ext/rubymain.cpp
@@ -260,16 +260,19 @@ static VALUE t_start_tls (VALUE self, VALUE signature)
/***************
t_set_tls_parms
+ * [MH] Added cafile - an optional certificate authority file provided to SSL_CTX_load_verify_locations for client-side server certificate verification using this list of known trusted CAs
+ * [MH] Added privkeypwd - an optional password used to decrypt the privatekey_filename
+
***************/
-static VALUE t_set_tls_parms (VALUE self, VALUE signature, VALUE privkeyfile, VALUE certchainfile, VALUE verify_peer)
+static VALUE t_set_tls_parms (VALUE self, VALUE signature, VALUE cafile, VALUE privkeyfile, VALUE privkeypwd, 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 (NUM2ULONG (signature), StringValuePtr (privkeyfile), StringValuePtr (certchainfile), (verify_peer == Qtrue ? 1 : 0));
+ evma_set_tls_parms (NUM2ULONG (signature), StringValuePtr (cafile), StringValuePtr (privkeyfile), StringValuePtr (privkeypwd), StringValuePtr (certchainfile), (verify_peer == Qtrue ? 1 : 0));
return Qnil;
}
@@ -1074,7 +1077,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, 4);
+ rb_define_module_function (EmModule, "set_tls_parms", (VALUE(*)(...))t_set_tls_parms, 6);
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);
View
26 ext/ssl.cpp
@@ -118,9 +118,11 @@ static void InitializeDefaultCredentials()
/**************************
SslContext_t::SslContext_t
+ * [MH] Added cafile - an optional certificate authority file provided to SSL_CTX_load_verify_locations for client-side server certificate verification using this list of known trusted CAs
+ * [MH] Added privkeypwd - an optional password used to decrypt the privatekey_filename
**************************/
-SslContext_t::SslContext_t (bool is_server, const string &privkeyfile, const string &certchainfile):
+SslContext_t::SslContext_t (bool is_server, const string &cafile, const string &privkeyfile, const string &privkeypwd, const string &certchainfile):
pCtx (NULL),
PrivateKey (NULL),
Certificate (NULL)
@@ -176,6 +178,11 @@ SslContext_t::SslContext_t (bool is_server, const string &privkeyfile, const str
else {
int e;
if (privkeyfile.length() > 0) {
+ // if a private key password is provided then set it in this context. note that that assumes
+ // the current approach of having a unique context per request
+ if (privkeypwd.length() > 0) {
+ SSL_CTX_set_default_passwd_cb_userdata(pCtx, const_cast<char*>(privkeypwd.c_str()));
+ }
e = SSL_CTX_use_PrivateKey_file (pCtx, privkeyfile.c_str(), SSL_FILETYPE_PEM);
assert (e > 0);
}
@@ -183,6 +190,11 @@ SslContext_t::SslContext_t (bool is_server, const string &privkeyfile, const str
e = SSL_CTX_use_certificate_chain_file (pCtx, certchainfile.c_str());
assert (e > 0);
}
+ // load trusted ca cert chain for validation of server certificatess
+ if (cafile.length() > 0) {
+ e = SSL_CTX_load_verify_locations(pCtx, const_cast<char*>(cafile.c_str()), 0);
+ assert (e > 0);
+ }
}
}
@@ -206,9 +218,11 @@ SslContext_t::~SslContext_t()
/******************
SslBox_t::SslBox_t
+ * [MH] Added cafile - an optional certificate authority file provided to SSL_CTX_load_verify_locations for client-side server certificate verification using this list of known trusted CAs
+ * [MH] Added privkeypwd - an optional password used to decrypt the privatekey_filename
******************/
-SslBox_t::SslBox_t (bool is_server, const string &privkeyfile, const string &certchainfile, bool verify_peer, const unsigned long binding):
+SslBox_t::SslBox_t (bool is_server, const string &cafile, const string &privkeyfile, const string &privkeypwd, const string &certchainfile, bool verify_peer, const unsigned long binding):
bIsServer (is_server),
bHandshakeCompleted (false),
bVerifyPeer (verify_peer),
@@ -220,7 +234,7 @@ SslBox_t::SslBox_t (bool is_server, const string &privkeyfile, const string &cer
* a new one every time we come here.
*/
- Context = new SslContext_t (bIsServer, privkeyfile, certchainfile);
+ Context = new SslContext_t (bIsServer, cafile, privkeyfile, privkeypwd, certchainfile);
assert (Context);
pbioRead = BIO_new (BIO_s_mem());
@@ -433,6 +447,11 @@ ssl_verify_wrapper
extern "C" int ssl_verify_wrapper(int preverify_ok, X509_STORE_CTX *ctx)
{
+ // if the pre verification has failed, then don't bother validating via ruby
+ if (preverify_ok != 1) {
+ return preverify_ok;
+ }
+
unsigned long binding;
X509 *cert;
SSL *ssl;
@@ -441,6 +460,7 @@ extern "C" int ssl_verify_wrapper(int preverify_ok, X509_STORE_CTX *ctx)
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 = (unsigned long) SSL_get_ex_data(ssl, 0);
View
4 ext/ssl.h
@@ -33,7 +33,7 @@ class SslContext_t
class SslContext_t
{
public:
- SslContext_t (bool is_server, const string &privkeyfile, const string &certchainfile);
+ SslContext_t (bool is_server, const string &cafile, const string &privkeyfile, const string &privkeypwd, const string &certchainfile);
virtual ~SslContext_t();
private:
@@ -57,7 +57,7 @@ class SslBox_t
class SslBox_t
{
public:
- SslBox_t (bool is_server, const string &privkeyfile, const string &certchainfile, bool verify_peer, const unsigned long binding);
+ SslBox_t (bool is_server, const string &cafile, const string &privkeyfile, const string &privkeypwd, const string &certchainfile, bool verify_peer, const unsigned long binding);
virtual ~SslBox_t();
int PutPlaintext (const char*, int);
View
15 lib/em/connection.rb
@@ -256,6 +256,11 @@ def connection_completed
# and other options to be used with this Connection object. Here are the currently-supported
# options:
#
+ # * :ca_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
+ #
# * :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
@@ -266,6 +271,10 @@ def connection_completed
# 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.
#
+ # * :private_key_pwd :
+ # takes a String, which is interpreted as the password for the private_key_file
+ # specified above. if no password is supplied the file is opened without one.
+ #
# * :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
@@ -297,7 +306,7 @@ def connection_completed
# behaved than the ones for raw chunks of memory.
#
def start_tls args={}
- priv_key, cert_chain, verify_peer = args.values_at(:private_key_file, :cert_chain_file, :verify_peer)
+ ca_file, priv_key, priv_key_pwd, cert_chain, verify_peer = args.values_at(:ca_file, :private_key_file, :private_key_pwd, :cert_chain_file, :verify_peer)
[priv_key, cert_chain].each do |file|
next if file.nil? or file.empty?
@@ -305,7 +314,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 || '', verify_peer)
+ EventMachine::set_tls_parms(@signature, ca_file || '', priv_key || '', priv_key_pwd || '', cert_chain || '', verify_peer)
EventMachine::start_tls @signature
end
@@ -561,4 +570,4 @@ def paused?
EventMachine::connection_paused? @signature
end
end
-end
+end
Something went wrong with that request. Please try again.