Socksify and SSL for HTTPS request - ruby

For a project I need to make an HTTPS call through a Socks5 proxy so I can access the remote API of a customer.
The original code I'm using is this:
url = "/proxyValidate?service=#{migration_station_verification_url}&ticket=#{service_ticket}"
if Rails.env == 'development'
uri = URI.parse(soe_cas_url + url)
Net::HTTP.SOCKSProxy('127.0.0.1', 1080).start(uri.host, uri.port) do |http|
res = http.get(uri.path)
doc = Nokogiri::XML(res)
doc.remove_namespaces!
doc.xpath('//sessionId').first.content
end
else
http = Net::HTTP.new(soe_cas_url)
http.use_ssl = true
req = Net::HTTP::Get.new(url)
res = http.request(req)
doc = Nokogiri::XML(res)
doc.remove_namespaces!
doc.xpath('//sessionId').first.content
end
The goal is to use the proxy only in development environment. When I make the call using the proxy I receive the error:
wrong status line: "<!DOCTYPE
Googling this shows I need to set the use_ssl flag to true, which works outside the development cycle. When using the socksify there is no such mechanism. If I call this inside the do block, I receive an error that the session is already started. Calling it outside the block returns an exception that no such function exists.
Anyone can tell me how to run SSL/HTTPS through the socksify proxy?
EDIT:
I'm currently playing around with pure sockets to get what I want, using the following code:
socket = Socket::TCPSocket.open('127.0.0.1', 1080)
ssl_context = OpenSSL::SSL::SSLContext.new
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
ssl_context.ca_file = File.join(Rails.root, 'ssl', 'cacert.pem')
ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
ssl_socket.sync_close = true
ssl_socket.connect
request = "GET #{url} HTTP/1.1\r\nAccept: */*\r\nHost: #{soe_cas_url}"
ssl_socket.puts(request)
ssl_socket.puts("")
response = ""
while (line = ssl_socket.gets)
response << line
end
This however fails at the ssl_socket.connect with the following error:
ssl_connect syscall returned=5 errno=0 state=sslv2/v3 read server hello a
I've tried using self-signed certificates, no certificates etc but nothing seems to work. I do know the endpoint relies on self-signed certificates on staging environmnet.

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

ADAL Proxy Error while connecting through Rake Task

I am following this website for connecting to Microsoft Graph to get the access token using ADAL gem.
I have written a Rake task to perform this. This works fine in the open network but fails in the Corporate Network.
I have set the proxy credentials in the bash profile and in the bashrc file in my terminal
export http_proxy=http://username:pwd#proxy.corporate.com:8080/
export HTTP_PROXY=http://username:pwd#proxy.corporate.com:8080/
export https_proxy=http://username:pwd#proxy.corporate.com:8080/
export HTTPS_PROXY=http://username:pwd#proxy.corporate.com:8080/
I am receiving
SocketError: Failed to open TCP connection to login.microsoftonline.com:443 (Hostname not known: login.microsoftonline.com)
Solution by OP.
Issue has been resolved after tweeking the user_credential.rb file in ADAL gem
File to edit: /azure-activedirectory-library-for-ruby-24e1b7f0dc37/lib/adal/user_credential.rb
def realm_discovery_response
#realm_discovery_response ||=
JSON.parse(Net::HTTP.get(realm_discovery_uri))
end
Replace the above code with the below as Net::HTTP.get doesnot take the system proxy use and only Net::HTTP.Get.new works
def realm_discovery_response
uri = URI.parse(realm_discovery_uri.to_s)
puts "URI #{uri}"
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true if uri.scheme == 'https'
req = Net::HTTP::Get.new(uri.request_uri)
res = http.request(req)
#realm_discovery_response ||= JSON.parse(res.body)
end

Net::HTTP not sending request

I have the following method in our Ruby 1.8.7 project:
def self.ping_server
request_data = get_request_data
uri = 'another_server.our_company.com'
https = Net::HTTP.new(uri)
https.use_ssl = true
path = "/our_service"
data = request_data.to_json
response = https.post(path, data, {'Content-Type' => 'application/json'})
return response
end
Whenever I run this method, I get the following time out error:
Completed 500 Internal Server Error in 128936ms
Errno::ETIMEDOUT (Connection timed out - connect(2)):
lib/my_class.rb:51:in `ping_our_server'
I checked with a coworker who has access to the logs for another_server.our_company.com. My request isn't arriving at the other server.
What should I do to get my request to work?
EDIT: On further inspection, this is what I think is happening (but I'm not completely sure): Our other server will only accept HTTPS requests, but it looks like my request is being sent over HTTP for some reason. Is there something I need to add to make sure my request is sent over HTTPS?
According to this website, this is how you send an HTTPS request:
require "net/https"
require "uri"
uri = URI.parse("https://secure.com/")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Get.new(uri.request_uri)
response = http.request(request)
response.body
response.status
response["header-here"] # All headers are lowercase
According to this website (which is linked from my first link), you should also do this to close up a vulnerability in the net/https library:
To get going, you need a local CA certificates bundle, the official
curl site maintains an up to date cacert.pem / ca-bundle.crt file
containing all of the major certificates if you need one.
Next, after a gem install always_verify_ssl_certificates, you can be
up and running with a test as simply as:
require 'always_verify_ssl_certificates'
AlwaysVerifySSLCertificates.ca_file = "/path/path/path/cacert.pem"
http= Net::HTTP.new('https://some.ssl.site', 443)
http.use_ssl = true
req = Net::HTTP::Get.new('/')
response = http.request(req)
If the site
has a bad certificate an error will be raised at this point. If not, a
legitimate HTTP response object will be returned.

SSL Error on HTTP POST (Unknown Protocol)

Trying to connect to Imgur API via SSL gives me an error. Here's the code and the error:
API_URI = URI.parse('https://api.imgur.com')
API_PUBLIC_KEY = 'Client-ID --'
ENDPOINTS = {
:image => '/3/image',
:gallery => '/3/gallery'
}
# Public: Upload an image
#
# args - The image path for the image to upload
#
def upload(image_path)
http = Net::HTTP.new(API_URI.host)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
params = {'image' => File.open(image_path)}
request = Net::HTTP::Post.new(API_URI.request_uri)
request.set_form_data(params)
request.add_field('Authorization', API_PUBLIC_KEY)
response = http.request(request)
puts response.body
end
And the error:
`connect': SSL_connect returned=1 errno=0 state=SSLv2/v3 read server hello A: unknown protocol (OpenSSL::SSL::SSLError)
I know VERIFY_NODE is not good practice but I just want to test the connection for now.
Ruby version: 1.9.2
Specifying the port when creating the HTTP client fixed this problem.
http = Net::HTTP.new(API_URI.host, API_URI.port)
or
http = Net::HTTP.new(API_URI.host, 443)
For me it was because I had started the server as a http (tcp://) servers instead of https (ssl://).
i.e.
bundle exec puma config.ru -b 'tcp://0.0.0.0:3456?key=/path/to/key.key&cert=/path/to/cert.crt'
instead of:
bundle exec puma config.ru -b 'ssl://0.0.0.0:3456?key=/path/to/key.key&cert=/path/to/cert.crt'

Ruby - remote file download ...timeout?

Excuse the tabs. I'm trying to download a file from remote to local and I keep getting one back that is exactly 310 bytes (regardless of what file I choose to download). I tried setting the timeout to 0, but this isn't working. What am I doing wrong? Thanks!
#downloadUrl = 'https://username:password#api.net'
Net::HTTP.start(#downloadUrl) do |http|
response = http.get('/file.ext')
open('/Users/me/file.ext', "wb", :read_timeout=>0) do |file|
file.write(response.body)
end
end
EDIT: I don't want to use httpclient, I want to use standard net/http. I am almost there, but I keep getting initialize': getaddrinfo: nodename nor servname provided, or not known (SocketError) thrown at Net::HTTP.start(url.path). But when I remote "https", I get ECONNREFUSED. ...Getting closer?
url = URI.parse('https://api.net/file.ext')
#request = Net::HTTP.start(url.path)
#request.basic_auth 'username', 'password'
sock = Net::HTTP.new(url.host, 443)
sock.use_ssl = true
sock.ssl_version='SSLv3'
sock.start do |http|
response = http.get(#request)
open('/Users/me/file.ext', "wb", :read_timeout=>0) do |file|
file.write(response.body)
end
end
Using httpclient is much simpler when accessing via SSL.
gem install httpclient
I haven't tried this out, but this should work for you. Here is the rdoc.
require 'httpclient'
domain = "https://api.net/file.ext"
username = "username"
password = "password"
clnt = HTTPClient.new
clnt.set_auth(domain, username, password)
res = clnt.get_content(https_url)
You can refer to the "writing a binary file in ruby" question for saving your response to a file.
Updated Using net/http
You should be doing Net::HTTP.start(url.host, url.port) instead of Net:HTTP.start(url.path). See RDoc for Net::HTTP.start.
url = URI.parse("https://api.net/file.ext")
download_file = opne("/Users/me/file.ext", "wb")
request = Net::HTTP.start(url.host, url.port)
# .. set basic auth, verify peer etc
begin
request.request_get(url.path) do |resp|
resp.read_body { |segment| download_file.write(segment) }
end
ensure
download_file.close
end

Resources