Ruby verify the certificate of secure ldap server - ruby-on-rails

I am using https://github.com/ruby-ldap/ruby-net-ldap (net-ldap) gem to verify the authenticity of a user in my rails app. But before passing data to the ldap server, I need to verify that I am talking with the same secure server.
Is there a workaround which allows me to verify the certificate in ruby
Additional details: (things I have tried)
The certificate which is passed on to me is same as the one I see when I run
openssl s_client -showcerts -connect "<host>:<port>" </dev/null 2>/dev/null|openssl x509 -outform PEM
I used http://www.ldapsoft.com/ to connect to client's server
Unless I add the certificate file given to me in Security > Manage server certificates, I get a warning saying unknown security certificate
I tried do it manually first in plain ruby (without gem)
But i get following error
test-ssl.rb:23:in `connect': SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed (OpenSSL::SSL::SSLError)
Code:
cert_store = OpenSSL::X509::Store.new
cert_store.add_file "server-wildcard.crt"
io = TCPSocket.new("SECURELDAP.MYSITE.EDU","636")
ctx = OpenSSL::SSL::SSLContext.new
#ctx.cert = OpenSSL::X509::Certificate.new(File.read("server-wildcard.crt"))
#ctx.client_ca = OpenSSL::X509::Certificate.new(File.read("server-wildcard.crt"))
#ctx.ca_file = "server-wildcard.crt"
#ctx.ca_path = "./"
ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
ctx.cert_store = cert_store
conn = OpenSSL::SSL::SSLSocket.new(io, ctx)
conn.connect

I am posting my solution here for the sake of completeness.
net-ldap gem override to support certificate validation
https://gist.github.com/mintuhouse/9931865
Ideal Solution:
Maintain list of trusted root CAs on your server
(If you are lazy like me, have a cron job which will download (weekly maintained by curl) copy from http://curl.haxx.se/ca/cacert.pem)
Override Net::HTTP to always use this trusted certificate list

As of today (late 2016), ruby-net-ldap supports this upstream! However, tls_options needs to be passed with verify_mode set to a value other than the default VERIFY_NONE.
# optional: create/pass your own cert_store
cert_store = OpenSSL::X509::Store.new
cert_store.set_default_paths # or add your own CAdir, &c.
# attributes documented for OpenSSL::SSL::SSLContext are valid here
tls_options = {
verify_mode: OpenSSL::SSL::VERIFY_PEER
cert_store: cert_store
}
ldap = Net::LDAP.new(
:host => host,
:port => port,
:encryption => {
:method => :simple_tls, # could also be :start_tls
:tls_options => tls_options
}
)

Related

Sentry is asking for Certificate on Rails Production

Im using rails 5 with sentry installed, I have tested it locally and it already works, however moving to production I am getting a certificate error when I boot up console and test Sentry.capture_message("new test 2") with the following error:
Event sending failed: SSL_connect returned=1 errno=0 state=error: certificate verify failed (certificate has expired)
Unreported Event: new test 2
exception happened in background worker: SSL_connect returned=1 errno=0 state=error: certificate verify failed (certificate has expired)
My code is as follows
Sentry.init do |config|
config.dsn = ENV["SENTRY_DNS"]
config.breadcrumbs_logger = [:active_support_logger, :http_logger]
config.traces_sample_rate = 0.25
config.enabled_environments = %[ staging ]
end
Your issue is that your server is attempting to verify the ssl cert when connecting to sentry. For
Sentry.init do |config|
config.transport.ssl_verification = false
config.dsn = ENV["SENTRY_DNS"]
config.breadcrumbs_logger = [:active_support_logger, :http_logger]
config.traces_sample_rate = 0.25
config.enabled_environments = %[ staging ]
end
When attempting to send to sentry your server is failing to verify the SSL certificate correctly. You can cancel verification by adding the above option. This is a bit of a security hole so the more correct way would be to set:
config.transport.ssl_ca_file = 'path to a valid local cert file'
instead.

Ruby - Connect with SSL and authenticate via client certificate - sslv3 alert bad certificate

I'm trying to connect a webcrawler that accesses a certain site via SSL and queries my data on that site. The authentication of this site is via a self-signed Digital Certificate. At the moment I want to access the site, I upload this certificate in .pfx format to my api, convert it to .pem, and when I try to access the site with this certificate, the response comes with status 403 (forbidden ).
However, when I try to access the site through a browser with the certificate in .pfx format I usually get it.
I already tried using Mechanize, and it worked for a while (until a few months ago it worked), but then it started to give the error:
SSL_connect returned = 1 errno = 0 state = SSLv3 read finished A: sslv3 alert bad certificate
The site is old, it does not receive updates frequently.
After that I already tried to use the net / http lib and the error persisted, I tried to use the httprb gem and lastly I tried with Faraday. All attempts ended either in that error quoted above or with the response status == 403.
What can I do to be able to connect? Is there something wrong with my script? Is it missing any information I need to get through?
Code:
# Faraday customs method:
class FaradayHttp
def with_openssl
system "openssl pkcs12 -in my-certificate-path -out certificate-output-path -nodes -password pass:certificate-password"
def cert_object
OpenSSL::X509::Certificate.new File.read("certificate-output-path")
end
# create PKey
def key_object
OpenSSL::PKey.read File.read("certificate-output-path")
end
faraday = Faraday::Connection.new 'https://example-site.com',
:ssl => {
certificate: cert_object,
private_key: key_object,
version: :SSLv3,
verify: false
}
faraday
end
end
# Controller that try to connect with the ssl server:
agent = FaradayHttp.new.with_openssl
page = agent.get '/login_path'
# mypki will prompt you for certificates
require 'mypki'
# faraday will use certificates from mypki
require 'faraday'
faraday = Faraday::Connection.new 'https://example-site.com'
faraday.get '/login_path'

ActionMailer SMTP "certificate verify failed"

I want to send emails from my Rails web application, and I do not want to disable TLS certificate verification. However for some reason, it always fails with "SSLv3 read server certificate B: certificate verify failed", even though the server certificate is valid.
I doubled checked with openssl s_client (using /etc/ssl/certs/ca-certificates.crt), and running the following in the rails console also works, delivering successfully.
smtp = Net::SMTP.new(host, port)
smtp.enable_tls
smtp.start("localhost", username, password, :login) do |smtp|
smtp.send_message msgstr, from, to
end
The server has Rails 4.2.6 and Ruby 2.3.0
config.action_mailer.smtp_setting = {
address:
port: 465,
user_name:
password:
authentication: :login,
openssl_verify_mode: OpenSSL::SSL::VERIFY_PEER,
enable_starttls_auto: false,
ssl: true
}
From the described behavior I am quite sure that peer verification has not been done in the console and that you need to explicitly set the certificate store for verifying peer certificates in your Rails configuration.
Why it "works" in the console and how to actually verify peers there:
The observation that it works from the console but does not from Rails code is caused by the fact that smtp.enable_tls in your console code does not force peer verification whereas your Rails configuration apparently does. Indeed, when you write the command to the console, you get the SSLContext printed out:
smtp.enable_tls
# => #<OpenSSL::SSL::SSLContext:0x000000064043d0 #cert=nil, #key=nil,
#client_ca=nil, #ca_file=nil, #ca_path=nil, #timeout=nil,
#verify_mode=nil, #verify_depth=nil, #renegotiation_cb=nil,
#verify_callback=nil, #cert_store=nil, #extra_chain_cert=nil,
#client_cert_cb=nil, #session_id_context=nil, #tmp_dh_callback=nil,
#session_get_cb=nil, #session_new_cb=nil, #session_remove_cb=nil,
#tmp_ecdh_callback=nil, #servername_cb=nil, #npn_protocols=nil,
#alpn_protocols=nil, #alpn_select_cb=nil, #npn_select_cb=nil>
Note that #verify_mode is nil so there is no peer verification enabled by default on the SSLContext.
To force peer verification in console, so that you can play with the SSL settings manually, you need to use a custom SSLContext and pass it to enable_tls:
ssl_context = Net::SMTP.default_ssl_context
ssl_context.set_params
smtp.enable_tls(ssl_context)
# => #<OpenSSL::SSL::SSLContext:0x000000063c27c8 #cert=nil, #key=nil,
#client_ca=nil, #ca_file=nil, #ca_path=nil, #timeout=nil,
#verify_mode=1, #verify_depth=nil, #renegotiation_cb=nil,
#verify_callback=nil, #cert_store=#<OpenSSL::X509::Store:0x00000002894408 #verify_callback=nil, #error=nil, #error_string=nil, #chain=nil, #time=nil>, #extra_chain_cert=nil,
#client_cert_cb=nil, #session_id_context=nil, #tmp_dh_callback=nil,
#session_get_cb=nil, #session_new_cb=nil, #session_remove_cb=nil,
#tmp_ecdh_callback=nil, #servername_cb=nil, #npn_protocols=nil,
#alpn_protocols=nil, #alpn_select_cb=nil, #npn_select_cb=nil>
Watch closely the differences: the SSLContext now has verify_mode set to 1 and has a certificate store for the verifications defined. This is (among other things) what the set_params method in SSLContext does.
How to configure the certificate store in Rails
Now, Rails does not call the set_params methods when constructing the SSLContext for SMTP connection. Instead, it sets the individual attributes on it according to the options (see here and here in the source code). You have properly configured Rails that you want to verify peer certificates but you have not configured a certificate store to verify peers against.
This can be done using the ca_file or ca_path options, so the following Rails configuration should work for you:
config.action_mailer.smtp_setting = {
...
ssl: true
enable_starttls_auto: false,
openssl_verify_mode: OpenSSL::SSL::VERIFY_PEER,
ca_file: "/etc/ssl/certs/ca-certificates.crt",
...
}
I have no idea why this is not properly documented in the Rails Guides...
This Rails configuration works for me (using Ruby 2.2.2 and Rails 5):
ActionMailer::Base.smtp_setting = {
...
enable_starttls_auto: true,
openssl_verify_mode: OpenSSL::SSL::VERIFY_PEER,
openssl_verify_depth: 3, # if your CA is a sub signer
ca_file: "/etc/ssl/certs/ca-certificates.crt",
...
}

Intermittent SSL certificate verification failures with mechanize and ruby

I have a Rails 3.2.8 app, with Ruby 1.9.3 on Ubuntu 12.04. It uses mechanize to connect to an https web site.
I am seeing this error intermittently:
SSL_connect returned=1 errno=0 state=SSLv3 read server certificate B: certificate verify failed
I do set the CA file:
Mechanize.new do |agent|
agent.ssl_version = "SSLv3"
agent.ca_file = Rails.root.join("lib/cacert.pem").to_s
end
I have also tried using cert_store:
cert_store = OpenSSL::X509::Store.new
cert_store.set_default_paths
Mechanize.new do |agent|
agent.ssl_version = "SSLv3"
agent.cert_store = cert_store
end
And setting the store explicitly:
cert_store = OpenSSL::X509::Store.new
cert_store.add_file Rails.root.join("lib/cacert.pem").to_s
Mechanize.new do |agent|
agent.ssl_version = "SSLv3"
agent.cert_store = cert_store
end
These errors appear regardless of which method I use to specify the CA/certificates (including relying on default behaviour). When I run the code manually from rails console, it works fine. Which of the above, if any, are correct? What else can I do to debug this?

How can I use a PEM certificate with password?

I'm having plenty of trouble trying to use a certificate that has a password on ruby. I'm using rest-client, but that's not a requirement.
This is the cURL-equivalent of the call I need to make:
curl -E certificate.pem:PASSWORD -d ident=language -d data="test" "https://theurl"
I tried many things, but I can't get the password part working. Here's what I have so far:
cert = OpenSSL::X509::Certificate.new(File.read("#{RAILS_ROOT}/certificate.pem"))
reply = RestClient.post("https://theurl", {:ident => 'language', :data => 'test'}, {:ssl_client_cert => cert})
I've tried putting the password everywhere, as :password and :ssl_client_key, I've looked through all documentation I could find but there's nowhere that will accept this password.
This is the error I always get:
SSL_connect returned=1 errno=0 state=SSLv3 read finished A: sslv3 alert handshake failure
What am I missing?
The way you uses curl with option -E, you are specifying a private key with a certificate.
(from the cURL man page)
-E/--cert
(SSL) Tells curl to use the specified
client certificate file when getting a
file with HTTPS, FTPS or another
SSL-based protocol. The certificate
must be in PEM format. If the optional
password isn't specified, it will be
queried for on the terminal. Note that
this option assumes a "certificate"
file that is the private key and the
private certificate concatenated! See
--cert and --key to specify them independently.
So in order to do the samething with RestClient, you can try using the ssl_client_key option. Like:
:ssl_client_key => OpenSSL::PKey::RSA.new(File.read("key.pem"), "passphrase, if any"),

Resources