Ruby client for 2-way ssl authentication - ruby

I have java web service supports 2-way ssl auth. So I have client key store (client.p12) with server certificate in trusted store and server key store with client cert in trusted store.
I can easily call my service using browser or postman (just need importing client.p12 in browser certificates management) but I have problems with ruby client.
My current version:
require 'rest_client'
p12 = OpenSSL::PKCS12.new(File.read('client.p12'), 'password')
client = RestClient::Resource.new('https://localhost:8080/service',
:ssl_client_cert => p12.certificate,
:ssl_cert_key => p12.key,
:verify_ssl => OpenSSL::SSL::VERIFY_NONE,
:ssl_version => 'TLSv1_2',
:ssl_ciphers => 'ECDHE-RSA-AES128-GCM-SHA256').get
fails with:
connect_nonblock': SSL_connect SYSCALL returned=5 errno=0 state=unknown state (OpenSSL::SSL::SSLError)
What is wrong with my client code?
openssl s_client output:
$ openssl s_client -connect localhost:8080
....
SSL-Session:
Protocol : TLSv1.2
Cipher : ECDHE-RSA-AES128-GCM-SHA256
...

The option for passing the key is not :ssl_cert_key, it is :ssl_client_key. Does that make any difference?

You need to import your client.p12 file into your nssdb location.
mkdir /root/nssdb
pk12util -i /path-to/your/client.p12 -d /root/nssdb
certutil -L -d /root/nssdb/
export SSL_DIR = /root/nssdb
curl -X POST -H "Content-Type: text/xml" --data "#{xml}" --cert cert:password "https://localhost:8080/service" -v -k
chmod -R 777 /root/nssdb
chown -R user /root/nssdb
Embed this curl call in your ruby client. It will work.
Note: If you are using a different ssl version you need to add --tlsv1.0 to the curl command

Related

curl - OpenSSL error error:0308010C:digital envelope routines::unsupported

I try to use curl on Windows to post a timestamp request. Authentication is needed, so I use p12 file. I get error message, but password of p12 file is correct.
Command:
curl --insecure --cert-type P12 --cert my.p12:mypassword -X POST -d #mytest.req <myTSURL>
Error message:
curl: (58) could not parse PKCS12 file, check password, OpenSSL error
error:0308010C:digital envelope routines::unsupported
curl -V
curl 7.83.1 (x86_64-pc-win32) libcurl/7.83.1 OpenSSL/3.0.2 (Schannel) zlib/1.2.12 brotli/1.0.9 libidn2/2.3.2 libssh2/1.10.0 nghttp2/1.47.0 ngtcp2/0.5.0 nghttp3/0.4.1 libgsasl/1.10.0
Release-Date: 2022-05-11
Protocols: dict file ftp ftps gopher gophers http https imap imaps ldap ldaps mqtt pop3 pop3s rtsp scp sftp smb smbs smtp smtps telnet tftp
Features: alt-svc AsynchDNS brotli gsasl HSTS HTTP2 HTTP3 HTTPS-proxy IDN IPv6 Kerberos Largefile libz MultiSSL NTLM SPNEGO SSL SSPI TLS-SRP UnixSocket
Meta: this isn't really programming or development, and would probably be better on superuser or maybe security.SX, but this is issue is likely to become more common as OpenSSL 3.0 spreads and I wanted to get the answer out.
OpenSSL 3.0.x by default doesn't support old/insecure algorithms, but until recently most software that creates PKCS12 (including OpenSSL 1.x.x) used such an algorithm for the certbag(s), namely a PKCS12-defined PBE using 40-bit RC2, usually abbreviated RC2-40 -- and some still does at least sometimes, like the Windows 10 cert-export dialog by default. To check this do
openssl pkcs12 -in my.p12 -info -nokeys -nocerts
# in 3.0.x add -provider legacy or just -legacy
# to avoid prompt use -password or -passin, see man pages
and I expect the output will include
PKCS7 Encrypted data: pbeWithSHA1And40BitRC2-CBC, Iteration 2048
See if your curl has an option to specify the OpenSSL 3.0.x provider and if so specify 'legacy'. Otherwise, convert your pkcs12 like (editted)
# 3.0.x
openssl pkcs12 -in old -nodes -provider legacy >temp && <temp openssl pkcs12 -export -out new
# or slightly simpler
openssl pkcs12 -in old -nodes -legacy >temp && <temp openssl pkcs12 -export -out new
# 1.x.x
openssl pkcs12 -in old -nodes >temp && <temp openssl pkcs12 -export -descert -out new
# and in either case securely delete temp; on systems with a memory tmpfs,
# typically /tmp, putting the file there can help assure this
# IFF 'old' was created by software that put the keybag before the certbag,
# which you can infer from the order displayed by pkcs12 -info,
# you can skip the temp file and pipe directly from one openssl to the other
Conversion loses any 'friendlyname' set in the existing file. For curl, and probably most other programs, this doesn't matter, but if you want to use this same file with something where friendlyname does matter, add -name $name on the -export part.
I was getting the same error using OpenVPN. I was able to fix it by adding or uncommenting the following lines in the /etc/ssl/openssl.cnf configuration file:
openssl_conf = openssl_init
[openssl_init]
providers = provider_sect
[provider_sect]
default = default_sect
legacy = legacy_sect
[default_sect]
activate = 1
[legacy_sect]
activate = 1
This is based on the information at OpenSSL WIKI

Error Certificate verify failed (certificate has expired)): in Mac OSX 11.6.1 and ruby 3.0.3

I have a ruby on rails webapp sending requests to a third party SOAP API. When I request like:
endpoint = "https://www.booking-manager.com/cbm_web_service2/services/CBM?wsdl"
client = Savon.client(wsdl: endpoint,
#log_level: :info,
log_level: :debug,
log: true,
pretty_print_xml: true,
open_timeout: 300,
read_timeout: 300)
message = {'in0' => xxx,
'in1' => 'xxxx',
'in2' => 'xxx'}
response = client.call(:get_bases, message: message)
I´m getting next error:
HTTPI::SSLError (SSL_connect returned=1 errno=0 state=error: certificate verify failed (certificate has expired)):
The webapp is running under:
Mac OSX Big Sur 11.6.1
ruby 3.0.3p157 (2021-11-24 revision 3fb7d2cadc) [x86_64-darwin20]
I have this issue for weeks and I don´t know what else to do. According to many posts, I tested
openssl s_client -showcerts -host valid-isrgrootx1.letsencrypt.org -port 443
and got:
CONNECTED(00000005)
depth=1 O = Digital Signature Trust Co., CN = DST Root CA X3
verify error:num=10:certificate has expired
notAfter=Sep 30 14:01:15 2021 GMT
verify return:0
depth=1 O = Digital Signature Trust Co., CN = DST Root CA X3
verify error:num=10:certificate has expired
notAfter=Sep 30 14:01:15 2021 GMT
verify return:0
depth=3 O = Digital Signature Trust Co., CN = DST Root CA X3
verify error:num=10:certificate has expired
notAfter=Sep 30 14:01:15 2021 GMT
verify return:0
---
Certificate chain
0 s:/CN=origin.letsencrypt.org
i:/C=US/O=Let's Encrypt/CN=R3
so, according to this: https://community.letsencrypt.org/t/help-thread-for-dst-root-ca-x3-expiration-september-2021/149190/970
I manually updated the file /etc/ssl/cert.pem to remove the DST Root CA X3 certificate. After that, I think that I moved one step forward. When running:
openssl s_client -showcerts -host valid-isrgrootx1.letsencrypt.org -port 443
Now, I don´t get the error and I think looks good:
CONNECTED(00000005)
depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = R3
verify return:1
depth=0 CN = origin.letsencrypt.org
verify return:1
---
Certificate chain
0 s:/CN=origin.letsencrypt.org
i:/C=US/O=Let's Encrypt/CN=R3
However, unfortunately the error in my ruby app still remains the same. According to this, I understand ruby is running an openssl that is not getting the information from this certs. I´m not skilled with this at all and don´t know if this makes sense.
I just read other posts and checking openssl version
I got LibreSSL 2.8.3
which openssl
/usr/bin/openssl
In my /usr/local/opt I see three openssl versions folders:
openssl
openssl#1.1
openssl#3
I updated my .zshrc file and now openssl version notifies
OpenSSL 3.0.1 14 Dec 2021 (Library: OpenSSL 3.0.1 14 Dec 2021)
and ruby seems to be using:
ruby -ropenssl -e "puts OpenSSL::OPENSSL_VERSION"
OpenSSL 1.1.1l 24 Aug 2021
I´m aware that the ruby OpenSSL version is 1.1.1 and the system is running 3.0.1. I don´t know how to update ruby to run OpenSSL 3.0.1, although I´m not sure if this can be the root problem. I´m lost at this point.
UPDATE
I think I´m narrowing the issue down. My guess is that Ruby is using a version of openSSL, in this case 1.1.1, that is pointing to /Users/Rober/.rbenv/versions/3.0.3/openssl/ssl/certs bundler instead of pointing to /etc/ssl/cert.pem
irb
irb(main):001:0> require "openssl"
=> true
irb(main):002:0> puts OpenSSL::OPENSSL_VERSION
OpenSSL 1.1.1l 24 Aug 2021
=> nil
irb(main):003:0> puts "SSL_CERT_FILE: %s" % OpenSSL::X509::DEFAULT_CERT_FILE
irb(main):004:0> puts "SSL_CERT_DIR: %s" % OpenSSL::X509::DEFAULT_CERT_DIR
SSL_CERT_FILE: /Users/Rober/.rbenv/versions/3.0.3/openssl/ssl/cert.pem
SSL_CERT_DIR: /Users/Rober/.rbenv/versions/3.0.3/openssl/ssl/certs
This file /Users/Rober/.rbenv/versions/3.0.3/openssl/ssl/cert.pem , unfortunately when I check the content is in the format:
-----BEGIN CERTIFICATE-----
certificate chain
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----
I mean, in this file /etc/ssl/cert.pem I could read some readable headers that helped identify the certificate to remove, but in this case the headers are not present, so it´s not possible.
I think that I probably just need to config ruby to run openssl to point to this file /etc/ssl/cert.pem. According to mamy posts, I just added export SSL_CERT_FILE="/etc/ssl/cert.pem" to my .zshrc file, but still getting
OpenSSL::X509::DEFAULT_CERT_FILE
SSL_CERT_FILE: /Users/Rober/.rbenv/versions/3.0.3/openssl/ssl/cert.pem
SOLUTION
Thanks to #JanGaraj that provided the right solution to this problem in my other production post: SSL_connect returned=1 errno=0 state=error: certificate verify failed in ruby and Ubuntu 14.04
Just to summarize, apart from the points depicted above, I just needed to update my web service request specifying my ca-certificates file, like: Savon.client(ssl_ca_cert_file: "/etc/ssl/certs/ca-certificates.crt ")
The solution to this question was provided in another post by #jangaraj
It looks like you are using Ubuntu 14 and Savon 2 client. Savon 2 client doc: https://www.savonrb.com/version2/globals.html
ssl_ca_cert_file
Sets the SSL ca cert file to use.
Savon.client(ssl_ca_cert_file: "lib/ca_cert.pem")
I would point ssl_ca_cert_file to /etc/ssl/certs/ca-certificates.crt explicitly.

How to properly use ca_file for self-signed certificates with Net::HTTP in ruby?

I am trying to understand how to use the ca_file property of the Net::HTTP class to allow connections to hosts with self-signed certificates.
I have prepared a minimal example of my current approach. Check https://repl.it/repls/ElegantRaggedInstitutes or keep reading here:
I have used this command to get a hold of the certificate of https://self-signed.badssl.com
openssl s_client -showcerts -verify 5 -connect self-signed.badssl.com:443 < /dev/null
I have then stored the certificate in a local file and tried to execute the following snippet
require 'net/http'
require 'openssl'
http_conn = Net::HTTP.new('self-signed.badssl.com', 443)
http_conn.use_ssl = true
http_conn.verify_mode = OpenSSL::SSL::VERIFY_PEER
http_conn.ca_file = '/path/to/badssl.cert'
http_conn.start
I expected this to successfully open a connection, accepting the certificate. Instead, it gives me this error:
SSL_connect returned=1 errno=0 state=error: certificate verify failed (self signed certificate)
I am surely doing something wrong, can you please advise?
Somehow the certificates got messed. Not sure if I executed the wrong command, copied from the wrong shell or maybe the certificate of self-signed.badssl.com just changed. The certificate that is included in the original repl is actually not self-signed but is signed by an untrusted intermediate CA that was not included in the chain (i.e. I did not add it to the ca_file).
I have verified that by running the command
openssl x509 -text -in /path/to/badssl.crt
and observing the oputput
...
Serial Number:
cd:bc:5a:4a:ec:97:67:b1
Signature Algorithm: sha256WithRSAEncryption
Issuer: C = US, ST = California, L = San Francisco, O = BadSSL, CN = BadSSL Intermediate Certificate Authority
Validity
Not Before: Aug 8 21:17:05 2016 GMT
Not After : Aug 8 21:17:05 2018 GMT
Subject: C = US, ST = California, L = San Francisco, O = BadSSL Fallback. Unknown subdomain or no SNI., CN = badssl-fallback-unknown-subdomain-or-no-sni
...
Today I grabbed the certificate again
openssl s_client -showcerts -verify 5 -connect self-signed.badssl.com:443 < /dev/null
After updating the certificate in my (forked) repl, the snippet passes:
https://repl.it/repls/UnselfishAvariciousDeletions

OpenSSL::X509::Certificate Showing Certificate for Wrong Domain

I'm looping through a list of domains to see if a) there is 443 listener and b) collect the ssl cert expiry, signature algorithm, and common name. All of the domains that have a 443 listener report the correct ssl cert (matching up to what Chrome reports), however, there is one domain that does not report correctly - myproair.com, which reports a certificate for parkinsonsed.com - any ideas?
# ssl cert lookup
begin
timeout(1) do
tcp_client = TCPSocket.new("#{instance["domain"]}", 443)
ssl_client = OpenSSL::SSL::SSLSocket.new(tcp_client)
ssl_client.connect
cert = OpenSSL::X509::Certificate.new(ssl_client.peer_cert)
ssl_client.sysclose
tcp_client.close
#http://ruby-doc.org/stdlib-2.0/libdoc/openssl/rdoc/OpenSSL/X509/Certificate.html
date = Date.parse((cert.not_after).to_s)
row.push("#{date.strftime('%F')} #{cert.signature_algorithm} #{cert.subject.to_a.select{|name, _, _| name == 'CN' }.first[1]}".downcase.ljust(57))
end
rescue SocketError
row.push("down".ljust(57))
rescue Errno::ECONNREFUSED
row.push("connection refused".ljust(57))
rescue Errno::ECONNRESET
row.push("connection reset".ljust(57))
rescue Timeout::Error
row.push("no 443 listener".ljust(57))
rescue Exception => ex
row.push("error: #{ex.class}".ljust(57))
end
Update: Here are the versions I'm working with:
$ ruby --version
ruby 2.0.0p481 (2014-05-08 revision 45883) [universal.x86_64-darwin14]
$ openssl version
OpenSSL 0.9.8zc 15 Oct 2014
I verified the SNI extension is being sent in the ClientHello using OpenSSL's s_client with -connect, -tls1 and -servername options.
however, there is one domain that does not report correctly - myproair.com, which reports a certificate for parkinsonsed.com - any ideas?
It looks like shared hosting combined with SSL is the culprit. Apparently, parkinsonsed.com is the default site for the server.
You should use SNI to overcome the limitations. SNI is available in TLS 1.0 and above. Also see Server Name Indication support in Net::HTTP?
myproair.com, with SSLv3 and no SNI:
$ openssl s_client -ssl3 -connect myproair.com:443 | openssl x509 -text -noout | grep -A 1 -i name
...
X509v3 Subject Alternative Name:
DNS:parkinsonsed.com, DNS:www.parkinsonsed.com, DNS:test.parkinsonsed.com, DNS:dev.parkinsonsed.com
myproair.com, with TLS 1.0 and SNI:
$ openssl s_client -tls1 -connect myproair.com:443 -servername myproair.com | openssl x509 -text -noout | grep -A 1 -i name
...
X509v3 Subject Alternative Name:
DNS:myproair.com, DNS:www.myproair.com, DNS:dev.myproair.com, DNS:test.myproair.com

Using a self-signed certificate

I am just trying to get my head around SSL.
I have set up a Jetty server on my localhost, and generated my own certificate using Keytool.
Now when I go to https://localhost:8443/ I get the can't trust this certificate error.
I use
keytool -export -alias pongus -keystore keystore -file certfile.cer
To create the certificate which I think is what the client needs to authenticate with the server. (This is where I could be very wrong!)
I have the following ruby code :
require 'net/https'
require 'openssl'
require 'open-uri'
puts 'yay' if File.exists?('certfile.cer')
uri = URI.parse("https://localhost:8443/")
http_session = Net::HTTP.new(uri.host, uri.port)
http_session.use_ssl = true
http_session.verify_mode = OpenSSL::SSL::VERIFY_PEER
http_session.ca_file = 'certfile.cer'
res = http_session.start do |http|
# do some requests here
http.get('/')
end
This does print 'yay', so the certfile.cer file does exist.
But I get the errors
/Applications/NetBeans/NetBeans 6.8.app/Contents/Resources/NetBeans/ruby2/jruby-1.4.0/lib/ruby/1.8/net/http.rb:586 warning: can't set verify locations
/Applications/NetBeans/NetBeans 6.8.app/Contents/Resources/NetBeans/ruby2/jruby-1.4.0/lib/ruby/1.8/net/http.rb:586:in `connect': certificate verify failed (OpenSSL::SSL::SSLError)
Any ideas what I am doing wrong?
EDIT
I want to get it so I guarantee that I am connecting to the right server, and the server can guarantee that it is me connecting to it, without any tampering in between. I am developing both the server and the client.
Your client needs access to its
private key.
You dont need the private key for server certificate verification. All you need is the certificate itself which contains the public key. Only the server has the private key. Well described here http://www.helpbytes.co.uk/https.php and here http://www.verisign.com/ssl/ssl-information-center/how-ssl-security-works/
My recommendation is simple. Check your certificate is correct.
openssl x509 -text -in mycert.crt
Also if you have access to the server you can explicitely validate if the certificate and key (used in httpd configuration) are correct (matches): http://kb.wisc.edu/middleware/page.php?id=4064 Please note this is explicit check ran on server. Never give out the private key. This check can be done only by the administrator to verify if the httpd was not misconfigured.
(openssl x509 -noout -modulus -in server.pem | openssl md5 ;\
openssl rsa -noout -modulus -in server.key | openssl md5) | uniq
You can also debug the SSL certificate communication using standard openssl command. Issue this command then wait few seconds and then type QUIT and hit enter. You will see the certificate the server sends out.
openssl s_client -connect your.server.com:443
Also try to import the certificate to your browser and access the URL resource. Browser is able to validate it by clicking on https (Firefox and Chrome). Then you will see the certificate itself and validity information.
All the above was all about the server certificate. This is only one part of the problem. "I am connecting to the right server, and the server can guarantee that it is me connecting to it" Actully in your code above you only check for the server cert. Now. If you want a client certificate (the second part of your statement) than you need this in Ruby:
File.open( "client_certificate.pem", 'rb' ) { |f| cert = f.read }
File.open( "client_key.pem", 'rb' ) { |f| key = f.read }
http_session.cert = OpenSSL::X509::Certificate.new(cert)
http_session.key = OpenSSL::PKey::RSA.new(key, nil)
This is how client cert should be used in Ruby. If your private key is encrypted with a password just pass it instead nil in the second argument of RSA constructor.
I highly recommend to get server certificate working (your code) and then start with client certificate. Please note you keep your current code (ca_cert, validation constant) and add the above four lines to it.
Hope this helps.
Your client needs access to its private key. The private key is not in the certificate, the certificate only contains the public key. Sorry, I don't know ruby, but a common technique is to bundle the private key and certificate in a single PKCS#12, aka p12, file and supply this to the crypto library.
Change
http_session.verify_mode = OpenSSL::SSL::VERIFY_PEER
to
http_session.verify_mode = OpenSSL::SSL::VERIFY_NONE
Once you do that, the SSL will work properly. I have used this multiple times in my development environments, always works flawlessly.

Resources