-
Notifications
You must be signed in to change notification settings - Fork 4
Description
Hi there 👋 This might be weird issue, I'm opening it because I use the SSL verification code from this library in faye-websocket and just discovered an issue with it.
The Faye website is https://faye.jcoglan.com/ and its TLS certificates are provided by Let's Encrypt's certbot. I found that the verification code rejected it even though my browsers think the site is fine, so I wanted to run this past someone else familiar with this code to see what you think.
If you run this code, you get a TLS failure:
require 'bundler/setup'
require 'faraday'
connection = Faraday.new('https://faye.jcoglan.com') do |conn|
conn.adapter(:em_http)
end
connection.get('/')To debug this, I made some adjustments to my copy of the TLS verification code and ended up with this demo that connects successfully:
require 'bundler/setup'
require 'eventmachine'
require 'openssl'
HOST = 'faye.jcoglan.com'
module Connection
def connection_completed
@cert_store = OpenSSL::X509::Store.new
@last_cert = nil
@last_verify = false
@cert_store.set_default_paths
start_tls(sni_hostname: HOST, verify_peer: true)
end
def ssl_verify_peer(cert_text)
certificate = OpenSSL::X509::Certificate.new(cert_text)
@last_cert = certificate
@last_verify = @cert_store.verify(certificate)
p certificate
if @last_verify
store_cert(certificate)
puts "\e[37;42m VERIFIED \e[0m"
else
puts "\e[37;41m UNVERIFIED \e[0m"
end
true
end
def ssl_handshake_completed
unless @last_verify and OpenSSL::SSL.verify_certificate_identity(@last_cert, HOST)
raise OpenSSL::SSL::SSLError,
%(host "#{HOST}" does not match the server certificate)
end
end
def store_cert(cert)
@cert_store.add_cert(cert)
rescue OpenSSL::X509::StoreError => error
raise error unless error.message == 'cert already in hash table'
end
end
EM.run { EM.connect(HOST, 443, Connection) }The main way this differs from the implementation in Faraday is that it's ok for @cert_store.verify(certificate) to fail. If it fails, the certificate is not added to the X509::Store, but we don't raise an exception immediately. Instead we just require that the last certificate in the chain is verified, and that it passed hostname checking.
When I run this script, I see:
#<OpenSSL::X509::Certificate: subject=#<OpenSSL::X509::Name CN=ISRG Root X1,O=Internet Security Research Group,C=US>, issuer=#<OpenSSL::X509::Name CN=DST Root CA X3,O=Digital Signature Trust Co.>, serial=#<OpenSSL::BN:0x000000010f1df190>, not_before=2021-01-20 19:14:03 UTC, not_after=2024-09-30 18:14:03 UTC>
[ UNVERIFIED ]
#<OpenSSL::X509::Certificate: subject=#<OpenSSL::X509::Name CN=R3,O=Let's Encrypt,C=US>, issuer=#<OpenSSL::X509::Name CN=ISRG Root X1,O=Internet Security Research Group,C=US>, serial=#<OpenSSL::BN:0x000000010f1decb8>, not_before=2020-09-04 00:00:00 UTC, not_after=2025-09-15 16:00:00 UTC>
[ VERIFIED ]
#<OpenSSL::X509::Certificate: subject=#<OpenSSL::X509::Name CN=faye.jcoglan.com>, issuer=#<OpenSSL::X509::Name CN=R3,O=Let's Encrypt,C=US>, serial=#<OpenSSL::BN:0x000000010f1de808>, not_before=2021-10-21 15:10:22 UTC, not_after=2022-01-19 15:10:21 UTC>
[ VERIFIED ]
We have 3 certs here:
- A:
CN=ISRG Root X1,O=Internet Security Research Group,C=US - B:
CN=R3,O=Let's Encrypt,C=US - C:
CN=faye.jcoglan.com
The first cert, A, is not verified given the default paths for X509::Store. However, cert B is verified, and then cert C is also verified if cert B has been added to the store.
If A is added to the store, this causes cert C to fail verification. Likewise if cert B is not added, cert C fails to verify.
What I'm wondering is: is the implementation above valid? Is it ok for cert A to fail, if cert B passes verification and can then go on to verify C? Does this mean there is some chain of certs from my machine's trusted certs to the site's own cert, and that means it can be trusted? Or should the failure of cert A cause the connection to fail?