Ruby’s XMLRPC::Client and SSL

For the past few days I’ve been working on a Ruby project that needed to interact with a remote XMLRPC API. This isn’t particularly unusual but it was the first time from within a Ruby application. Luckily enough Ruby has a built in XMLRPC client that handles a lot of the messy bits.

The XMLRPC::Client class itself seems fairly simple. There are only a handful of methods, five of which are for opening a new connection in a few different ways, and at least two ways to open each type of connection.

As a starting point this was a simplified chunk of code that I was using to connect to the remote API:

require 'xmlrpc/client'
 
class APIConnection
  def initialize(username, password, host)
    # Build the arguments for the XMLRPC::Client object
    conn_args = {
      :user => username,
      :password => password,
      :host => host,
      :use_ssl => true,
      :path => "/api"
    }
 
    @connection = XMLRPC::Client.new_from_hash(conn_args)
  end
 
  def version
    @connection.call("version")
  end
end

The problem I ran into was when connecting to a server using HTTPS. I knew that this certificate was good however I continued to get the message:

warning: peer certificate won't be verified in this SSL session

Ruby has taken the approach of by default not including any trusted certificate authorities which I greatly appreciate especially considering that in 2010 and 2011 12 certificate authorities were known to have been hacked including major ones such as VeriSign, and DigiNotar. Some of which were proven to have issued false certificates.

Since XMLRPC::Client doesn’t expose it’s SSL trust settings through it’s methods I went on a bit of a journey through Google to find an answer. What I found was overly disturbing, a lot of people don’t seem to understand what SSL is actually for. The solutions I found from the most egregious to least:

  • Disabling OpenSSL certificate checking globally with OpenSSL::SSL::VERIFY_NONE
  • Overriding the Net::HTTP certificate checking
  • Disabling OpenSSL certificate checking locally by extending XMLRPC::Client and over-riding how it was establishing connections
  • Using an SSL stripping proxy

I couldn’t find a solution out there that didn’t the security conscious voice in my head scream in despair. I asked on StackOverflow for a good solution. When I asked I didn’t have a good grasp on how Ruby was handling SSL certificates at all. The thorough answer from emboss didn’t quite answer my question but it gave me more than enough to really hunt down what I wanted.

First stop, I needed the certificates that I’ll be using to verify the connection. Every single certificate authority that issues certificates for public websites makes the public portion of their certificates available and this is what we need to verify the connection. To find out which ones you specifically need you can go to the API server’s address and look at it’s certificate information by clicking on the site’s lock icon. Every browser is a little different so you’ll have to find this out on your own. With Chrome (and perhaps others) you can download each of the certificates in the chain that you’ll need to verify the server’s certificate.

The server I was connecting to was using a RapidSSL certificate, who has been verified by GeoTrust. You want to grab their certificates base64 encoded in PEM format. Stick them all in a “ca.crt” file. For these two CAs you’re file will look a lot like this one:

-----BEGIN CERTIFICATE-----
MIID1TCCAr2gAwIBAgIDAjbRMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
YWwgQ0EwHhcNMTAwMjE5MjI0NTA1WhcNMjAwMjE4MjI0NTA1WjA8MQswCQYDVQQG
EwJVUzEXMBUGA1UEChMOR2VvVHJ1c3QsIEluYy4xFDASBgNVBAMTC1JhcGlkU1NM
IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx3H4Vsce2cy1rfa0
l6P7oeYLUF9QqjraD/w9KSRDxhApwfxVQHLuverfn7ZB9EhLyG7+T1cSi1v6kt1e
6K3z8Buxe037z/3R5fjj3Of1c3/fAUnPjFbBvTfjW761T4uL8NpPx+PdVUdp3/Jb
ewdPPeWsIcHIHXro5/YPoar1b96oZU8QiZwD84l6pV4BcjPtqelaHnnzh8jfyMX8
N8iamte4dsywPuf95lTq319SQXhZV63xEtZ/vNWfcNMFbPqjfWdY3SZiHTGSDHl5
HI7PynvBZq+odEj7joLCniyZXHstXZu8W1eefDp6E63yoxhbK1kPzVw662gzxigd
gtFQiwIDAQABo4HZMIHWMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUa2k9ahhC
St2PAmU5/TUkhniRFjAwHwYDVR0jBBgwFoAUwHqYaI2J+6sFZAwRfap9ZbjKzE4w
EgYDVR0TAQH/BAgwBgEB/wIBADA6BgNVHR8EMzAxMC+gLaArhilodHRwOi8vY3Js
Lmdlb3RydXN0LmNvbS9jcmxzL2d0Z2xvYmFsLmNybDA0BggrBgEFBQcBAQQoMCYw
JAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmdlb3RydXN0LmNvbTANBgkqhkiG9w0B
AQUFAAOCAQEAq7y8Cl0YlOPBscOoTFXWvrSY8e48HM3P8yQkXJYDJ1j8Nq6iL4/x
/torAsMzvcjdSCIrYA+lAxD9d/jQ7ZZnT/3qRyBwVNypDFV+4ZYlitm12ldKvo2O
SUNjpWxOJ4cl61tt/qJ/OCjgNqutOaWlYsS3XFgsql0BYKZiZ6PAx2Ij9OdsRu61
04BqIhPSLT90T+qvjF+0OJzbrs6vhB6m9jRRWXnT43XcvNfzc9+S7NIgWW+c+5X4
knYYCnwPLKbK3opie9jzzl9ovY8+wXS7FXI6FoOpC+ZNmZzYV+yoAVHHb1c0XqtK
LEL2TxyJeN4mTvVvk0wVaydWTQBUbHq3tw==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG
EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg
R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9
9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq
fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv
iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU
1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+
bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW
MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA
ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l
uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn
Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS
tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF
PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un
hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV
5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw==
-----END CERTIFICATE-----

Ugly right? That’s what ruby needs though. But how do we get XMLRPC::Client to actually use that information without hacking it all to pieces? Net::HTTP has a few methods that allow you to set the appropriate connection settings and XMLRPC::Client uses Net::HTTP. If XMLRPC::Client allowed to you specify this directly somehow I would’ve been a lot happier.

Here’s that code snippet again, this time forcing certificate verification with the ca.crt file. This code assumes that the ca.crt file lives in the same directory as the connection script:

require 'xmlrpc/client'
 
class APIConnection
  def initialize(username, password, host)
    # Build the arguments for the XMLRPC::Client object
    conn_args = {
      :user => username,
      :password => password,
      :host => host,
      :use_ssl => true,
      :path => "/api"
    }
 
    @connection = XMLRPC::Client.new_from_hash(conn_args)
 
    @connection.instance_variable_get("@http").verify_mode = OpenSSL::SSL::VERIFY_PEER
    @connection.instance_variable_get("@http").ca_file = File.join(File.dirname(__FILE__), "ca.crt")
  end
 
  def version
    @connection.call("version")
  end
end

Those last two lines in the initialize method first dive into the connection we’ve already setup (but before it’s been called), grab the of Net::HTTP and tells it to force peer verification and to use the certificate file we created before. No more warning, and we’re actually safe.

Both comments and pings are currently closed.

Comments are closed.