11

I'm trying to write an HTTPS client in Ruby. It will connect to the server using HTTPS, passing an authentication token (obtained through a separate login process) and an SSL client certificate.

I'm doing the following with rest-client:

client = RestClient::Resource.new(url,
                         :ssl_client_cert  =>  OpenSSL::X509::Certificate.new(File.read('./certificate/client-2048.pem')),
                         :ssl_client_key   =>  OpenSSL::PKey::RSA.new(File.read('./certificate/client-2048.key'), ''),
                         :verify_ssl       =>  OpenSSL::SSL::VERIFY_NONE)

# ...

headers = {
  'X-Application' => APP_KEY,
  'X-Authentication' => @session_token,
  'content-type' => 'application/json',
  'Accept' => 'application/json'
}

response = client.post(request, headers)

This works, but what I'd like to do is use keep-alive to avoid having to go through the whole process of bringing up the connection each time I want to make a request. The delay involved makes the monitoring application I'm writing much less useful.

However, what I can't seem to find is a Ruby library that offers the following:

  • HTTPS support
  • SSL Client Certificate support
  • Keep-Alive

There's a pull request languishing for rest-client that should provide it. httparty has persistent_httparty but if it supports SSL Client Certificates, there's no documentation for it.

I could fork rest-client, merge the pull-request which has by now bit-rotted, and use that. But surely I'm missing something here ... is there an existing library that offers what I'm looking for? Or some documentation for httparty which explains SSL Client Certificate use with that library?

Any assistance would be greatly appreciated.

Duncan Bayne
  • 3,870
  • 4
  • 39
  • 64
  • `OpenSSL::SSL::VERIFY_NONE` is usually a bad idea. If the workflow included the minimum customary checks, then you might find it does not work. – jww Nov 19 '16 at 11:13

3 Answers3

10

Is there something that already works?

I believe the HTTP client library Faraday is focus of more Ruby community action is these days.

It comes with a :net_http_persistent adapter, which supports SSL client certificates. You can probably do something like this:

ssl_options = {
  cert: OpenSSL::X509::Certificate.new(File.read('./certificate/client-2048.pem')),
  key:  OpenSSL::PKey::RSA.new(File.read('./certificate/client-2048.key'), 'mypassword')
}
conn = Faraday.new(url: 'https://example.com', ssl: ssl_options) do |faraday|
  faraday.adapter = Faraday::Adapter::NetHttpPersistent
end

conn.get '/my-resource'

HTTParty

According to the specs:

... the resulting connection when providing PEM certificates when scheme is https

  • uses the provided PEM certificate
  • will verify the certificate

You can use the pem classmethod to provide a client certificate in PEM format.

REST Client

It's not as dead as all that -- Larry Gilbert (@L2G) is still merging in pull requests and keeping the lights on. He's nodded his general approval in the issue tracker. I suspect it's just not at the top of his priority queue at the moment.

The guy who sent in that pull request, @byroot, has been keeping his code up to date so you shouldn't need to do much at all while you wait.

Rajkaran Mishra
  • 4,532
  • 2
  • 36
  • 61
RJHunter
  • 2,829
  • 3
  • 25
  • 30
  • I've taken the REST Client approach - an even quicker and easier one than merging the pull request. I forked the repo containing it, and just installed it with the following in my Gemfile: `gem 'rest-client', :github => 'duncan-bayne/rest-client', :branch => 'feature-keep-alive'`. I'll investigate a port to Faraday though, as that does seem to be the way of the future. – Duncan Bayne Sep 23 '13 at 07:05
  • And by "I'll investigate" I mean "two years later in 2015". But hey, better late than never ... :) – Duncan Bayne Mar 16 '15 at 09:58
9

The Ruby standard library's Net::HTTP API satisfies the requirements you listed: HTTPS support, SSL Client Certificate support and Keep-Alive.

Since Net::HTTP can be instantiated and reused without block semantics, it's also easy to wrap in your own library.

#!/usr/bin/env ruby
#
# https://gist.github.com/sheldonh/4693e2eca35b62b22c55

require 'openssl'
require 'net/http'
require 'json'

class Gist

  DEFAULT_OPTIONS = {
    use_ssl: true,
    verify_mode: OpenSSL::SSL::VERIFY_PEER,
    keep_alive_timeout: 30,
    cert: OpenSSL::X509::Certificate.new(File.read('./client.cert.pem')),
    key: OpenSSL::PKey::RSA.new(File.read('./client.key.pem'))
  }


  def initialize(http = nil)
    if http
      @http = http
    else
      @http = Net::HTTP.start("api.github.com", 443, DEFAULT_OPTIONS)
    end
  end

  def fetch(id, file)
    response = @http.request Net::HTTP::Get.new "/gists/#{id}"
    JSON.parse(response.body)["files"][file]["content"]
  end

end


gist = Gist.new
puts gist.fetch "fc5b5c42ff2e22171f09", "gistfile1.txt"  # Lorem ipsum
puts gist.fetch "4693e2eca35b62b22c55", "gist.rb" # This script
sheldonh
  • 2,684
  • 24
  • 31
1

Have you tried mechanize?

According to the examples you can pass a client certification like this:

require 'rubygems'
require 'mechanize'

# create Mechanize instance
agent = Mechanize.new

# set the path of the certificate file
agent.cert = 'example.cer'

# set the path of the private key file
agent.key = 'example.key'

# get the login form & fill it out with the username/password
login_form = agent.get("http://example.com/login_page").form('Login')
login_form.Userid = 'TestUser'
login_form.Password = 'TestPassword'

# submit login form
agent.submit(login_form, login_form.buttons.first)

According to this topic, you may need to force mechanize to use SSLV3:

page = Mechanize.new{|a| a.ssl_version, a.verify_mode = 'SSLv3', OpenSSL::SSL::VERIFY_NONE}.get "https://yourHTTPSurl"
Community
  • 1
  • 1
Paulo Fidalgo
  • 21,709
  • 7
  • 99
  • 115
  • 1
    `OpenSSL::SSL::VERIFY_NONE` is usually a bad idea. Its not clear to me why sensitive information, like a password, is coughed up to any server that answers (instead of the expected one). – jww Nov 19 '16 at 11:15