fixing cerfificate hostname validation callback

This commit is contained in:
HoneyryderChuck 2020-12-12 00:50:46 +00:00
parent 0b7dbb8cfa
commit 2ecfde95d8
7 changed files with 35 additions and 20 deletions

View File

@ -18,6 +18,8 @@ AllCops:
- 'vendor/**/*' - 'vendor/**/*'
- 'www/**/*' - 'www/**/*'
- 'lib/httpx/extensions.rb' - 'lib/httpx/extensions.rb'
# Do not lint ffi block, for openssl parity
- 'lib/httpx/io/tls/*.rb'
Metrics/ClassLength: Metrics/ClassLength:
Max: 400 Max: 400

View File

@ -36,4 +36,3 @@ Style/Documentation:
Naming/AccessorMethodName: Naming/AccessorMethodName:
Enabled: false Enabled: false

View File

@ -100,7 +100,7 @@ module HTTPX
# signals TLS invalid status / shutdown. # signals TLS invalid status / shutdown.
def close_cb(msg = nil) def close_cb(msg = nil)
log { "TLS Error: #{msg}, closing" } log { "TLS Error: #{msg}, closing" }
raise TLSError, msg || "TLS Error" raise TLSError, "TLSError: certificate verify failed (#{msg})"
end end
# TLS callback. # TLS callback.

View File

@ -15,19 +15,28 @@ module HTTPX
bio_out = SSL.BIO_new(SSL.BIO_s_mem) bio_out = SSL.BIO_new(SSL.BIO_s_mem)
ret = SSL.PEM_write_bio_X509(bio_out, x509) ret = SSL.PEM_write_bio_X509(bio_out, x509)
unless ret if ret
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
cert = buffer.read_string(size)
SSL.BIO_free(bio_out) SSL.BIO_free(bio_out)
raise "Error reading certificate" # InstanceLookup[ssl.address].verify(cert) || preverify_ok.zero? ? 1 : 0
box = InstanceLookup[ssl.address]
hostname_verify = box.verify(cert)
if hostname_verify
1
else
SSL.X509_STORE_CTX_set_error(x509_store, SSL::X509_V_ERR_HOSTNAME_MISMATCH)
0
end
else
SSL.BIO_free(bio_out)
SSL.X509_STORE_CTX_set_error(x509_store, SSL::X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT)
0
end end
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
cert = buffer.read_string(size)
SSL.BIO_free(bio_out)
InstanceLookup[ssl.address].verify(cert) || preverify_ok.zero? ? 1 : 0
end end
attr_reader :is_server, :context, :handshake_completed, :hosts, :ssl_version, :cipher, :verify_peer attr_reader :is_server, :context, :handshake_completed, :hosts, :ssl_version, :cipher, :verify_peer

View File

@ -60,9 +60,9 @@ module HTTPX
set_alpn_negotiation(options[:protocols]) set_alpn_negotiation(options[:protocols])
end end
def cleanup def cleanup
return unless @ssl_ctx return unless @ssl_ctx
SSL.SSL_CTX_free(@ssl_ctx) SSL.SSL_CTX_free(@ssl_ctx)
@ssl_ctx = nil @ssl_ctx = nil
end end
@ -101,9 +101,9 @@ module HTTPX
def set_min_version(version) def set_min_version(version)
return unless version return unless version
num = SSL.const_get("#{version}_VERSION") num = SSL.const_get("#{version}_VERSION")
SSL.SSL_CTX_set_min_proto_version(@ssl_ctx, num) == 1 SSL.SSL_CTX_set_min_proto_version(@ssl_ctx, num) == 1
puts "version done"
rescue NameError rescue NameError
raise Error, "#{version} is unsupported" raise Error, "#{version} is unsupported"
end end
@ -124,7 +124,6 @@ module HTTPX
else else
protocols = Context.build_alpn_string(protocols) protocols = Context.build_alpn_string(protocols)
@alpn_set = SSL.SSL_CTX_set_alpn_protos(@ssl_ctx, protocols, protocols.length) == 0 @alpn_set = SSL.SSL_CTX_set_alpn_protos(@ssl_ctx, protocols, protocols.length) == 0
puts "alpn protocols: #{protocols}"
end end
end end
else else

View File

@ -148,16 +148,21 @@ module HTTPX
attach_function :SSL_set_ex_data, %i[ssl int string], :int attach_function :SSL_set_ex_data, %i[ssl int string], :int
callback :verify_callback, %i[int x509], :int callback :verify_callback, %i[int x509], :int
attach_function :SSL_set_verify, %i[ssl int verify_callback], :void attach_function :SSL_set_verify, %i[ssl int verify_callback], :void
attach_function :SSL_CTX_set_verify, %i[ssl int verify_callback], :void
attach_function :SSL_get_verify_result, %i[ssl], :long attach_function :SSL_get_verify_result, %i[ssl], :long
attach_function :SSL_connect, [:ssl], :int attach_function :SSL_connect, [:ssl], :int
# Verify callback # Verify callback
X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT = 2
X509_V_ERR_HOSTNAME_MISMATCH = 62
X509_V_ERR_CERT_REJECTED = 28
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 :X509_STORE_CTX_get_error_depth, %i[x509], :int attach_function :X509_STORE_CTX_get_error_depth, %i[x509], :int
attach_function :PEM_write_bio_X509, %i[bio x509], :bool attach_function :PEM_write_bio_X509, %i[bio x509], :bool
attach_function :X509_verify_cert_error_string, %i[long], :string attach_function :X509_verify_cert_error_string, %i[long], :string
attach_function :X509_STORE_CTX_set_error, %i[ssl_ctx long], :void
# 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
@ -230,7 +235,7 @@ module HTTPX
def self.SSL_set_tlsext_host_name(ssl, host_name) def self.SSL_set_tlsext_host_name(ssl, host_name)
name_ptr = FFI::MemoryPointer.from_string(host_name) name_ptr = FFI::MemoryPointer.from_string(host_name)
raise Error, "error setting SNI hostname" if SSL_ctrl(ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, name_ptr) == 0 raise Error, "error setting SNI hostname" if SSL_ctrl(ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, name_ptr).zero?
end end
# Server Name Indication (SNI) Support # Server Name Indication (SNI) Support
@ -286,7 +291,7 @@ module HTTPX
# Deconstructor # Deconstructor
attach_function :SSL_CTX_free, [:ssl_ctx], :void attach_function :SSL_CTX_free, [:ssl_ctx], :void
PrivateMaterials = <<~keystr PrivateMaterials = <<~KEYSTR
-----BEGIN RSA PRIVATE KEY----- -----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDCYYhcw6cGRbhBVShKmbWm7UVsEoBnUf0cCh8AX+MKhMxwVDWV MIICXAIBAAKBgQDCYYhcw6cGRbhBVShKmbWm7UVsEoBnUf0cCh8AX+MKhMxwVDWV
Igdskntn3cSJjRtmgVJHIK0lpb/FYHQB93Ohpd9/Z18pDmovfFF9nDbFF0t39hJ/ Igdskntn3cSJjRtmgVJHIK0lpb/FYHQB93Ohpd9/Z18pDmovfFF9nDbFF0t39hJ/
@ -325,7 +330,7 @@ module HTTPX
Df6hTAs7H3MWww62ddvR8l07AWfSzSP5L6mDsbvq7EmQsmPODwb6C+i2aF3EDL8j Df6hTAs7H3MWww62ddvR8l07AWfSzSP5L6mDsbvq7EmQsmPODwb6C+i2aF3EDL8j
uw73m4YIGI0Zw2XdBpiOGkx2H56Kya6mJJe/5XORZedh1wpI7zki01tHYbcy uw73m4YIGI0Zw2XdBpiOGkx2H56Kya6mJJe/5XORZedh1wpI7zki01tHYbcy
-----END CERTIFICATE----- -----END CERTIFICATE-----
keystr KEYSTR
BuiltinPasswdCB = FFI::Function.new(:int, %i[pointer int int pointer]) do |buffer, _len, _flag, _data| BuiltinPasswdCB = FFI::Function.new(:int, %i[pointer int int pointer]) do |buffer, _len, _flag, _data|
buffer.write_string("kittycat") buffer.write_string("kittycat")

View File

@ -112,7 +112,8 @@ class HTTPSTest < Minitest::Test
uri = build_uri("/get") uri = build_uri("/get")
response = HTTPX.with(ssl: { hostname: "great-gatsby.com" }).get(uri) response = HTTPX.with(ssl: { hostname: "great-gatsby.com" }).get(uri)
assert response.is_a?(HTTPX::ErrorResponse), "response should contain errors" assert response.is_a?(HTTPX::ErrorResponse), "response should contain errors"
assert response.error.message.include?("certificate verify failed") assert response.error.message.include?("certificate verify failed"),
"unexpected error: #{response.error.message}"
end end
private private