From 654e024beec5a4f6deb05c8c7994544aabd1e825 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Fri, 20 Jan 2017 23:12:10 +0900 Subject: ssl: show reason of 'certificate verify error' in exception message The 'certificate verify error' is one of the most common errors that can be raised by OpenSSL::SSL::SSLSocket#connect. The certificate verification may fail due to many different issues such as misconfigured trusted certificate store or inaccurate system clock. Unfortunately, since the detail is not put to the queue and is only accessible through OpenSSL::SSL::SSLSocket#verify_result, it is sometimes hard to figure out the real reason. Let's include a human readable reason message in the exception message. Like this: require "socket" require "openssl" ctx = OpenSSL::SSL::SSLContext.new ctx.set_params(cert_store: OpenSSL::X509::Store.new) ssl = OpenSSL::SSL::SSLSocket.new(Socket.tcp("www.ruby-lang.org", 443), ctx) ssl.connect #=> -:7:in `connect': SSL_connect returned=1 errno=0 state=error: certificate verify failed (unable to get local issuer certificate) (OpenSSL::SSL::SSLError) from -:7:in `
' --- ext/openssl/ossl_ssl.c | 20 ++++++++++++++++++++ test/test_ssl.rb | 24 ++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index eef7dbec..fae4c66a 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -1525,6 +1525,9 @@ ossl_start_ssl(VALUE self, int (*func)(), const char *funcname, VALUE opts) int ret, ret2; VALUE cb_state; int nonblock = opts != Qfalse; +#if defined(SSL_R_CERTIFICATE_VERIFY_FAILED) + unsigned long err; +#endif rb_ivar_set(self, ID_callback_state, Qnil); @@ -1558,6 +1561,23 @@ ossl_start_ssl(VALUE self, int (*func)(), const char *funcname, VALUE opts) case SSL_ERROR_SYSCALL: if (errno) rb_sys_fail(funcname); ossl_raise(eSSLError, "%s SYSCALL returned=%d errno=%d state=%s", funcname, ret2, errno, SSL_state_string_long(ssl)); +#if defined(SSL_R_CERTIFICATE_VERIFY_FAILED) + case SSL_ERROR_SSL: + err = ERR_peek_last_error(); + if (ERR_GET_LIB(err) == ERR_LIB_SSL && + ERR_GET_REASON(err) == SSL_R_CERTIFICATE_VERIFY_FAILED) { + const char *err_msg = ERR_reason_error_string(err), + *verify_msg = X509_verify_cert_error_string(SSL_get_verify_result(ssl)); + if (!err_msg) + err_msg = "(null)"; + if (!verify_msg) + verify_msg = "(null)"; + ossl_clear_error(); /* let ossl_raise() not append message */ + ossl_raise(eSSLError, "%s returned=%d errno=%d state=%s: %s (%s)", + funcname, ret2, errno, SSL_state_string_long(ssl), + err_msg, verify_msg); + } +#endif default: ossl_raise(eSSLError, "%s returned=%d errno=%d state=%s", funcname, ret2, errno, SSL_state_string_long(ssl)); } diff --git a/test/test_ssl.rb b/test/test_ssl.rb index 8d74f25f..2b6d9291 100644 --- a/test/test_ssl.rb +++ b/test/test_ssl.rb @@ -749,6 +749,30 @@ class OpenSSL::TestSSL < OpenSSL::SSLTestCase end end + def test_connect_certificate_verify_failed_exception_message + start_server(ignore_listener_error: true) { |server, port| + ctx = OpenSSL::SSL::SSLContext.new + ctx.set_params + assert_raise_with_message(OpenSSL::SSL::SSLError, /self signed/) { + server_connect(port, ctx) + } + } + + ctx_proc = proc { |ctx| + ctx.cert = issue_cert(@svr, @svr_key, 30, [], @ca_cert, @ca_key, + not_before: Time.now-100, not_after: Time.now-10) + } + start_server(ignore_listener_error: true, ctx_proc: ctx_proc) { |server, port| + store = OpenSSL::X509::Store.new + store.add_cert(@ca_cert) + ctx = OpenSSL::SSL::SSLContext.new + ctx.set_params(cert_store: store) + assert_raise_with_message(OpenSSL::SSL::SSLError, /expired/) { + server_connect(port, ctx) + } + } + end + def test_multibyte_read_write #German a umlaut auml = [%w{ C3 A4 }.join('')].pack('H*') -- cgit v1.2.3