How to validate SSL certificate chain in ruby with net/http - ruby

How can I verify the certificates of a site like https://processing.ukash.com/ in ruby with net/http?
https = Net::HTTP.new('processing.ukash.com', 443)
https.use_ssl = true
https.verify_mode = OpenSSL::SSL::VERIFY_NONE
Works so far, but how do I verify that it's the right cert now? I saved the certificate from within firefox, but the resulting .pem file has many certificates in it and net/http doesn't seem to like it.

From my code snippets collection:
#!/usr/bin/env ruby
# How to:
# =======
# Use Ruby's net/https library, to verify a SSL certificate.
# ==========================================================
# - Without verification the following code will lead to:
# warning: peer certificate won't be verified in this SSL session
#
# #------------------begin example-----------------------------
# require 'net/http'
# require 'net/https'
# require 'uri'
#
# url = URI.parse 'https://myname:mypass#mail.google.com/'
# http = Net::HTTP.new(url.host, url.port)
# http.use_ssl = (url.scheme == 'https')
# request = Net::HTTP::Get.new(url.path)
# request.basic_auth url.user, url.password
# response = http.request(request)
# #-------------------end example------------------------------
#
# To verify the ssl cert cosider adapting the following.
# Status: Untested
# =======
#
# References:
# ===========
# [1] http://mimori.org/%7Eh/tdiary/20080301.html#p03
# [2] http://redcorundum.blogspot.com/2008/03/ssl-certificates-and-nethttps.html
#
require 'net/http'
require 'net/https'
require 'uri'
RootCA = '/etc/ssl/certs'
url = URI.parse 'https://myname:mypass#mail.google.com/'
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = (url.scheme == 'https')
if (File.directory?(RootCA) && http.use_ssl?)
http.ca_path = RootCA
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
http.verify_depth = 5
else
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
request = Net::HTTP::Get.new(url.path)
request.basic_auth url.user, url.password
response = http.request(request)
Hope this helps?

For the sake of completeness and for my future me, this is a small gem I made out of this: https://github.com/jarthod/ssl-test

Related

OpenSSL::SSL::SSLError: Ruby client's server ca certificate does not work while it worked with curl

I got certificate from customer to connect with their VPN, but it does not work with ruby code while it worked with curl command. Curl command is as follows:
curl --cacert cert.cer -d '{"acb": 123 }' -H 'Content-Type: application/json' 'https://demo.com'
In ruby, I am trying to do the following to connect the client APIs provided to us for transactions.
require 'net/http'
require 'json'
require 'uri'
full_url = "https://demo.com"
uri = URI.parse(full_url)
data = { "acb": 123 }
headers = { 'Content-Type' => "application/json" }
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
raw = File.read("path_to_the_certificate")
http.cert = OpenSSL::X509::Certificate.new(raw)
request = Net::HTTP::Post.new(uri.request_uri, headers)
request.body = data.to_json
response = http.request(request)
puts response.code
puts response.body
We also tried to pass our server's certificate as follows, but that doesn't work either
http.ca_path='/etc/pki/tls/certs'
http.ca_file='/etc/pki/tls/certs/cert.cer'
http.cert = OpenSSL::X509::Certificate.new(File.read("/path/client.crt"))
http.key = OpenSSL::PKey::RSA.new(File.read("/path/client.key"))
Getting the following error while
OpenSSL::SSL::SSLError (SSL_connect returned=1 errno=0 state=error: certificate verify failed (unable to get local issuer certificate))
I think the issue with their self-signed certificate. It fails verification.
However, you can manually disable it with
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
verify_mode[RW]
Sets the flags for server the certification verification at beginning of SSL/TLS session.
OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER are acceptable.
from https://ruby-doc.org/stdlib-2.7.0/libdoc/net/http/rdoc/Net/HTTP.html
I tried to replicate it locally and it worked with this fix.
It should be that the vpn certificate is self-signed, you need to specify your own cacert, so you specify cacert file as the file used by the curl above, not the cacert file that comes with the system
add this line:
http.ca_file = "cacert filename"
Like this:
require 'net/http'
require 'json'
require 'uri'
full_url = "https://localhost/test.html"
uri = URI.parse(full_url)
data = { "acb": 123 }
headers = { 'Content-Type' => "application/json" }
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
# You need to specify the cacert file used for curl above (filename: cert.cer)
http.ca_file = "/root/myca/cacert.crt"
request = Net::HTTP::Post.new(uri.request_uri, headers)
request.body = data.to_json
response = http.request(request)
puts response.code
puts response.body
you should add your certificate in .pem format to (depending on the version) either:
C:\Ruby{version number}{-x64 - if 64 bit operating system}\ssl
e.g. C:\Ruby25-x64\ssl
or to
C:\Ruby{version number}{-x64 - if 64 bit operating system}\lib\ruby{version number}\rubygems\ssl_certs{your cn}
e.g. C:\Ruby25-x64\lib\ruby\2.5.0\rubygems\ssl_certs\client.cn
and then in the C:\Ruby{The version number}{-x64 - If it's a 64-bit operating system}\ssl\certs run the c_rehash.r script
For Apps using PayPal::SDK.configure(...)
PayPal::SDK.configure(
mode: ...,
client_id: ...,
client_secret: ...,
# Deliberately set ca_file to nil so the system's Cert Authority is used,
# instead of the bundled paypal.crt file which is out-of-date due to:
# https://www.paypal.com/va/smarthelp/article/discontinue-use-of-verisign-g5-root-certificates-ts2240
ssl_options: { ca_file: nil }
)
For apps using a YAML config file
ssl_options:
ca_file: null

Post png image to pngcrush with Ruby

In ruby, I want to get the same result than the code below but without using curl:
curl_output = `curl -X POST -s --form "input=##{png_image_file};type=image/png" http://pngcrush.com/crush > #{compressed_png_file}`
I tried this:
#!/usr/bin/env ruby
require "net/http"
require "uri"
# Image to crush
png_image_path = "./media/images/foo.png"
# Crush with http://pngcrush.com/
png_compress_uri = URI.parse("http://pngcrush.com/crush")
png_image_data = File.read(png_image_path)
req = Net::HTTP.new(png_compress_uri.host, png_compress_uri.port)
headers = {"Content-Type" => "image/png" }
response = req.post(png_compress_uri.path, png_image_data, headers)
p response.body
# => "Input is empty, provide a PNG image."
The problem with your code is you do not send required parameter to the server ("input" for http://pngcrush.com/crush). This works for me:
require 'net/http'
require 'uri'
uri = URI.parse('http://pngcrush.com/crush')
form_data = [
['input', File.open('filename.png')]
]
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Post.new uri
# prepare request parameters
request.set_form(form_data, 'multipart/form-data')
response = http.request(request)
# save crushed image
open('crushed.png', 'wb') do |file|
file.write(response.body)
end
But I suggest you to use RestClient. It encapsulates net/http with cool features like multipart form data and you need just a few lines of code to do the job:
require 'rest_client'
resp = RestClient.post('http://pngcrush.com/crush',
:input => File.new('filename.png'))
# save crushed image
open('crushed.png', 'wb') do |file|
file.write(resp)
end
Install it with gem install rest-client

REST HTTPS login with Ruby - Connection Reset by peer

I am trying to do a REST API login over HTTPS. I keep getting Connection reset by peer (Errno::ECONNRESET):
require 'net/http'
require 'net/https'
uri = URI.parse("https://.../login.jsp")
https = Net::HTTP.new(uri.host,uri.port)
https.use_ssl = true
req = Net::HTTP::Post.new(uri.path)
req['userid'] = 'myemail%40hostname.com'
req['passwd'] = 'mypassword'
res = https.request(req)
puts res.body
I'm not sure what I'm doing wrong. The corresponding cURL command that works without a hitch is:
curl --user-agent "MyUserAgent" --cookie-jar cookiefile -o - --data 'userid=myemail%40hostname.com&passwd=mypassword' https://.../login.jsp
(I haven't gotten to dealing with capturing the cookie file yet, I'm just trying to get the login to work)
Suggestions appreciated! Thanks!!
Turned out that I needed to set form data for this work.
require 'net/http'
require 'openssl'
uri = URI.parse("https://.../login.jsp")
https = Net::HTTP.new(uri.host,uri.port)
https.use_ssl = true
req = Net::HTTP::Post.new(uri)
req.set_form_data('userid' => 'myemail#hostname.com', 'passwd' => 'mypassword')
res = https.request(req)
puts res

Ruby Net::HTTPS SSL handshake error

I have the following code
uri = URI.parse( 'https://my.fancy.uri/with/some/path.ext' )
https = Net::HTTP.new( uri.host, uri.port )
https.use_ssl = true
https.verify_mode = OpenSSL::SSL::VERIFY_PEER
store = OpenSSL::X509::Store.new
store.add_file( File.join( Rails.root, 'config', 'crt', 'correct_ca.crt' ) )
https.cert_store = store
request = Net::HTTP::Post.new( uri.request_uri )
request.set_form_data post_data
request.basic_auth( 'HTTP_USER', 'HTTP_PASS' )
response = https.request( request )
which generates an error: OpenSSL::SSL::SSLError: SSL_read:: ssl handshake failure
and I'm totally stumped.
Apparently Ruby wasn't checking all ssl versions durring the handshake. I don't know much about it, but adding:
https.ssl_version = :TLSv1
solved the problem.

How to do basic authentication over HTTPs in Ruby?

After looking a lot, I've found some solutions that seem working, but not for me...
For example, I have this script:
require 'net/http'
require "net/https"
#http=Net::HTTP.new('www.xxxxxxx.net', 443)
#http.use_ssl = true
#http.verify_mode = OpenSSL::SSL::VERIFY_NONE
#http.start() {|http|
req = Net::HTTP::Get.new('/gb/PastSetupsXLS.asp?SR=31,6')
req.basic_auth 'my_user', 'my_password'
response = http.request(req)
print response.body
}
When I run it, it gives me a page that requests for authentication, but if I write the following URL in the browser, I get into the website without problems:
https://my_user:my_password#www.xxxxxxx.net/gb/PastSetupsXLS.asp?SR=31,6
I have also tried with open-uri:
module OpenSSL
module SSL
remove_const :VERIFY_PEER
end
end
OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE
def download(full_url, to_here)
writeOut = open(to_here, "wb")
writeOut.write(open(full_url, :http_basic_authentication=>["my_user", "my_password"]).read)
writeOut.close
end
download('https://www.xxxxxxx.net/gb/PastSetupsXLS.asp?SR=31,6', "target_file.html")
But the result is the same, the site is asking for user authentication.
Any tips of what am I doing wrong?. Must I encode the password in Base 64?
I wrote a piece of code based on examples given in the Net::HTTP docs and tested it on my local WAMP server - it works fine. Here's what I have:
require 'net/http'
require 'openssl'
uri = URI('https://localhost/')
Net::HTTP.start(uri.host, uri.port,
:use_ssl => uri.scheme == 'https',
:verify_mode => OpenSSL::SSL::VERIFY_NONE) do |http|
request = Net::HTTP::Get.new uri.request_uri
request.basic_auth 'matt', 'secret'
response = http.request request # Net::HTTPResponse object
puts response
puts response.body
end
And my .htaccess file looks like this:
AuthName "Authorization required"
AuthUserFile c:/wamp/www/ssl/.htpasswd
AuthType basic
Require valid-user
My .htpasswd is just a one liner generated with htpasswd -c .htpasswd matt for password "secret". When I run my code I get "200 OK" and contents of index.html. If I remove the request.basic_auth line, I get 401 error.
UPDATE:
As indicated by #stereoscott in the comments, the :verify_mode value I used in the example (OpenSSL::SSL::VERIFY_NONE) is not safe for production.
All available options listed in the OpenSSL::SSL::SSLContext docs are: VERIFY_NONE, VERIFY_PEER, VERIFY_CLIENT_ONCE, VERIFY_FAIL_IF_NO_PEER_CERT, out of which (according to the OpenSSL docs) only the first two ones are used in the client mode.
So VERIFY_PEER should be used on production, which is the default btw, so you can skip it entirely.
The following is what ended up working for me:
require "uri"
require "net/http"
url = URI("https://localhost/")
https = Net::HTTP.new(url.host, url.port)
https.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = "Basic " + Base64::encode64("my_user:my_password")
response = https.request(request)
puts response.read_body
I came up with this by building a new HTTP Request in Postman, specifying the URL, choosing an Authorization Type of "Basic Auth," and inputting the credentials.
Clicking the Code icon (</>) and selecting "Ruby - Net::HTTP" will then generate a code snippet, giving you the output above.
Postman took care of encoding the credentials, but this answer helped me to dynamically set these values. You also can likely omit the "cookie" key as part of the request.

Resources