Estoy trabajando en la creación de un servidor SSL habilitado en Ruby, junto con un cliente Ruby correspondiente para usar con el servidor. Para probar, creé mi propio certificado de CA raíz con los siguientes comandos.¿Por qué funciona esta prueba de cliente/servidor habilitada para Ruby SSL?
$:~/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
entonces genera un certificado SSL para mi servidor:
$:~/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
Después de eso, he creado un servidor habilitado para SSL simplista para utilizar el certificado SSL que acaba de crear.
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
Cuando se inicia, que parece funcionar bien ...
$:~/devel/ssl-test$ ruby server.rb
Listening securely on port 1337...
Entonces creé un cliente capaz no SSL sólo para asegurarse de que se le negó la conectividad.
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
Cuando ejecuto esto a través de lo siguiente:
$:~/devel/ssl-test$ ruby client.rb
hello
Error from client: Connection reset by peer
En consecuencia, me sale el siguiente desde el servidor:
$:~/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>'
Esto fue todo lo esperado. Luego, modifiqué el código del cliente para usar un contexto SSL.
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
estaba totalmente convencida que falle también durante el proceso de apretón de manos, pero no fue así ... me dieron el siguiente resultado:
$:~/devel/ssl-test$ ruby client.rb
hello
You Said: hello
¿Por qué este trabajo? Asumí que fallaría porque no creía que el cliente tuviera ninguna idea sobre la CA raíz que creé y firmé con el certificado SSL del servidor, y por lo tanto no podría verificar el certificado del servidor. ¿Qué me estoy perdiendo? Cuando creé y firmé el certificado del servidor y estaba "comprometido", ¿esto de alguna manera lo puso a disposición de la biblioteca OpenSSL? Esperaba tener que decir de alguna manera el contexto SSL en el cliente dónde buscar la CA raíz que creé para realizar pruebas ...
Como prueba de seguimiento, copié el código de mi cliente en una máquina diferente que definitivamente no sabe nada acerca de la CA raíz que creé para esta prueba, cambió la dirección IP a la que se conecta el cliente y volvió a ejecutar la prueba. Esta prueba produjo los mismos resultados: el cliente pudo comunicarse con el servidor cuando asumí que no podría hacerlo. ¿Algunas ideas?
Ah sí, eso funcionó. Gracias Ian! Ahora, ¿qué debo hacer para informar al contexto SSL en el cliente sobre dónde buscar mi CA raíz personalizada para que se pueda verificar el certificado del servidor? – Bryan
Por extraño que parezca, de acuerdo con http://rubydoc.info/stdlib/openssl/1.9.2/OpenSSL/SSL/SSLContext#DEFAULT_PARAMS-constant VERIFY_PEER debe ser un parámetro predeterminado, pero aparentemente los valores predeterminados no se aplican correctamente. –
@Bryan Te recomendamos utilizar las configuraciones 'ca_file' o' ca_path' en el objeto 'SSLContext'. 'ca_file' es probablemente el más fácil ya que solo lo apunta al archivo .pem, o lo que sea. 'ca_path' requiere un directorio que ha sido correctamente hash por openssl. No he hecho eso por un tiempo, pero veré si puedo encontrar un enlace sobre él. –