updated verify certs callback, inorder only to verify the hostname when dealing with the leaf certificate

This commit is contained in:
HoneyryderChuck 2020-11-16 12:45:42 +00:00
parent 384d273715
commit da21b4a342
7 changed files with 52 additions and 28 deletions

View File

@ -197,7 +197,7 @@ module Faraday
response_headers.merge!(response.headers)
end
@app.call(env)
rescue OpenSSL::SSL::SSLError => e
rescue ::HTTPX::TLSError => e
raise SSL_ERROR, e
rescue Errno::ECONNABORTED,
Errno::ECONNREFUSED,

View File

@ -444,7 +444,7 @@ module HTTPX
throw(:jump_tick)
rescue Errno::ECONNREFUSED,
Errno::EADDRNOTAVAIL,
OpenSSL::SSL::SSLError => e
TLSError => e
# connect errors, exit gracefully
handle_error(e)
@state = :closed

View File

@ -149,7 +149,7 @@ module RubyTls
attach_function :X509_STORE_CTX_get_current_cert, [:pointer], :x509
attach_function :SSL_get_ex_data_X509_STORE_CTX_idx, [], :int
attach_function :X509_STORE_CTX_get_ex_data, %i[pointer int], :ssl
attach_function :PEM_write_bio_X509, %i[bio x509], :int
attach_function :PEM_write_bio_X509, %i[bio x509], :bool
# SSL Context Class
# OpenSSL before 1.1.0 do not have these methods
@ -219,9 +219,10 @@ module RubyTls
attach_function :SSL_ctrl, %i[ssl int long pointer], :long
SSL_CTRL_SET_TLSEXT_HOSTNAME = 55
def self.SSL_set_tlsext_host_name(ssl, host_name)
name = FFI::MemoryPointer.from_string(host_name)
SSL_ctrl(ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, name)
name_ptr = FFI::MemoryPointer.from_string(host_name)
raise "error setting SNI hostname" if SSL_ctrl(ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, name_ptr) == 0
end
# Server Name Indication (SNI) Support
@ -781,22 +782,29 @@ module RubyTls
end
end
VerifyCB = FFI::Function.new(:int, %i[int pointer]) do |_preverify_ok, x509_store|
x509 = SSL.X509_STORE_CTX_get_current_cert(x509_store)
ssl = SSL.X509_STORE_CTX_get_ex_data(x509_store, SSL.SSL_get_ex_data_X509_STORE_CTX_idx)
VerifyCB = FFI::Function.new(:int, %i[int pointer]) do |preverify_ok, x509_store|
if preverify_ok.zero?
1
else
x509 = SSL.X509_STORE_CTX_get_current_cert(x509_store)
ssl = SSL.X509_STORE_CTX_get_ex_data(x509_store, SSL.SSL_get_ex_data_X509_STORE_CTX_idx)
bio_out = SSL.BIO_new(SSL.BIO_s_mem)
SSL.PEM_write_bio_X509(bio_out, x509)
bio_out = SSL.BIO_new(SSL.BIO_s_mem)
ret = SSL.PEM_write_bio_X509(bio_out, x509)
unless ret
SSL.BIO_free(bio_out)
raise "Error reading certificate"
end
len = SSL.BIO_pending(bio_out)
buffer = FFI::MemoryPointer.new(:char, len, false)
size = SSL.BIO_read(bio_out, buffer, len)
len = SSL.BIO_pending(bio_out)
buffer = FFI::MemoryPointer.new(:char, len, false)
size = SSL.BIO_read(bio_out, buffer, len)
# THis is the callback into the ruby class
result = InstanceLookup[ssl.address].verify(buffer.read_string(size))
SSL.BIO_free(bio_out)
result
# THis is the callback into the ruby class
cert = buffer.read_string(size)
SSL.BIO_free(bio_out)
InstanceLookup[ssl.address].verify(cert)
end
end
def pending_data(bio)

View File

@ -3,6 +3,8 @@
require "openssl"
module HTTPX
TLSError = OpenSSL::SSL::SSLError
class SSL < TCP
TLS_OPTIONS = if OpenSSL::SSL::SSLContext.instance_methods.include?(:alpn_protocols)
{ alpn_protocols: %w[h2 http/1.1] }

View File

@ -4,6 +4,7 @@ require "httpx/io/ruby-tls"
require "openssl"
module HTTPX
TLSError = Class.new(StandardError)
class SSL < TCP
def initialize(_, _, options)
super
@ -96,8 +97,8 @@ module HTTPX
#
# signals TLS invalid status / shutdown.
def close_cb
log { "TLS closing" }
transport_close
log { "Error, TLS closing" }
raise TLSError, "SSL Error"
end
# TLS callback.
@ -115,10 +116,11 @@ module HTTPX
# passed the peer +cert+ to be verified.
#
def verify_cb(cert)
raise "Peer verification enabled, but no certificate received." if cert.nil?
raise TLSError, "Peer verification enabled, but no certificate received." if cert.nil?
log { "TLS verifying #{cert}" }
@peer_cert = OpenSSL::X509::Certificate.new(cert)
# by default one doesn't verify client certificates in the server
verify_hostname(@sni_hostname)
end
@ -126,8 +128,10 @@ module HTTPX
# copied from:
# https://github.com/ruby/ruby/blob/8cbf2dae5aadfa5d6241b0df2bf44d55db46704f/ext/openssl/lib/openssl/ssl.rb#L395-L409
#
def verify_hostname(peer_cert)
OpenSSL::SSL.verify_certificate_identity(peer_cert, @hostname)
def verify_hostname(host)
return false unless @ctx.verify_peer && @peer_cert
OpenSSL::SSL.verify_certificate_identity(@peer_cert, host)
end
private
@ -147,8 +151,18 @@ module HTTPX
options = {}
options[:verify_peer] = !ssl_options.key?(:verify_mode) || ssl_options[:verify_mode] != OpenSSL::SSL::VERIFY_NONE
options[:version] = ssl_options[:ssl_version] if ssl_options.key?(:ssl_version)
# options[:private_key] = tls[:key] if tls.key?(:key)
options[:cert_chain] = ssl_options[:cert_store] if ssl_options.key?(:cert_store)
if ssl_options.key?(:key)
private_key = ssl_options[:key]
private_key = private_key.to_pem if private_key.respond_to?(:to_pem)
options[:private_key] = private_key
end
if ssl_options.key?(:ca_path) || ssl_options.key?(:ca_file)
ca_path = ssl_options[:ca_path] || ssl_options[:ca_file].path
options[:cert_chain] = ca_path
end
options[:ciphers] = ssl_options[:ciphers] if ssl_options.key?(:ciphers)
options[:protocols] = ssl_options.fetch(:alpn_protocols, %w[h2 http/1.1])
options[:fallback] = "http/1.1"

View File

@ -17,7 +17,7 @@ module HTTPX
Errno::ECONNRESET,
Errno::ECONNABORTED,
Errno::EPIPE,
(OpenSSL::SSL::SSLError if defined?(OpenSSL)),
(TLSError if defined?(TLSError)),
TimeoutError,
Parser::Error,
Errno::EINVAL,

View File

@ -34,9 +34,9 @@ class FaradayTest < Minitest::Test
assert JSON.parse(res.body.to_s)["gzipped"]
end
SYSTEM_CERT_STORE_DIR = "/usr/share/ca-certificates/mozilla"
def test_adapter_get_ssl_fails_with_bad_cert
fake_store = OpenSSL::X509::Store.new
conn = create_connection(ssl: { cert_store: fake_store, verify: OpenSSL::SSL::VERIFY_PEER })
conn = create_connection(ssl: { ca_path: SYSTEM_CERT_STORE_DIR, verify: OpenSSL::SSL::VERIFY_PEER })
err = assert_raises Faraday::Adapter::HTTPX::SSL_ERROR do
conn.get(build_path("/get"))
end