Skip to content

Commit

Permalink
ssl: set verify error code in the case of verify_hostname failure
Browse files Browse the repository at this point in the history
When the verify_hostname option is enabled, the hostname verification is
done before calling verify_callback provided by the user.

The callback should be notified of the hostname verification failure.
OpenSSL::X509::StoreContext's error code must be set to an appropriate
value rather than OpenSSL::X509::V_OK.

If the constant X509_V_ERR_HOSTNAME_MISMATCH is available (OpenSSL >=
1.0.2), use it. Otherwise use the generic X509_V_ERR_CERT_REJECTED.

Reference: ruby#244
Fixes: 028e495 ("ssl: add verify_hostname option to SSLContext", 2016-06-27)
  • Loading branch information
rhenium committed Feb 24, 2020
1 parent 65ea09c commit 74ef8c0
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 1 deletion.
9 changes: 8 additions & 1 deletion ext/openssl/ossl_ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,14 @@ ossl_ssl_verify_callback(int preverify_ok, X509_STORE_CTX *ctx)
rb_ivar_set(ssl_obj, ID_callback_state, INT2NUM(status));
return 0;
}
preverify_ok = ret == Qtrue;
if (ret != Qtrue) {
preverify_ok = 0;
#if defined(X509_V_ERR_HOSTNAME_MISMATCH)
X509_STORE_CTX_set_error(ctx, X509_V_ERR_HOSTNAME_MISMATCH);
#else
X509_STORE_CTX_set_error(ctx, X509_V_ERR_CERT_REJECTED);
#endif
}
}

return ossl_verify_cb_call(cb, preverify_ok, ctx);
Expand Down
40 changes: 40 additions & 0 deletions test/test_ssl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,46 @@ def test_verify_hostname_on_connect
end
end

def test_verify_hostname_failure_error_code
ctx_proc = proc { |ctx|
exts = [
["keyUsage", "keyEncipherment,digitalSignature", true],
["subjectAltName", "DNS:a.example.com"],
]
ctx.cert = issue_cert(@svr, @svr_key, 4, exts, @ca_cert, @ca_key)
ctx.key = @svr_key
}

start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port|
verify_callback_ok = verify_callback_err = nil

ctx = OpenSSL::SSL::SSLContext.new
ctx.verify_hostname = true
ctx.cert_store = OpenSSL::X509::Store.new
ctx.cert_store.add_cert(@ca_cert)
ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
ctx.verify_callback = -> (preverify_ok, store_ctx) {
verify_callback_ok = preverify_ok
verify_callback_err = store_ctx.error
preverify_ok
}

begin
sock = TCPSocket.new("127.0.0.1", port)
ssl = OpenSSL::SSL::SSLSocket.new(sock, ctx)
ssl.hostname = "b.example.com"
assert_handshake_error { ssl.connect }
assert_equal false, verify_callback_ok
code_expected = openssl?(1, 0, 2) || defined?(OpenSSL::X509::V_ERR_HOSTNAME_MISMATCH) ?
OpenSSL::X509::V_ERR_HOSTNAME_MISMATCH :
OpenSSL::X509::V_ERR_CERT_REJECTED
assert_equal code_expected, verify_callback_err
ensure
sock&.close
end
end
end

def test_unset_OP_ALL
ctx_proc = Proc.new { |ctx|
# If OP_DONT_INSERT_EMPTY_FRAGMENTS is not defined, this test is
Expand Down

0 comments on commit 74ef8c0

Please sign in to comment.