I'm working through creating an SSL-enabled server in Ruby, along with a corresponding Ruby client to use with the server. In order to test, I created my own Root CA certificate with the following commands.
$:~/devel/ssl-test/ssl/CA$ openssl genrsa -out TestCA.key 2048
Generating RSA private key, 2048 bit long modulus
............+++
...........................+++
e is 65537 (0x10001)
$:~/devel/ssl-test/ssl/CA$ openssl req -new -key TestCA.key -out TestCA.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (eg, YOUR name) []:
Email Address []:
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
$:~/devel/ssl-test/ssl/CA$ openssl x509 -req -days 365 -in TestCA.csr -out TestCA.crt -signkey TestCA.key
Signature ok
subject=/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd
Getting Private key
I then generated an SSL certificate for my server:
$:~/devel/ssl-test/ssl/keys$ openssl genrsa -out server.key 2048
Generating RSA private key, 2048 bit long modulus
.+++
............................................+++
e is 65537 (0x10001)
$:~/devel/ssl-test/ssl/keys$ cd ../csrs/
$:~/devel/ssl-test/ssl/csrs$ openssl req -new -key ../keys/server.key -out server.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (eg, YOUR name) []:my.secure.test
Email Address []:
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
$:~/devel/ssl-test/ssl/csrs$ cd ../certs/
$:~/devel/ssl-test/ssl/certs$ openssl ca -in ../csrs/server.csr -cert ../CA/TestCA.crt -keyfile ../CA/TestCA.key -out server.crt
Using configuration from /usr/lib/ssl/openssl.cnf
I am unable to access the ./demoCA/newcerts directory
./demoCA/newcerts: No such file or directory
$:~/devel/ssl-test/ssl/certs$ mkdir -p demoCA/newcerts
$:~/devel/ssl-test/ssl/certs$ touch demoCA/index.txt
$:~/devel/ssl-test/ssl/certs$ echo "01" > demoCA/serial
$:~/devel/ssl-test/ssl/certs$ openssl ca -in ../csrs/server.csr -cert ../CA/TestCA.crt -keyfile ../CA/TestCA.key -out server.crt
Using configuration from /usr/lib/ssl/openssl.cnf
Check that the request matches the signature
Signature ok
Certificate Details:
Serial Number: 1 (0x1)
Validity
Not Before: Oct 25 16:25:05 2011 GMT
Not After : Oct 24 16:25:05 2012 GMT
Subject:
countryName = AU
stateOrProvinceName = Some-State
organizationName = Internet Widgits Pty Ltd
commonName = my.secure.test
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
Netscape Comment:
OpenSSL Generated Certificate
X509v3 Subject Key Identifier:
48:50:B5:04:11:02:F1:40:97:58:BF:5F:8B:27:50:10:C0:3F:EE:D9
X509v3 Authority Key Identifier:
DirName:/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd
serial:81:44:16:06:5C:EB:5E:71
Certificate is to be certified until Oct 24 16:25:05 2012 GMT (365 days)
Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
After that, I created a simplistic SSL-enabled server to use the SSL certificate I just created.
require 'socket'
require 'openssl'
require 'thread'
server = TCPServer.new(1337)
context = OpenSSL::SSL::SSLContext.new
context.cert = OpenSSL::X509::Certificate.new(File.open('ssl/certs/server.crt'))
context.key = OpenSSL::PKey::RSA.new(File.open('ssl/keys/server.key'))
secure = OpenSSL::SSL::SSLServer.new(server, context)
puts 'Listening securely on port 1337...'
loop do
Thread.new(secure.accept) do |conn|
begin
while request = conn.gets
$stdout.puts '=> ' + request
response = "You said: #{request}"
$stdout.puts '<= ' + response
conn.puts response
end
rescue
$stderr.puts $!
end
end
end
When started, it seems to work fine...
$:~/devel/ssl-test$ ruby server.rb
Listening securely on port 1337...
I then created a non-SSL capable client just to ensure it was denied connectivity.
require 'socket'
require 'thread'
client = TCPSocket.new('127.0.0.1', 1337)
Thread.new do
begin
while response = client.gets
$stdout.puts response
end
rescue
$stderr.puts "Error from client: #{$!}"
end
end
while request = $stdin.gets
request = request.chomp
client.puts request
end
When I run this via the following:
$:~/devel/ssl-test$ ruby client.rb
hello
Error from client: Connection reset by peer
Correspondingly, I get the following from the server:
$:~/devel/ssl-test$ ruby server.rb
Listening securely on port 1337...
/usr/local/rvm/rubies/ruby-1.9.2-head/lib/ruby/1.9.1/openssl/ssl-internal.rb:164:in `accept': SSL_accept returned=1 errno=0 state=SSLv2/v3 read client hello A: unknown protocol (OpenSSL::SSL::SSLError)
from /usr/local/rvm/rubies/ruby-1.9.2-head/lib/ruby/1.9.1/openssl/ssl-internal.rb:164:in `accept'
from server.rb:16:in `block in <main>'
from server.rb:15:in `loop'
from server.rb:15:in `<main>'
This was all expected. Next, I modified the client code to use an SSL context.
require 'socket'
require 'openssl'
require 'thread'
client = TCPSocket.new('127.0.0.1', 1337)
context = OpenSSL::SSL::SSLContext.new
secure = OpenSSL::SSL::SSLSocket.new(client, context)
secure.sync_close = true
secure.connect
Thread.new do
begin
while response = secure.gets
$stdout.puts response
end
rescue
$stderr.puts "Error from client: #{$!}"
end
end
while request = $stdin.gets
request = request.chomp
secure.puts request
end
I fully expected this to fail as well during the handshake process, but it did not... I got the following result:
$:~/devel/ssl-test$ ruby client.rb
hello
You Said: hello
Why did this work? I was assuming it would fail because I didn't think the client would have any idea about the Root CA I created and signed the server SSL certificate with, and therefore wouldn't be able to verify the server's certificate. What am I missing? When I created and signed the server's certificate and it was "committed", did this somehow make it available to the OpenSSL library? I was expecting to have to somehow tell the SSL context in the client where to look for the Root CA I created for testing purposes...
As a follow-up test, I copied my client code over to a different machine that definitely knows nothing about the Root CA I created for this test, changed the IP address the client connects to, and ran the test again. This test produced the same results - the client was able to communicate with the server when I assumed it wouldn't be able to. Any ideas?
Why am I seeing certificate verify failed ? This error happens when your computer is missing a file that it needs to verify that the server behind RubyGems.org is the correct one. The latest version of RubyGems should fix this problem, so we recommend updating to the current version.
Strictly, SSL_CERT_FILE is an OpenSSL environment variable. Requests doesn't use it because it does not ever check for it being set, and never trusts that OpenSSL will locate certs.
Depending on the version of Ruby you are using, the default verify mode for the SSLContext object may not be enforcing certificate verification. You can force the verify mode with:
context = OpenSSL::SSL::SSLContext.new
context.verify_mode = OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
This should cause the client's connection attempt to fail, as expected.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With