=begin
Heh. Actually, without the ((|verify_callback|)) I get consistent results.
net/https does a (({post_connection_check})) internally when (({use_ssl=true})).
(({VERIFY_PEER})) is also automatically set when (({use_ssl=true})) using
1.9.2-p180. Didn't check when that was introduced but on Ruby 1.8.7-p174
you have to explicitly set (({VERIFY_PEER})).
require 'net/http'
require 'net/https'
require 'uri'
url = URI.parse("https://github.com/") # OK
#url = URI.parse("https://etutorials.org/") # Hostname mismatch
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
request = Net::HTTP::Get.new(url.path)
response = http.request(request)
Result on both Mac OS 10.6.6 and Ubuntu 10.04.1 LTS:¶
=> OpenSSL::SSL::SSLError: hostname was not match with the server
certificate
((<Gist 871758|URL:https://gist.github.com/871758>))
Delving deeper, here's what I found. While I haven't found the root
cause, I think this might be interesting.
From the OpenSSL docs:
The verify_callback function is used to control the behaviour when the
SSL_VERIFY_PEER flag is set. It must be supplied by the
application and receives two arguments: preverify_ok indicates,
whether the verification of the certificate in question was passed
(preverify_ok=1) or not (preverify_ok=0).
[...]
The certificate chain is checked starting with the deepest nesting
level (the root CA certificate) and worked upward to the peer's
certificate. At each level signatures and issuer attributes are checked.
Whenever a verification error is found, the error number is stored in
x509_ctx and verify_callback is called with preverify_ok=0
[...]
If no error is found for a certificate, verify_callback is called with
preverify_ok=1 before advancing to the next level.
[...]
The return value of verify_callback controls the strategy of the
further verification process. If verify_callback returns 0, the
verification process is immediately stopped with ``verification failed''
state. If SSL_VERIFY_PEER is set, a verification failure alert is sent
to the peer and the TLS/SSL handshake is terminated. If verify_callback
returns 1, the verification process is continued. If verify_callback
always returns 1, the TLS/SSL handshake will not be terminated with
respect to verification failures and the connection will be established.
The calling process can however retrieve the error code of the last
verification error using SSL_get_verify_result(3) or by maintaining its
own error storage managed by verify_callback.
Ok. So it's pretty darn important what ((|verify_callback|)) actually
returns. Which in Hongli's test case is ((nil))! Let's revise
((|verify_callback|)) so that it returns ((|preverify_ok|)):
((<Gist 871668|URL:https://gist.github.com/871668>))
Now I get the same end results as before. Note that I say end results
because the validation process differs.
Per documentation ((|verify_callback|)) is called for each certificate
in the chain. This seems to happen successfully on Ubuntu but
not on OS X.
Running the gist against ((github.com)):
On Mac OS X 10.6.6 we get:
preverify_ok: false
error: 19
=> #<Net::HTTPOK 200 OK readbody=true>
On Ubuntu 10.04.1 LTS:
preverify_ok: true
error: 0
preverify_ok: true
error: 0
preverify_ok: true
error: 0
preverify_ok: true
error: 0
=> #<Net::HTTPOK 200 OK readbody=true>
The error on OS X means "(({X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN})):
((self signed certificate in certificate chain))" which is obviously
not the case since the certificate is valid.
Github's certificate chain is 4 deep and on Ubuntu no errors are found
while traversing the chain. On OS X the first certificate in the chain
couldn't be validated and stops but still makes a connection.
If we now run it against ((etutorials.org)) which has a hostname mismatch:
On Mac OS X 10.6.6 we get:
preverify_ok: false
error: 20
OpenSSL::SSL::SSLError: hostname was not match with the server certificate
On Ubuntu 10.04.1 LTS:
preverify_ok: true
error: 0
preverify_ok: true
error: 0
OpenSSL::SSL::SSLError: hostname was not match with the server certificate
Again we see that the certificate chain is successfully traversed on
Ubuntu. AFAIK it doesn't throw an error code inside the
((|verify_callback|)) because the certificate itself is valid in the
chain.
On OS X the first certificate in the chain couldn't be
validated and stops but this time an exception is thrown! Hooray.
(The error on OS X means
"(({X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY})): ((unable to get
local issuer certificate))" i.e. that it can't find a trusted root CA.)
The important bit here is that the connection failed and that we can
trust the outcome even though the validation process doesn't seem to
work the same.
((%$openssl version -d%)) will give you the path where OpenSSL looks for
certs. On 10.6.6 thats /System/Library/OpenSSL/ which is empty. Wild
guess: OpenSSL on OS X looks for certs in /System/Library/OpenSSL/,
doesn't find any and fails over to the keychain. Or something to that
effect.
You can try https://www.debian-administration.org/ for testing a
self-signed certificate.
System:
Mac OS 10.6.6 / Build 10J567
OpenSSL 0.9.8l 5 Nov 2009
Ubuntu 10.04.1 LTS
OpenSSL 0.9.8k 25 Mar 2009
Also a good read ((<Braintree: SSL socket verify_mode doesn't verify|URL:http://www.braintreepaymentsolutions.com/devblog/sslsocket-verify_mode-doesnt-verify>))
=end