Skip to content

Commit

Permalink
ssl: show reason of 'certificate verify error' in exception message
Browse files Browse the repository at this point in the history
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("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 `<main>'
  • Loading branch information
rhenium committed Jan 20, 2017
1 parent b67f042 commit 3334ebe
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 0 deletions.
20 changes: 20 additions & 0 deletions ext/openssl/ossl_ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -1521,6 +1521,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);

Expand Down Expand Up @@ -1554,6 +1557,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));
}
Expand Down
24 changes: 24 additions & 0 deletions test/test_ssl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,30 @@ def test_verify_hostname_on_connect
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*')
Expand Down

0 comments on commit 3334ebe

Please sign in to comment.