How can i detect certificate issue programmatically in Ruby - ruby

I have cases that a few customers get an error while posting to our server (HTTPS). This is the error:
SSL_connect returned=1 errno=0 state=error: certificate verify failed (unable to get local issuer certificate)
I can use this method to detect what fails in the certificate chain:
openssl s_client -connect <url>:443 -CAfile <path/cacert.pem>
But, not all customers are willing to download and install openssl. So, I want to add this test using ruby code (we already have ruby installed on the customer's computer).

This ruby code should do the work:
require 'openssl'
require 'socket'
CA_CERT = "<your-path-to-cert-file. e.g. cacert.pem>"
def verify_host(host)
puts "Connecting to: #{host} ..."
ctx = OpenSSL::SSL::SSLContext.new(:TLSv1_2)
ctx.ca_file = CA_CERT
ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
ctx.verify_callback = lambda do | preverify_ok, cert_store |
cert = cert_store.current_cert
puts "subject: #{cert.subject}, issuer: #{cert.issuer}, valid: #{preverify_ok}"
preverify_ok
end
socket = TCPSocket.new host, 443
ssl_socket = OpenSSL::SSL::SSLSocket.new socket, ctx
ssl_socket.hostname = host
puts "SSL Version: #{ssl_socket.ssl_version}"
ssl_socket.connect
ssl_socket.sysclose
socket.close
rescue => ex
puts ex
end
Sample usage:
verify_host('s3.amazonaws.com')
Output:
Connecting to: s3.amazonaws.com ...
SSL Version: TLSv1.3
subject: /C=IE/O=Baltimore/OU=CyberTrust/CN=Baltimore CyberTrust Root, issuer: /C=IE/O=Baltimore/OU=CyberTrust/CN=Baltimore CyberTrust Root, valid: true
subject: /C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Baltimore CA-2 G2, issuer: /C=IE/O=Baltimore/OU=CyberTrust/CN=Baltimore CyberTrust Root, valid: true
subject: /C=US/ST=Washington/L=Seattle/O=Amazon.com, Inc./CN=s3.amazonaws.com, issuer: /C=US/O=DigiCert Inc/OU=www.digicert.com/CN=DigiCert Baltimore CA-2 G2, valid: true

Related

Ruby https with ca certification does not work while it worked with curl

In ruby, I try to do a ssl connection to a nginx server I setup locally, with an auto-signed certificate. My code is :
require "net/http"
require "net/https"
require "openssl"
require "uri"
require "pp"
request = Net::HTTP::Get.new("/")
response = Net::HTTP.start(
"localhost",
443,
{
:use_ssl => true,
:key => OpenSSL::PKey::RSA.new(File.read("/home/gg/crt/client.key")),
:cert => OpenSSL::X509::Certificate.new(File.read("/home/gg/crt/client.crt")),
:ca_file => "/home/gg/crt/ca.pem",
:verify_mode => OpenSSL::SSL::VERIFY_PEER,
:verify_depth => 5,
}
) do |http|
http.request(request)
end
puts response.inspect
puts response.body
When I run it it return
/home/gg/.rvm/rubies/ruby-2.2.1/lib/ruby/2.2.0/net/http.rb:923:in `connect': SSL_connect returned=1 errno=0 state=error: certificate verify failed (OpenSSL::SSL::SSLError)
from /home/gg/.rvm/rubies/ruby-2.2.1/lib/ruby/2.2.0/net/http.rb:923:in `block in connect'
from /home/gg/.rvm/rubies/ruby-2.2.1/lib/ruby/2.2.0/timeout.rb:74:in `timeout'
from /home/gg/.rvm/rubies/ruby-2.2.1/lib/ruby/2.2.0/net/http.rb:923:in `connect'
from /home/gg/.rvm/rubies/ruby-2.2.1/lib/ruby/2.2.0/net/http.rb:863:in `do_start'
from /home/gg/.rvm/rubies/ruby-2.2.1/lib/ruby/2.2.0/net/http.rb:852:in `start'
from /home/gg/.rvm/rubies/ruby-2.2.1/lib/ruby/2.2.0/net/http.rb:583:in `start'
from testso.rb:8:in `<main>'
But I have the correct result if I run it with curl:
curl https://localhost --key crt/client.key --cert crt/client.crt --cacert crt/ca.pem
What am i doing wrong?
To possibly help others, here is a working solution with Ruby 2.0, for an HTTP GET.
require "net/http"
uri = URI.parse('https://your_url.com')
http = Net::HTTP.new(uri.host, uri.port)
OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:ssl_version]=nil
http.use_ssl = true
http.ca_path='/etc/pki/tls/certs/'
http.ca_file='/etc/pki/tls/certs/YOUR_CERT_CHAIN_FILE'
http.cert = OpenSSL::X509::Certificate.new(File.read("YOUR_CERT)_FILE"))
http.key = OpenSSL::PKey::RSA.new(File.read("YOUR_KEY_FILE"))
#SSLv3 is cracked, and often not allowed
http.ssl_version = :TLSv1_2
#### This is IMPORTANT
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
#Crete the GET request
request = Net::HTTP::Get.new(uri.request_uri)
#Add Headers, if needed
request.add_field 'X_REMOTE_USER', 'USER_NAME'
request.add_field 'Accept', '*'
#Get Response
response = http.request(request)
#Review Response
puts response.body
I feel dumb but figured out the problem.
When generating the different certificates I left some fields blank.
It seems that openssl detected this certificate as "looking" autosigned.
Thus if we use OpenSSL::SSL::VERIFY_PEER the connection fails
So to be sure that the certificate is well generated using the CA you can do :
openssl verify -CAfile ca.crt server.crt
(and same command with client.crt)
if we get
error 18 at 0 depth lookup:self signed certificate
OK
The certificate is detected as autosigned and it fails
if we get
Signature ok
It should work

Ruby SSL Client

I've got a SSL Echo Server working fine when I tested with
gnutls-cli --starttls --port 9002 --insecure localhost
My SSL Echo server is as below:
#!/usr/bin/ruby
require 'socket';
require 'openssl';
certfile = 'privkey.pem';
host = "127.0.0.1"
port = 9002;
server = TCPServer.new( host, port );
# Establish an SSL context
sslContext = OpenSSL::SSL::SSLContext.new
sslContext.cert = OpenSSL::X509::Certificate.new(File.open( "myssl.cert.cert" ))
sslContext.key = OpenSSL::PKey::RSA.new(File.open( "myssl.cert.key" ))
# Create SSL server
sslServer = OpenSSL::SSL::SSLServer.new( server, sslContext );
# Don't expect an immidate SSL handshake upon connection.
sslServer.start_immediately = false;
sslSocket = sslServer.accept;
sslSocket.puts( "Toast.." );
# Server loop
while line = sslSocket.gets
line.chomp!;
if "STARTTLS" == line
# Starting TLS
sslSocket.accept;
end
sslSocket.puts( "Got '#{line}'" );
end
sslSocket.close;
My Client is however, not working (which I borrow somewhere in StackOverflow) as below:
#!/usr/bin/env ruby
require "socket"
require "thread"
require "openssl"
host = "127.0.0.1"
port = 9002
socket = TCPSocket.new(host, port)
expectedCert = OpenSSL::X509::Certificate.new(File.open("myssl.cert.cert"))
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
Error I've got.
./sslclient.rb:13:in `connect': SSL_connect returned=1 errno=0 state=SSLv2/v3 read server hello A: unknown protocol (OpenSSL::SSL::SSLError)
from ./sslclient.rb:13:in `<main>'
Why is there a error in ssl.connect? Did I miss anything?
On the server side you start with plain text, send a welcome message and expect a starttls command - only then you upgrade to TLS. But on the client side you immediatly try to upgrade to TLS after the TCP connect, e.g. without reading the server hello and without sending the starttls command.

How to programmatically check if a certificate has been revoked?

I'm working on an xcode automated build system. When performing some pre-build validation I would like to check if the specified certificate file has been revoked. I understand that security verify-cert verifies other cert properties but not revokation. How can I check for revokation?
I'm writing the build system in Ruby but am really open to ideas in any language.
I read this answer (Openssl - How to check if a certificate is revoked or not) but the link towards the bottom (Does OpenSSL automatically handle CRLs (Certificate Revocation Lists) now?) gets into material that's a bit too involved for my purposes (a user uploading a revoked cert is a far out edge case). Is there a simpler / ruby oriented method for checking for revokation?
Thanks in advance!
Checking if a certificate is revoked can be a complex process. First you have to look for a CDP or OCSP AIA, then make a request, parse the response, and check that the response is signed against by a CA that is authorized to respond for the certificate in question. If it is a CRL you then need to see if the serial number of the certificate you're checking is present in the list. If it is OCSP then you need to see if you've received a "good" response (as opposed to unknown, revoked, or any of the various OCSP responder errors like unauthorized). Additionally you may want to verify that the certificate is within its validity period and chains to a trusted root. Finally, you should do revocation checks against every intermediate as well and check the certificate's fingerprint against the explicit blacklists that Mozilla/Apple/Google/Microsoft maintain.
I'm unaware of any Ruby libraries that automate the revocation checking process for you (eventually I hope to add it to r509), but given your more specific use case here's some untested code that should point you in the right direction.
require 'r509'
require 'net/http'
cert = R509::Cert.load_from_file("some_iphone_cert.pem")
crl_uri = cert.crl_distribution_points.crl.uris[0]
crl = Net::HTTP.get_response(URI(crl_uri)) # you may need to follow redirects here, but let's assume you got the CRL.
# Also note that the Apple WWDRCA CRL is like 28MB so you may want to cache this damned thing. OCSP would be nicer but it's a bit trickier to validate.
parsed_crl = R509::CRL::SignedList.new(crl)
if not parsed_crl.verify(cert.public_key)
raise StandardError, "Invalid CRL for certificate"
end
if parsed_crl.revoked?(cert.serial)
puts 'revoked'
end
Unfortunately, due to the enormous size (~680k entries) of the Apple WWDRCA CRL this check can be quite slow with r509's current hash map model.
If you're interested in going down the OCSP path I can write up how to generate OCSP requests/parse responses in Ruby as well.
Edit: It appears the iPhone developer certificates I have do not contain an embedded OCSP AIA so the only option for revocation checking will be via CRL distribution point as presented above.
Edit2: Oh why not, let's do an OCSP check in Ruby! For this we'll need the certificate and its issuing certificate. You can't use a WWDRCA certificate for this so just grab one from your favorite website. I'm using my own website.
require 'net/http'
require 'r509'
cert = R509::Cert.load_from_file("my_website.pem")
# get the first OCSP AIA URI. There can be more than one
# (degenerate example!)
ocsp_uri = cert.aia.ocsp.uris[0]
issuer = R509::Cert.load_from_file("my_issuer.pem")
cert_id = OpenSSL::OCSP::CertificateId.new(cert.cert,issuer.cert)
request = OpenSSL::OCSP::Request.new
request.add_certid(cert_id)
# we're going to make a GET request per RFC 5019. You can also POST the
# binary DER encoded version if you're more of an RFC 2560 partisan
request_uri = URI(ocsp_uri+"/"+URI.encode_www_form_component(req_pem.strip)
http_response = Net::HTTP.get_response(request_uri)
if http_response.code != "200"
raise StandardError, "Invalid response code from OCSP responder"
end
response = OpenSSL::OCSP::Response.new(http_response.body)
if response.status != 0
raise StandardError, "Not a successful status"
end
if response.basic[0][0].serial != cert.serial
raise StandardError, "Not the same serial"
end
if response.basic[0][1] != 0 # 0 is good, 1 is revoked, 2 is unknown.
raise StandardError, "Not a good status"
end
current_time = Time.now
if response.basic[0][4] > current_time or response.basic[0][5] < current_time
raise StandardError, "The response is not within its validity window"
end
# we also need to verify that the OCSP response is signed by
# a certificate that is allowed and chains up to a trusted root.
# To do this you'll need to build an OpenSSL::X509::Store object
# that contains the certificate you're checking + intermediates + root.
store = OpenSSL::X509::Store.new
store.add_cert(cert.cert)
store.add_cert(issuer.cert) #assuming issuer is a trusted root here, but in reality you'll need at least one more certificate
if response.basic.verify([],store) != true
raise StandardError, "Certificate verification error"
end
The example code above neglects to handle many possible edge cases, so it should be considered a starting point only. Good luck!
Paul's example has not worked with my local server, made by OpenSSL Cookbook, but have worked with post request
# openssl ocsp -port 9080 -index db/index -rsigner root-ocsp.crt -rkey private/root-ocsp.key -CA root-ca.crt -text
# openssl ocsp -issuer root-ca.crt -CAfile root-ca.crt -cert root-ocsp.crt -url http://127.0.0.1:9080
require 'net/http'
require 'openssl'
require 'base64'
require 'test/unit'
extend Test::Unit::Assertions
def load_cert(name)
OpenSSL::X509::Certificate.new(File.read(name))
end
ca_file = issuer = load_cert('root-ca.crt')
cert = load_cert('root-ocsp.crt')
cid = OpenSSL::OCSP::CertificateId.new(cert, issuer)
request = OpenSSL::OCSP::Request.new.add_certid(cid)
# with get, invalid, server responding with
# Invalid request
# Responder Error: malformedrequest (1)
#
# encoded_der = Base64.encode64(request.to_der)
# request_uri = URI.parse('http://127.0.0.1/' + URI.encode_www_form_component(encoded_der.strip))
# req = Net::HTTP::Get.new(request_uri.path, 'Content-Type' => 'application/ocsp-response')
# http_resp = Net::HTTP.new(request_uri.host, '9080').request(req)
# with post, work
ocsp_uri = URI('http://127.0.0.1:9080/')
http_resp = Net::HTTP.post(ocsp_uri, request.to_der, 'Content-Type' => 'application/ocsp-response')
resp = OpenSSL::OCSP::Response.new(http_resp.body)
assert_equal resp.status, OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL
assert resp.basic.is_a? OpenSSL::OCSP::BasicResponse
current_time = Time.now
resp.basic.status.each do |status_arr|
certificate_id, status, reason, revocation_time, this_update, next_update, extensions = status_arr
assert_equal status, 0 # 0 is good, 1 is revoked, 2 is unknown.
assert this_update < current_time
assert next_update.nil?
end
first_cert_id = resp.basic.status[0][0]
assert first_cert_id.cmp(cid)
assert first_cert_id.cmp_issuer(cid)
assert_equal first_cert_id.serial, cert.serial
resp.basic.responses.each do |resp|
assert resp.is_a? OpenSSL::OCSP::SingleResponse
assert resp.check_validity
end
store = OpenSSL::X509::Store.new
store.add_cert(cert)
store.add_cert(issuer) # assuming issuer is a trusted root here, but in reality you'll need at least one more certificate
assert resp.basic.verify([], store)
P.S.
For now it requesting status of ocsp certificate (like in book), wanted to request server/end-entity status, but at first I have to try it with openssl cli, and here I have stumbled
P.S.S
done this, thanks Steffen Ullrich
# openssl ocsp -port 9080 -index db/index -rsigner subca-ocsp.crt -rkey private/subca-ocsp.key -CA sub-ca.crt -text
# cat sub-ca.crt root-ca.crt > sub-and-root.crt
# openssl ocsp -issuer sub-ca.crt -CAfile sub-and-root.crt -cert server.crt -url http://127.0.0.1:9080
require 'net/http'
require 'openssl'
require 'base64'
require 'test/unit'
extend Test::Unit::Assertions
def load_cert(name)
OpenSSL::X509::Certificate.new(File.read(name))
end
subca = load_cert('sub-ca.crt')
root = load_cert('root-ca.crt')
cert = load_cert('server.crt')
cid = OpenSSL::OCSP::CertificateId.new(cert, subca)
request = OpenSSL::OCSP::Request.new.add_certid(cid)
# with post, work
ocsp_uri = URI('http://127.0.0.1:9080/')
http_resp = Net::HTTP.post(ocsp_uri, request.to_der, 'Content-Type' => 'application/ocsp-response')
resp = OpenSSL::OCSP::Response.new(http_resp.body)
assert_equal resp.status, OpenSSL::OCSP::RESPONSE_STATUS_SUCCESSFUL
assert resp.basic.is_a? OpenSSL::OCSP::BasicResponse
first_cert_id = resp.basic.status[0][0]
assert first_cert_id.cmp(cid)
assert first_cert_id.cmp_issuer(cid)
assert_equal first_cert_id.serial, cert.serial
resp.basic.responses.each do |resp|
assert resp.is_a? OpenSSL::OCSP::SingleResponse
assert resp.check_validity
end
store = OpenSSL::X509::Store.new
store.add_cert(cert)
store.add_cert(subca)
store.add_cert(root)
assert resp.basic.verify([], store)
For the record, largely inspired from Paul Kehrer answer (Thanks!) I wrote a small ruby gem to check the validity and revocation of a certificate (it is used in my product updown.io): https://github.com/jarthod/ssl-test
# Gemfile
gem 'ssl-test'
Here is an example:
valid, error, cert = SSLTest.test "https://revoked.badssl.com"
valid # => false
error # => "SSL certificate revoked: The certificate was revoked for an unknown reason (revocation date: 2019-10-07 20:30:39 UTC)"
cert # => #<OpenSSL::X509::Certificate...>
Since 1.4 it supports both OCSP and CRL.

SSL Error on HTTP POST (Unknown Protocol)

Trying to connect to Imgur API via SSL gives me an error. Here's the code and the error:
API_URI = URI.parse('https://api.imgur.com')
API_PUBLIC_KEY = 'Client-ID --'
ENDPOINTS = {
:image => '/3/image',
:gallery => '/3/gallery'
}
# Public: Upload an image
#
# args - The image path for the image to upload
#
def upload(image_path)
http = Net::HTTP.new(API_URI.host)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
params = {'image' => File.open(image_path)}
request = Net::HTTP::Post.new(API_URI.request_uri)
request.set_form_data(params)
request.add_field('Authorization', API_PUBLIC_KEY)
response = http.request(request)
puts response.body
end
And the error:
`connect': SSL_connect returned=1 errno=0 state=SSLv2/v3 read server hello A: unknown protocol (OpenSSL::SSL::SSLError)
I know VERIFY_NODE is not good practice but I just want to test the connection for now.
Ruby version: 1.9.2
Specifying the port when creating the HTTP client fixed this problem.
http = Net::HTTP.new(API_URI.host, API_URI.port)
or
http = Net::HTTP.new(API_URI.host, 443)
For me it was because I had started the server as a http (tcp://) servers instead of https (ssl://).
i.e.
bundle exec puma config.ru -b 'tcp://0.0.0.0:3456?key=/path/to/key.key&cert=/path/to/cert.crt'
instead of:
bundle exec puma config.ru -b 'ssl://0.0.0.0:3456?key=/path/to/key.key&cert=/path/to/cert.crt'

Ruby Generate Self-Signed Certificate

I'm trying to generate a self-signed certificate in ruby, but am running into trouble. This is what I currently have right now:
require 'openssl'
if ARGV.length != 3 then
puts "USAGE: #{__FILE__} <type[der|pem]> <private-out> <public-out>"
exit
end
type = ARGV[0].downcase
privateKeyFile = ARGV[1]
publicKeyFile = ARGV[2]
values = [{ 'C' => 'US'},
{'ST' => 'SomeState'},
{ 'L' => 'SomeCity'},
{ 'O' => 'Organization'},
{'OU' => 'Organizational Unit'},
{'CN' => "somesite.com"}]
name = values.collect{ |l| l.collect { |k, v| "/#{k}=#{v}" }.join }.join
key = OpenSSL::PKey::RSA.generate(1024)
pub = key.public_key
ca = OpenSSL::X509::Name.parse(name)
cert = OpenSSL::X509::Certificate.new
cert.version = 2
cert.serial = 1
cert.subject = ca
cert.issuer = ca
cert.public_key = pub
cert.not_before = Time.now
cert.not_before = Time.now + (360 * 24 * 3600)
File.open(privateKeyFile + "." + type, "w") {|f| f.write key.send("to_#{type}") }
File.open(publicKeyFile + "." + type, "w") {|f| f.write cert.send("to_#{type}") }
When I try to use the generated private key and certificate in apache, I get this error:
[Thu Mar 04 10:58:44 2010] [error] Init: Unable to read server certificate from file /etc/ssl/certs/gnarly.pem
[Thu Mar 04 10:58:44 2010] [error] SSL Library Error: 218529960 error:0D0680A8:asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag
[Thu Mar 04 10:58:44 2010] [error] SSL Library Error: 218595386 error:0D07803A:asn1 encoding routines:ASN1_ITEM_EX_D2I:nested asn1 error
This is what my certificate says:
-----BEGIN CERTIFICATE-----
<lots of stuff>
-----END CERTIFICATE-----
It calls itself a certificate instead of a CSR, which is what most of the things I've found online say about that apache2 error (that I might have gotten the CSR and CERT mixed up). My guess is that I'm not generating the right type of certificate. Maybe I have to change the serial or version attributes. Also, I'm not doing any self-signing anywhere, not that I know of anyways. I know you can do something like this though:
require "openssl"
key = OpenSSL::PKey::RSA.generate(1024)
signature = key.sign(OpenSSL::Digest::SHA1.new, "data to sign")
Reminder: My goal is to generate a self-signed certificate, in case my long-ish question lost focus on the way.
EDIT: I guess the real question is how to sign a certificate with a key
I created a helper class for this from code I lifted directly from nickyp's gist that I found on a Google search. The only dependency you need is the openssl gem (gem install openssl)
require 'openssl'
class SelfSignedCertificate
def initialize
#key = OpenSSL::PKey::RSA.new(1024)
public_key = #key.public_key
subject = "/C=BE/O=Test/OU=Test/CN=Test"
#cert = OpenSSL::X509::Certificate.new
#cert.subject = #cert.issuer = OpenSSL::X509::Name.parse(subject)
#cert.not_before = Time.now
#cert.not_after = Time.now + 365 * 24 * 60 * 60
#cert.public_key = public_key
#cert.serial = 0x0
#cert.version = 2
ef = OpenSSL::X509::ExtensionFactory.new
ef.subject_certificate = #cert
ef.issuer_certificate = #cert
#cert.extensions = [
ef.create_extension("basicConstraints","CA:TRUE", true),
ef.create_extension("subjectKeyIdentifier", "hash"),
# ef.create_extension("keyUsage", "cRLSign,keyCertSign", true),
]
#cert.add_extension ef.create_extension("authorityKeyIdentifier",
"keyid:always,issuer:always")
#cert.sign #key, OpenSSL::Digest::SHA1.new
end
def self_signed_pem
#cert.to_pem
end
def private_key
#key
end
end
Usage:
my_cert = SelfSignedCertificate.new
puts "Private Key:\n#{my_cert.private_key}"
puts "Self-signed PEM:\n#{my_cert.self_signed_pem}"
Output:
Private Key:
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDTtjPd3X9KX9BZpXKS82tM74Bs/hXsSLgnkitrc+oR4oF5PVko
NZL3j51gkX3jJRSG9tNPQC5NVR+5h7tXPxU5TAQZl6MUiV+YWuRng98GeCjP3ePp
meSStsKEMUiZI8YLVWrdbIjS+Q+lZnYMffeEOAoMSaei9hR4rOX0i+9hdwIDAQAB
AoGBALdAc/6sFd0zuC2Qhu7p4kvS11AAUsuWWkhuPkUhLU9TxwxBbOXgEZlVZzzK
UrQFSZJVHazweeOYNgCqmx82zE+cB4YzRLqkCPUD9t1bZcgk31tV39MSrC9CDKCB
inUTMKflPbHL0B+Lq24S8KfuW9bOPofhspjlV7cZCX5adFiBAkEA7KOMkiQMyq9X
ZVeRzJU0LmVdjrb7UBD5NebV+KaN8O7q+W4FG0nihcNj7xt2fZnvKM4FMfRwDP3G
HRUfR0alQQJBAOUIjKXYyoUsk+tLASoYLX+uPocjd7YSB9UPK2lFxqHOzekAlynF
u1JWEDPOjZNtNHmsQKOp5AWTUnm33JxfQLcCQByY5zQCB0m3RuiIXKZMobG5rkTA
+D4EzxkkfFdASYcEWIEsOpHBrA5ePoV23Crxn2VfAGG5GJF5WafKFa2XbAECQFL/
5Ch+BfZ5DynnxoZAuMxakuJaYhmjMx9tHehKlw8waMKVqjJDK/1MnxaHNhtFKg0l
9U7aVH4Iw4zEqrgodMUCQQCWZEUepSGoRVs1YDtag4FKSTMGXcnI/jllJmxHQhf4
uiy/8Hb+FW49w3KO1zeq7WdXw7W7Q1uO94npYX5p535d
-----END RSA PRIVATE KEY-----
Signed PEM:
-----BEGIN CERTIFICATE-----
MIICgjCCAeugAwIBAgIBADANBgkqhkiG9w0BAQUFADA6MQswCQYDVQQGEwJCRTEN
MAsGA1UECgwEVGVzdDENMAsGA1UECwwEVGVzdDENMAsGA1UEAwwEVGVzdDAeFw0x
NDA4MjEwMTI1MTZaFw0xNTA4MjEwMTI1MTZaMDoxCzAJBgNVBAYTAkJFMQ0wCwYD
VQQKDARUZXN0MQ0wCwYDVQQLDARUZXN0MQ0wCwYDVQQDDARUZXN0MIGfMA0GCSqG
SIb3DQEBAQUAA4GNADCBiQKBgQDTtjPd3X9KX9BZpXKS82tM74Bs/hXsSLgnkitr
c+oR4oF5PVkoNZL3j51gkX3jJRSG9tNPQC5NVR+5h7tXPxU5TAQZl6MUiV+YWuRn
g98GeCjP3ePpmeSStsKEMUiZI8YLVWrdbIjS+Q+lZnYMffeEOAoMSaei9hR4rOX0
i+9hdwIDAQABo4GXMIGUMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNNbdqck
QT/B5hdQqimtW1Wnf+fmMGIGA1UdIwRbMFmAFNNbdqckQT/B5hdQqimtW1Wnf+fm
oT6kPDA6MQswCQYDVQQGEwJCRTENMAsGA1UECgwEVGVzdDENMAsGA1UECwwEVGVz
dDENMAsGA1UEAwwEVGVzdIIBADANBgkqhkiG9w0BAQUFAAOBgQB80KzzhkXCgJ0s
1zXJXuocNDU5v0Z42ditNX9jS8pXuhHwcQbx7PVfOieO3GHC5YzzgMHGR3i2U2CQ
rz9hP937ERxCfqpxhfMAD3Q+3rHsdGdNIauzzFb6XoXsM7koRnM27I6qvO3bamcz
AVGH5eLic9IjZTQbZizFzNoR+H2N/g==
-----END CERTIFICATE-----
There is a create_self_signed_cert method in webrick/ssl, which is easy to understand and useful.
I've since found several very good sources for examples using OpenSSL:
http://snippets.dzone.com/posts/show/6309
http://projects.reductivelabs.com/projects/puppet/repository/revisions/master/entry/lib/puppet/sslcertificates.rb
http://projects.reductivelabs.com/projects/puppet/repository/revisions/master/entry/lib/puppet/sslcertificates/ca.rb
http://projects.reductivelabs.com/projects/puppet/repository/revisions/master/entry/lib/puppet/sslcertificates/certificate.rb
I still haven't found any good documentation for this yet, although I don't think it would take too long to write down what's in the examples.
I've also figured out how to do what I wanted from the puppet source code. Hope this helps someone else who's frustrated at the lack of documentation of OpenSSL in ruby.

Resources