Bug #3150

net/https peer verification doesn't do anything

Added by Hongli Lai about 4 years ago. Updated almost 3 years ago.

[ruby-core:29528]
Status:Third Party's Issue
Priority:Normal
Assignee:Hiroshi Nakamura
Category:ext
Target version:1.9.3
ruby -v:ruby 1.8.7 (2009-06-08 patchlevel 173) [universal-darwin10.0] Backport:

Description

=begin
Setting verifymode to VERIFYPEER should make net/https raise an exception if peer certificate verification fails. For example:

require 'net/http'
require 'net/https'
require 'openssl'

url = URI.parse("https://bugzilla.redhat.com/")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_PEER

http.verify_callback = proc do |preverify_ok, ssl_context|
  puts "verification succeeded: #{preverify_ok}"
end

request = Net::HTTP::Get.new(url.path)
response = http.request(request)   # Should raise error

Expected output:

$ ruby ssltest.rb 
verification succeeded: false
.../lib/ruby/1.8/net/http.rb:586:in `connect': certificate verify failed (OpenSSL::SSL::SSLError)

Actual output:

$ ruby ssltest.rb
verification succeeded: false
(no exception raised)

Either net/https is broken, or OpenSSL is broken, or OpenSSL changed some default behavior.
I can reproduce the problem OS X Snow Leopard with OpenSSL 0.9.8k and the following Ruby versions:
- ruby 1.8.6 (2010-02-05 patchlevel 399) [i686-darwin10.3.0]
- ruby 1.8.7 (2009-06-08 patchlevel 173) [universal-darwin10.0]
- ruby 1.9.1p376 (2009-12-07 revision 26041) [i386-darwin10.2.0]
- ruby 1.9.2dev (2010-04-09 trunk 27271) [x86_64-darwin10.3.0]

The problem does not occur on Debian Linux 5 with OpenSSL 0.9.8g and the following Ruby versions:
- ruby 1.8.6 (2008-08-11 patchlevel 287) [i686-linux]

I don't know whether 1.8.6-p287 exhibits the problem on Snow Leopard, it fails to compile with the following errors:

gcc -I. -I../.. -I../../. -I../.././ext/openssl -DRUBYEXTCONFH=\"extconf.h\" -fno-common -g -O2 -pipe -fno-common -c opensslmissing.c
In file included from openssl
missing.c:22:
opensslmissing.h:123: error: conflicting types for 'BNrandrange'
/usr/include/openssl/bn.h:411: error: previous declaration of 'BN
randrange' was here
openssl
missing.h:124: error: conflicting types for 'BNpseudorandrange'
/usr/include/openssl/bn.h:412: error: previous declaration of 'BN
pseudorandrange' was here
=end

History

#1 Updated by Hiroshi Nakamura over 3 years ago

  • Assignee set to Hiroshi Nakamura

=begin
Anyone can reproduce this? On Snow Leopard only? It must be critical issue if it still exists.
=end

#2 Updated by Aaron Patterson over 3 years ago

=begin
On Fri, Aug 20, 2010 at 07:12:47AM +0900, Hiroshi NAKAMURA wrote:

Issue #3150 has been updated by Hiroshi NAKAMURA.

Assigned to set to Hiroshi NAKAMURA

Anyone can reproduce this? On Snow Leopard only? It must be critical issue if it still exists.

I am able to reproduce this on Snow Leopard. I will try to debug and provide more
info soon!

--
Aaron Patterson
http://tenderlovemaking.com/

Attachment: (unnamed)
=end

#3 Updated by Hiroshi Nakamura over 3 years ago

=begin
Thanks Aaron. Hope we can identify the cause soon.
=end

#4 Updated by Aaron Patterson over 3 years ago

=begin
On Mon, Sep 06, 2010 at 11:26:01AM +0900, Hiroshi NAKAMURA wrote:

Issue #3150 has been updated by Hiroshi NAKAMURA.

Thanks Aaron. Hope we can identify the cause soon.

Hi!

I think I have good news. I am starting to suspect this is not a bug
of Ruby.

First (for posterity), here is a chunk of Ruby code to reproduce the
problem without using net/http:

 require 'socket'
 require 'openssl'

 s   = TCPSocket.new 'bugzilla.redhat.com', 443
 ctx = OpenSSL::SSL::SSLContext.new
 ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
 s   = OpenSSL::SSL::SSLSocket.new s, ctx
 s.connect

This code raises an exception on my linux machine, but not on my Snow
Leopard machine.

I wrote a (mostly) equivalent program in C here:

http://gist.github.com/570593

On my Snow Leopard machine, SSL_connect() returns a value greater than
0. On my linux machine, this program results in an error. This program
leads me to believe this is not a bug of Ruby, but I don't know why the
behavior is different on linux vs OS X. I am still researching.

--
Aaron Patterson
http://tenderlovemaking.com/

Attachment: (unnamed)
=end

#5 Updated by Shyouhei Urabe over 3 years ago

  • Status changed from Open to Assigned

=begin

=end

#6 Updated by Justin Case over 3 years ago

=begin
Workaround with rvm:

$ openssl version
OpenSSL 0.9.8l 5 Nov 2009

$ rvm package install openssl
$ rvm remove 1.9.2
$ rvm install 1.9.2 -C --with-openssl-dir=$HOME/.rvm/usr

$ $HOME/.rvm/usr/bin/openssl version
OpenSSL 0.9.8n 24 Mar 2010

Output:

$ ruby ssltest.rb
verification succeeded: false
[...] http.rb:677:in `connect': SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed (OpenSSL::SSL::SSLError)
=end

#7 Updated by Hiroshi Nakamura over 3 years ago

=begin
I found that the OS X 10.6.5 includes security fix for OpenSSL. http://support.apple.com/kb/HT4435
And it looks similar to this issue for me. Can someone check if the behavior of above scripts changed on 10.6.5?

The ticket I file at developer.apple.com is not yet updated so it could be a different fix.
=end

#8 Updated by Aaron Patterson over 3 years ago

=begin
On Thu, Nov 25, 2010 at 08:48:59PM +0900, Hiroshi NAKAMURA wrote:

Issue #3150 has been updated by Hiroshi NAKAMURA.

I found that the OS X 10.6.5 includes security fix for OpenSSL. http://support.apple.com/kb/HT4435
And it looks similar to this issue for me. Can someone check if the behavior of above scripts changed on 10.6.5?

The ticket I file at developer.apple.com is not yet updated so it could be a different fix.

I've updated to 10.6.5, and nothing has changed with this. :-(

--
Aaron Patterson
http://tenderlovemaking.com/

Attachment: (unnamed)
=end

#9 Updated by Hiroshi Nakamura over 3 years ago

=begin
Thank you for sharing your time. It's bad to know that the problem still exists...
=end

#10 Updated by Nathaniel Bibler over 3 years ago

=begin
I tested this again in OS X 10.6.6 yesterday, and the problem still exists. I used Aaron's code from 09/09/2010 03:42 AM and no exceptions were raised.

ruby 1.9.2p0 (2010-08-18 revision 29036) [x86_64-darwin10.4.0]
=> #OpenSSL::SSL::SSLSocket:0x000001008470a0

ruby 1.8.7 (2009-12-24 patchlevel 248) [i686-darwin10.2.0]
=> #OpenSSL::SSL::SSLSocket:0x1005b3be0
=end

#11 Updated by Martin Bosslet over 3 years ago

=begin
Using Ubuntu 10.04 and OpenSSL 1.0.0c, I get the expected exception.
=end

#12 Updated by Hiroshi Nakamura about 3 years ago

=begin
http://www.afp548.com/article.php?story=20091007164413755 (Thanks for finding this, Eric: https://twitter.com/#!/drbrain/status/43788281007194112)

Apple looks to ship customized OpenSSL which integrates trusted root certificates from OS's system keychain. So we should test against not https://bugs.redhat.com/ but another SSL site which is using the certificate OS does not trust.

Hongli, can you confirm that the result of your program affected by OS's keychain setting?
=end

#13 Updated by Justin Case about 3 years ago

=begin
Heh. Actually, without the ((|verifycallback|)) I get consistent results.
net/https does a (({post
connectioncheck})) internally when (({usessl=true})).
(({VERIFYPEER})) is also automatically set when (({usessl=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.usessl = 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

(())

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 verifycallback function is used to control the behaviour when the
SSL
VERIFYPEER 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
(preverifyok=1) or not (preverifyok=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
x509ctx and verifycallback is called with preverify_ok=0

[...]

If no error is found for a certificate, verifycallback is called with
preverify
ok=1 before advancing to the next level.

[...]

The return value of verifycallback controls the strategy of the
further verification process. If verify
callback returns 0, the
verification process is immediately stopped with ``verification failed''
state. If SSLVERIFYPEER is set, a verification failure alert is sent
to the peer and the TLS/SSL handshake is terminated. If verifycallback
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 SSLgetverifyresult(3) or by maintaining its
own error storage managed by verify
callback.

Ok. So it's pretty darn important what ((|verifycallback|)) actually
returns. Which in Hongli's test case is ((nil))! Let's revise
((|verify
callback|)) so that it returns ((|preverify_ok|)):

(())

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
=> #

On Ubuntu 10.04.1 LTS:

preverifyok: true
error: 0
preverify
ok: true
error: 0
preverifyok: true
error: 0
preverify
ok: true
error: 0
=> #

The error on OS X means "(({X509VERRSELFSIGNEDCERTIN_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:

preverifyok: 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
"(({X509VERRUNABLETOGETISSUERCERTLOCALLY})): ((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 (())
=end

#14 Updated by Akira Tanaka almost 3 years ago

  • Project changed from Ruby to ruby-trunk

#15 Updated by Hiroshi Nakamura almost 3 years ago

  • Target version set to 1.9.3

#16 Updated by Hiroshi Nakamura almost 3 years ago

  • Category set to ext
  • Status changed from Assigned to Third Party's Issue
  • Priority changed from High to Normal

Finally I found that Apple ships patched version of OpenSSL.

http://www.opensource.apple.com/source/OpenSSL098/OpenSSL098-27/src/crypto/x509/x509_vfy_apple.h
/*
* X509verifycert
*
* Originally located in x509vfy.c.
*
* Verify certificate with OpenSSL created X509
verifycert. If and only if
* OpenSSL cannot get certificate issuer locally then OS X security API will
* verify the certificate, using TrustEvaluationAgent.
*
* Return values:
* --------------
* -1: Null was passed for either ctx or ctx->cert.
* 0: Certificate is trusted.
* 1: Certificate is not trusted.
*/
int X509
verifycert(X509STORE_CTX *ctx);

So, with the OpenSSL on Show Leopard, a certificate is trusted if the certificate is trusted by system even if you don't set proper SSLCERTDIR.

You can see the original report have a verify callback and it reports 'false'. Here's what x509applyvfy.c is doing:

http://www.opensource.apple.com/source/OpenSSL098/OpenSSL098-27/src/crypto/x509/x509_vfy_apple.c
/* Try OpenSSL, if we get a local certificate issue verify against trusted roots */
ret = X509verifycertorig(ctx);
if (ret != 1 && (ctx->error & X509
VERRUNABLETOGETISSUERCERT_LOCALLY)) {
...

So even though you return false from verifycallback, it could be trusted by system if the reason is X509VERRUNABLETOGETISSUERCERT_LOCALLY.

I close this as 'Third Party's Issue.' Yes, it's an issue of Snow Leopard.

Please let me know if I am wrong. I'll reopen this.

Also available in: Atom PDF