Verify Client Certificate Using Sslserver in Ruby

Verify client certificate using SSLServer in Ruby

Have a look at test_client_auth and start_server in the tests for OpenSSL::SSL.

From the top of my head, the only thing I see missing in your code is that you forgot to explicitly require client authentication on the server side - it is important to set the flag combination

flags = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
ctx.verify_mode = flags

so that the server will actually require client authentication and not silently accept requests that come unauthenticated. If you don't set these, the server will be happy without requesting client authentication and as a result there will also be no peer certificate available.

How can I implement custom verification of an SSL certificate in Ruby's SSLServer?


In this case, I am the CA, so trusting the CA isn't an issue. I'm
signing these certificates on behalf of my users. The canonical names
aren't even domain names. Users connect peer-to-peer. I want the
client software I distribute to verify that the connecting user has a
certificate I signed and is the right user.

Its sounds like you are running a Private PKI. Just load the root of the trust chain into OpenSSL with SSL_CTX_load_verify_locations or SSL_load_verify_locations.

Be sure to use SSL_PEER_VERIFY to ensure OpenSSL performs the verification. The call would probably look like SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);. If peer validation fails, then the connect will fail.

There are ways to ensure the connect succeeds and then catching the error later. The trick is to set the verify callback, have the verify callback always return 1, and then call SSL_get_verify_result after the connection is setup. See SSL/TLS Client for an example.

Note: in all cases, you still have to perform name checking manually. OpenSSL currently does not do it (its in HEAD for OpenSSL 1.1.0). See libcurl or PostgreSQL for some code you can rip.

An example of a SSL/TLS client is provided by OpenSSL at its wiki. See SSL/TLS Client. There's no server code or example at the moment.

Use Client Cert and TLS_RSA_WITH_AES_256_CBC_SHA256 cipher suite

According to openssl, this is also called "AES256-SHA256". According to Ruby lang, AES256-SHA256 is considered insecure and therefor disabled. The link contains a "patch" to re-enable the insecure ciphers.

You may want to seek advise from a security pro about the risks involved for your organisation.

(Edit) The error "self signed certificate in certificate chain" needs to be taken care of.

Why is Ruby unable to verify an SSL certificate?

If you are only interested in how to make Ruby behave the same way as OpenSSL s_client or your browser does, you may skip to the very last section, I'll cover the fine print in what is following.

By default, the OpenSSL::X509::Store used for making the connection doesn't use any trusted certificates at all. Based on your knowledge of the application domain, you would typically feed an instance of X509::Store with the trusted certificate(s) that are relevant for your application. There are several options for this:

  • Store#add_file takes a path to a PEM/DER-encoded certificate
  • Store#add_cert takes an instance of X509::Certificate
  • Store#add_path takes a path to a directory where trusted certificates can be found

The "Browser" Approach

This is in contrast to the approach browsers, Java (cacerts), or Windows with its own internal store of trusted certificates, take. There the software is pre-equipped with a set of trusted certificates that is considered to be "good" in the opinion of the software vendor. Generally this is not a bad idea, but if you actually look into these sets, then you will soon notice that there are just too many certificates. An individual can't really tell whether all of these certificates should be trusted blindly or not.

The Ruby Approach

The requirements of your typical Ruby application on the other hand are a lot different than that of a browser. A browser must be be able to let you navigate to any "legitimate" web site that comes with a TLS certificate and is served over https. But in a typical Ruby application you will only have to deal with a few services that use TLS or would otherwise require certificate validation.

And there is the benefit of the Ruby approach - although it requires more manual work, you will end up with a hand-tailored solution that exactly trusts the certificates it should trust in your given application context. This is tedious, but security is much higher this way because you expose a lot less attack surface. Take recent events: if you never had to include DigiNotar or any other compromised root in your trust set, then there's no way such breaches can affect you.

The downside of this, however, as you have already noticed, is that by default, if you don't actively add trusted certificates, the OpenSSL extension will not be able to validate any peer certificate at all. In order to make things work, you have to set up the configuration manually.

This inconvenience has led to a lot of dubious measures to circumvent it, the worst of all being to globally set OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE. Please don't do this. We have even made jokes about adding code that lets your application crash randomly if we encounter that hack :)

If manual trust setup seems too complicated, I'll offer an easy alternative now that makes the OpenSSL extension behave exactly the same as OpenSSL CLI commands like s_client.

Why s_client can verify the certificate

OpenSSL uses a similar approach to browsers and Windows. A typical installation will put a bundle of trusted certificates somewhere on your hard disk (something like /etc/ssl/certs/ca-bundle.crt) and this will serve as the default set of trusted certificates. That's where s_client looks when it needs to verify peer certificates and that's why your experiment succeeded.

Making Ruby act like s_client

If you'd still like to have the same comfort when validating certificates with Ruby, you can tell it to use the OpenSSL bundle of trusted certificates if available on your system by calling OpenSSL::X509::Store#set_default_paths. Additional information can be found here. To use this with XMLRPC::Client, simply ensure that set_default_paths gets called on the X509::Store it uses.

Trying to create a simple Ruby server over SSL

I figured it out, thanks to that link to some decent documentation.

For one thing, SSLSocket.connect() is only meant to be called on the client.

But the main problem is that I'm trying to take a GServer socket and upgrade it to SSL. Instead, I should use OpenSSL::SSL::SSLServer.

Also, I separated my certificate and private key into two files.

Here is the working server:

#!/usr/bin/ruby

require "socket"
require "openssl"
require "thread"

listeningPort = Integer(ARGV[0])

server = TCPServer.new(listeningPort)
sslContext = OpenSSL::SSL::SSLContext.new
sslContext.cert = OpenSSL::X509::Certificate.new(File.open("cert.pem"))
sslContext.key = OpenSSL::PKey::RSA.new(File.open("priv.pem"))
sslServer = OpenSSL::SSL::SSLServer.new(server, sslContext)

puts "Listening on port #{listeningPort}"

loop do
connection = sslServer.accept
Thread.new {
begin
while (lineIn = connection.gets)
lineIn = lineIn.chomp
$stdout.puts "=> " + lineIn
lineOut = "You said: " + lineIn
$stdout.puts "<= " + lineOut
connection.puts lineOut
end
rescue
$stderr.puts $!
end
}
end

And client:

#!/usr/bin/ruby

require "socket"
require "thread"
require "openssl"

host = ARGV[0]
port = Integer(ARGV[1])

socket = TCPSocket.new(host, port)
expectedCert = OpenSSL::X509::Certificate.new(File.open("cert.pem"))
ssl = OpenSSL::SSL::SSLSocket.new(socket)
ssl.sync_close = true
ssl.connect
if ssl.peer_cert.to_s != expectedCert.to_s
stderrr.puts "Unexpected certificate"
exit(1)
end

Thread.new {
begin
while lineIn = ssl.gets
lineIn = lineIn.chomp
$stdout.puts lineIn
end
rescue
$stderr.puts "Error in input loop: " + $!
end
}

while (lineOut = $stdin.gets)
lineOut = lineOut.chomp
ssl.puts lineOut
end


Related Topics



Leave a reply



Submit