mirror of
https://github.com/HoneyryderChuck/httpx.git
synced 2025-10-06 00:02:08 -04:00
updated verify certs callback, inorder only to verify the hostname when dealing with the leaf certificate
This commit is contained in:
parent
384d273715
commit
da21b4a342
@ -197,7 +197,7 @@ module Faraday
|
|||||||
response_headers.merge!(response.headers)
|
response_headers.merge!(response.headers)
|
||||||
end
|
end
|
||||||
@app.call(env)
|
@app.call(env)
|
||||||
rescue OpenSSL::SSL::SSLError => e
|
rescue ::HTTPX::TLSError => e
|
||||||
raise SSL_ERROR, e
|
raise SSL_ERROR, e
|
||||||
rescue Errno::ECONNABORTED,
|
rescue Errno::ECONNABORTED,
|
||||||
Errno::ECONNREFUSED,
|
Errno::ECONNREFUSED,
|
||||||
|
@ -444,7 +444,7 @@ module HTTPX
|
|||||||
throw(:jump_tick)
|
throw(:jump_tick)
|
||||||
rescue Errno::ECONNREFUSED,
|
rescue Errno::ECONNREFUSED,
|
||||||
Errno::EADDRNOTAVAIL,
|
Errno::EADDRNOTAVAIL,
|
||||||
OpenSSL::SSL::SSLError => e
|
TLSError => e
|
||||||
# connect errors, exit gracefully
|
# connect errors, exit gracefully
|
||||||
handle_error(e)
|
handle_error(e)
|
||||||
@state = :closed
|
@state = :closed
|
||||||
|
@ -149,7 +149,7 @@ module RubyTls
|
|||||||
attach_function :X509_STORE_CTX_get_current_cert, [:pointer], :x509
|
attach_function :X509_STORE_CTX_get_current_cert, [:pointer], :x509
|
||||||
attach_function :SSL_get_ex_data_X509_STORE_CTX_idx, [], :int
|
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 :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
|
# SSL Context Class
|
||||||
# OpenSSL before 1.1.0 do not have these methods
|
# 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
|
attach_function :SSL_ctrl, %i[ssl int long pointer], :long
|
||||||
SSL_CTRL_SET_TLSEXT_HOSTNAME = 55
|
SSL_CTRL_SET_TLSEXT_HOSTNAME = 55
|
||||||
|
|
||||||
def self.SSL_set_tlsext_host_name(ssl, host_name)
|
def self.SSL_set_tlsext_host_name(ssl, host_name)
|
||||||
name = FFI::MemoryPointer.from_string(host_name)
|
name_ptr = FFI::MemoryPointer.from_string(host_name)
|
||||||
SSL_ctrl(ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, name)
|
raise "error setting SNI hostname" if SSL_ctrl(ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, name_ptr) == 0
|
||||||
end
|
end
|
||||||
|
|
||||||
# Server Name Indication (SNI) Support
|
# Server Name Indication (SNI) Support
|
||||||
@ -781,22 +782,29 @@ module RubyTls
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
VerifyCB = FFI::Function.new(:int, %i[int pointer]) do |_preverify_ok, x509_store|
|
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)
|
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)
|
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)
|
bio_out = SSL.BIO_new(SSL.BIO_s_mem)
|
||||||
SSL.PEM_write_bio_X509(bio_out, x509)
|
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)
|
len = SSL.BIO_pending(bio_out)
|
||||||
buffer = FFI::MemoryPointer.new(:char, len, false)
|
buffer = FFI::MemoryPointer.new(:char, len, false)
|
||||||
size = SSL.BIO_read(bio_out, buffer, len)
|
size = SSL.BIO_read(bio_out, buffer, len)
|
||||||
|
|
||||||
# THis is the callback into the ruby class
|
# THis is the callback into the ruby class
|
||||||
result = InstanceLookup[ssl.address].verify(buffer.read_string(size))
|
cert = buffer.read_string(size)
|
||||||
|
|
||||||
SSL.BIO_free(bio_out)
|
SSL.BIO_free(bio_out)
|
||||||
result
|
InstanceLookup[ssl.address].verify(cert)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def pending_data(bio)
|
def pending_data(bio)
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
require "openssl"
|
require "openssl"
|
||||||
|
|
||||||
module HTTPX
|
module HTTPX
|
||||||
|
TLSError = OpenSSL::SSL::SSLError
|
||||||
|
|
||||||
class SSL < TCP
|
class SSL < TCP
|
||||||
TLS_OPTIONS = if OpenSSL::SSL::SSLContext.instance_methods.include?(:alpn_protocols)
|
TLS_OPTIONS = if OpenSSL::SSL::SSLContext.instance_methods.include?(:alpn_protocols)
|
||||||
{ alpn_protocols: %w[h2 http/1.1] }
|
{ alpn_protocols: %w[h2 http/1.1] }
|
||||||
|
@ -4,6 +4,7 @@ require "httpx/io/ruby-tls"
|
|||||||
require "openssl"
|
require "openssl"
|
||||||
|
|
||||||
module HTTPX
|
module HTTPX
|
||||||
|
TLSError = Class.new(StandardError)
|
||||||
class SSL < TCP
|
class SSL < TCP
|
||||||
def initialize(_, _, options)
|
def initialize(_, _, options)
|
||||||
super
|
super
|
||||||
@ -96,8 +97,8 @@ module HTTPX
|
|||||||
#
|
#
|
||||||
# signals TLS invalid status / shutdown.
|
# signals TLS invalid status / shutdown.
|
||||||
def close_cb
|
def close_cb
|
||||||
log { "TLS closing" }
|
log { "Error, TLS closing" }
|
||||||
transport_close
|
raise TLSError, "SSL Error"
|
||||||
end
|
end
|
||||||
|
|
||||||
# TLS callback.
|
# TLS callback.
|
||||||
@ -115,10 +116,11 @@ module HTTPX
|
|||||||
# passed the peer +cert+ to be verified.
|
# passed the peer +cert+ to be verified.
|
||||||
#
|
#
|
||||||
def verify_cb(cert)
|
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}" }
|
log { "TLS verifying #{cert}" }
|
||||||
@peer_cert = OpenSSL::X509::Certificate.new(cert)
|
@peer_cert = OpenSSL::X509::Certificate.new(cert)
|
||||||
|
|
||||||
# by default one doesn't verify client certificates in the server
|
# by default one doesn't verify client certificates in the server
|
||||||
verify_hostname(@sni_hostname)
|
verify_hostname(@sni_hostname)
|
||||||
end
|
end
|
||||||
@ -126,8 +128,10 @@ module HTTPX
|
|||||||
# copied from:
|
# copied from:
|
||||||
# https://github.com/ruby/ruby/blob/8cbf2dae5aadfa5d6241b0df2bf44d55db46704f/ext/openssl/lib/openssl/ssl.rb#L395-L409
|
# https://github.com/ruby/ruby/blob/8cbf2dae5aadfa5d6241b0df2bf44d55db46704f/ext/openssl/lib/openssl/ssl.rb#L395-L409
|
||||||
#
|
#
|
||||||
def verify_hostname(peer_cert)
|
def verify_hostname(host)
|
||||||
OpenSSL::SSL.verify_certificate_identity(peer_cert, @hostname)
|
return false unless @ctx.verify_peer && @peer_cert
|
||||||
|
|
||||||
|
OpenSSL::SSL.verify_certificate_identity(@peer_cert, host)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@ -147,8 +151,18 @@ module HTTPX
|
|||||||
options = {}
|
options = {}
|
||||||
options[:verify_peer] = !ssl_options.key?(:verify_mode) || ssl_options[:verify_mode] != OpenSSL::SSL::VERIFY_NONE
|
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[: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[:ciphers] = ssl_options[:ciphers] if ssl_options.key?(:ciphers)
|
||||||
options[:protocols] = ssl_options.fetch(:alpn_protocols, %w[h2 http/1.1])
|
options[:protocols] = ssl_options.fetch(:alpn_protocols, %w[h2 http/1.1])
|
||||||
options[:fallback] = "http/1.1"
|
options[:fallback] = "http/1.1"
|
||||||
|
@ -17,7 +17,7 @@ module HTTPX
|
|||||||
Errno::ECONNRESET,
|
Errno::ECONNRESET,
|
||||||
Errno::ECONNABORTED,
|
Errno::ECONNABORTED,
|
||||||
Errno::EPIPE,
|
Errno::EPIPE,
|
||||||
(OpenSSL::SSL::SSLError if defined?(OpenSSL)),
|
(TLSError if defined?(TLSError)),
|
||||||
TimeoutError,
|
TimeoutError,
|
||||||
Parser::Error,
|
Parser::Error,
|
||||||
Errno::EINVAL,
|
Errno::EINVAL,
|
||||||
|
@ -34,9 +34,9 @@ class FaradayTest < Minitest::Test
|
|||||||
assert JSON.parse(res.body.to_s)["gzipped"]
|
assert JSON.parse(res.body.to_s)["gzipped"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
SYSTEM_CERT_STORE_DIR = "/usr/share/ca-certificates/mozilla"
|
||||||
def test_adapter_get_ssl_fails_with_bad_cert
|
def test_adapter_get_ssl_fails_with_bad_cert
|
||||||
fake_store = OpenSSL::X509::Store.new
|
conn = create_connection(ssl: { ca_path: SYSTEM_CERT_STORE_DIR, verify: OpenSSL::SSL::VERIFY_PEER })
|
||||||
conn = create_connection(ssl: { cert_store: fake_store, verify: OpenSSL::SSL::VERIFY_PEER })
|
|
||||||
err = assert_raises Faraday::Adapter::HTTPX::SSL_ERROR do
|
err = assert_raises Faraday::Adapter::HTTPX::SSL_ERROR do
|
||||||
conn.get(build_path("/get"))
|
conn.get(build_path("/get"))
|
||||||
end
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user