I have a custom HTTP server with ruby/socket (just for experimental purposes, no actual use case) and want to try to use SSL/https for the connections. I have tried several solutions with OpenSSL/SSLServer but none of them work.
$HOST = "0.0.0.0"
$PORT = 7777
tcp_server = TCPServer.new($HOST, $PORT)
ctx = OpenSSL::SSL::SSLContext.new
ctx.key = OpenSSL::PKey::RSA.new 2048
ctx.cert = OpenSSL::X509::Certificate.new
ctx.cert.subject = OpenSSL::X509::Name.new [["CN", "localhost"]]
ctx.cert.issuer = ctx.cert.subject
ctx.cert.public_key = ctx.key
ctx.cert.not_before = Time.now
ctx.cert.not_after = Time.now + 60 * 60 * 24
ctx.cert.sign ctx.key, OpenSSL::Digest::SHA1.new
server = OpenSSL::SSL::SSLServer.new tcp_server, ctx
puts "Server listening on #{$HOST}:#{$PORT.to_s}"
loop do
serverThread = Thread.start(server.accept) do |client|
client.print "HTTP/1.1 200\r\n"
client.print "Content-type: text/html\r\n"
client.print "\r\n"
client.print "<h1>Hello World</h1>"
end
end
That code just causes chrome to say connection not private - NET::ERR_CERT_INVALID
Related
I have created a very simple server:
#!/bin/ruby
require 'socket'
server = TCPServer.open 2000
puts "Listening on port 2000"
loop {
client = server.accept
client.puts "HTTP/1.1 200/OK\r\nContent-type:text/html\r\n\r\n"
response = "My super slim ruby http server"
client.puts response
received = client.recv(1024)
puts received
puts "\n"
client.close
}
So far, it serves my purpose, which is to print out the requests that might come from a given client. However, if I use, for example, the following curl command to create a request:
curl -F "data=someData" http://localhost:2000
My ruby server only prints out the HTTP headers, but not the body of the request.
Is there a way to do this?
Looks like you have to call recv again to get the body:
#!/bin/ruby
require 'socket'
server = TCPServer.open 2000
puts "Listening on port 2000"
loop {
client = server.accept
client.puts "HTTP/1.1 200/OK\r\nContent-type:text/html\r\n\r\n"
response = "My super slim ruby http server"
client.puts response
headers = client.recv(1024)
headers =~ /Content-Length: (\d+)/ # get content length
body = $1 ? client.recv($1.to_i) : '' # <- call again to get body if there is one
puts headers + body
client.close
}
Thanks bundacia, I ended up mixing what you sent and some other findings and this is the result:
#!/bin/ruby
require 'socket'
server = TCPServer.open 2000
puts "Listening on port 2000"
loop {
client = server.accept
client.puts "HTTP/1.1 200/OK\r\nContent-type:text/xml\r\n\r\n"
response = "My super slim ruby http server"
client.puts response
all_data = []
i = 1024
firstrun = "yes"
while i > 0
partial_data = client.recv(i)
if (firstrun == "no")
i = 0
end
if (firstrun == "yes")
partial_data =~ /Content-Length: (\d+)/ # get content length
length = $1
if (nil != length && !length.empty?)
i = length.to_i
firstrun = "no"
end
end
all_data << partial_data
end
puts all_data.join()
client.close
}
I've got a SSL Echo Server working fine when I tested with
gnutls-cli --starttls --port 9002 --insecure localhost
My SSL Echo server is as below:
#!/usr/bin/ruby
require 'socket';
require 'openssl';
certfile = 'privkey.pem';
host = "127.0.0.1"
port = 9002;
server = TCPServer.new( host, port );
# Establish an SSL context
sslContext = OpenSSL::SSL::SSLContext.new
sslContext.cert = OpenSSL::X509::Certificate.new(File.open( "myssl.cert.cert" ))
sslContext.key = OpenSSL::PKey::RSA.new(File.open( "myssl.cert.key" ))
# Create SSL server
sslServer = OpenSSL::SSL::SSLServer.new( server, sslContext );
# Don't expect an immidate SSL handshake upon connection.
sslServer.start_immediately = false;
sslSocket = sslServer.accept;
sslSocket.puts( "Toast.." );
# Server loop
while line = sslSocket.gets
line.chomp!;
if "STARTTLS" == line
# Starting TLS
sslSocket.accept;
end
sslSocket.puts( "Got '#{line}'" );
end
sslSocket.close;
My Client is however, not working (which I borrow somewhere in StackOverflow) as below:
#!/usr/bin/env ruby
require "socket"
require "thread"
require "openssl"
host = "127.0.0.1"
port = 9002
socket = TCPSocket.new(host, port)
expectedCert = OpenSSL::X509::Certificate.new(File.open("myssl.cert.cert"))
ssl = OpenSSL::SSL::SSLSocket.new(socket)
ssl.sync_close = true
ssl.connect
if ssl.peer_cert.to_s != expectedCert.to_s
stderrr.puts "Unexpected certificate"
exit(1)
end
Thread.new {
begin
while lineIn = ssl.gets
lineIn = lineIn.chomp
$stdout.puts lineIn
end
rescue
$stderr.puts "Error in input loop: " + $!
end
}
while (lineOut = $stdin.gets)
lineOut = lineOut.chomp
ssl.puts lineOut
end
Error I've got.
./sslclient.rb:13:in `connect': SSL_connect returned=1 errno=0 state=SSLv2/v3 read server hello A: unknown protocol (OpenSSL::SSL::SSLError)
from ./sslclient.rb:13:in `<main>'
Why is there a error in ssl.connect? Did I miss anything?
On the server side you start with plain text, send a welcome message and expect a starttls command - only then you upgrade to TLS. But on the client side you immediatly try to upgrade to TLS after the TCP connect, e.g. without reading the server hello and without sending the starttls command.
I am having issues using powershell to connect to an ssl socket with a self singed cert. I have a ruby client that connects fine, and I have also tested ncat with the --ssl switch and confirmed it connects just fine. $sslStream.AuthenticateAsClient is where the code fails and I get the following error from powershell "Exception calling "AuthenticateAsClient" with "1" argument(s): "A call to SSPI failed, see inner exception.". Any help is much appreciated.
Powershell Client
$socket = New-Object Net.Sockets.TcpClient('172.26.4.38', 8080)
$stream = $socket.GetStream()
$sslStream = New-Object System.Net.Security.SslStream($stream,$false,({$True} -as [Net.Security.RemoteCertificateValidationCallback]))
$sslStream.AuthenticateAsClient('172.26.4.38')
$writer = new-object System.IO.StreamWriter($sslStream)
$writer.WriteLine('hello world')
$writer.flush()
$socket.close()
Ruby Server
#!/usr/bin/env ruby
require 'openssl'
require 'socket'
tcp_server = TCPServer.new('172.26.4.38', 8080)
ctx = OpenSSL::SSL::SSLContext.new
ctx.ssl_version = :SSLv23
ctx.key = OpenSSL::PKey::RSA.new 2048
ctx.cert = OpenSSL::X509::Certificate.new
ctx.cert.subject = OpenSSL::X509::Name.new [['CN', '172.26.4.38']]
ctx.cert.issuer = ctx.cert.subject
ctx.cert.public_key = ctx.key
ctx.cert.not_before = Time.now
ctx.cert.not_after = Time.now + 60 * 60 * 24
ctx.cert.sign ctx.key, OpenSSL::Digest::SHA1.new
server = OpenSSL::SSL::SSLServer.new tcp_server, ctx
socket = server.accept
puts 'client connected'
puts socket.gets
This was an issue with the ruby code creating a self-sign cert. Using openssl and openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout server.key -out server.crt the following powershell code worked correctly.
Client
$socket = New-Object Net.Sockets.TcpClient('172.26.4.38', 8080)
$stream = $socket.GetStream()
$sslStream = New-Object System.Net.Security.SslStream($stream,$false,({$True} -as [Net.Security.RemoteCertificateValidationCallback]))
$sslStream.AuthenticateAsClient('172.26.4.38')
$writer = new-object System. IO.StreamWriter($sslStream)
$writer.WriteLine('Hello World')
$writer.flush()
$socket.close()
Server
#!/usr/bin/env ruby
require 'openssl'
require 'socket'
tcp_server = TCPServer.new('172.26.4.38', 8080)
ctx = OpenSSL::SSL::SSLContext.new
ctx.cert = OpenSSL::X509::Certificate.new(File.open('server.crt'))
ctx.key = OpenSSL::PKey::RSA.new(File.open('server.key'))
server = OpenSSL::SSL::SSLServer.new tcp_server, ctx
socket = server.accept
puts 'client connected'
puts socket.gets
You can get powershell to ignore any cert issue by forcing the callback to just return true no matter what.
[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
beaware that this will bypass any ssl validation in the powershell session.
I need to establish a TCP connection with my server which has a SSL enabled port, that I need to access.
I need to send a XML file and get the response from the server.
Before the SSL was enabled, I was able to get the data from the server using the below mentioned code.
require 'socket'
myXML = 'test_xml'
host = 'myhost.com'
port = 12482
socket = TCPSocket.open(host,port) # Connect to server
socket.send(myXML, 0)
response = socket.recvfrom(port)
puts response
socket.close
Now I have a 'certi.pfx' with which I need to establish a connection, Send my_xml data and get the response. How can this be done.
I would also like to know if I have the 'pem' and 'key' file, how can I establish a connection, Send my_xml data and get the response.
Please help.
require 'socket'
require 'openssl'
myXML = 'my_sample_data'
host = 'my_host.com'
port = my_port
socket = TCPSocket.open(host,port)
ssl_context = OpenSSL::SSL::SSLContext.new()
ssl_context.cert = OpenSSL::X509::Certificate.new(File.open("certificate.crt"))
ssl_context.key = OpenSSL::PKey::RSA.new(File.open("certificate.key"))
ssl_context.ssl_version = :SSLv23
ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
ssl_socket.sync_close = true
ssl_socket.connect
ssl_socket.puts(myXML)
while line = ssl_socket.gets
p line
end
ssl_socket.close
Like this:
sock = TCPSocket.new('hostname', 443)
ctx = OpenSSL::SSL::SSLContext.new
ctx.set_params(verify_mode: OpenSSL::SSL::VERIFY_PEER)
#socket = OpenSSL::SSL::SSLSocket.new(sock, ctx).tap do |socket|
socket.sync_close = true
socket.connect
end
I'm trying to use built-in XMLRPC in Ruby 1.9. Unfortunately, XMLRPC is not documented on ruby-docs.org, so i have tried to build a test code based on examples found by google:
# Server.rb
require "xmlrpc/server"
server = XMLRPC::Server.new( 1234 )
server.add_handler( "test" ) { |msg| return "responce for #{msg}" }
server.serve()
# Client.rb
require "xmlrpc/client"
server = XMLRPC::Client.new( "localhost", "/", 1234 )
server.call( "test", 42 ) == "responce for 42"
Unfortunately, this is not working on both Windows and OSX. Server.rb fails with cryptic error:
C:/Ruby193/lib/ruby/1.9.1/xmlrpc/client.rb:414:in `call': Uncaught exception unexpected return in method test (XMLRPC::FaultException)
from client.rb:6:in `<main>'
Maybe anyone knows what is my error?
Its another way with block:
#server.rb:
require "xmlrpc/server"
server = XMLRPC::Server.new( 1234 )
server.add_handler('my_test.test') { |msg|"responce for #{msg}" }
#client.rb
require "xmlrpc/client"
client = XMLRPC::Client.new( "localhost", "/", 1234 )
s = client.call('my_test.test','asd')
You got it almost right. Here is a tutorial you can use. Your example needs a little modification, you have to pass an object to add_handler that will be used to serve your RPC calls:
# server.rb
require "xmlrpc/server"
class MyClass
def dosomething(a)
"response for #{a}"
end
end
server = XMLRPC::Server.new( 1234 )
server.add_handler( "test", MyClass.new )
server.serve
# client.rb
require "xmlrpc/client"
server = XMLRPC::Client.new( "localhost", "/", 1234 )
puts server.call( "test.dosomething", 42 ) == "response for 42"
Please note: the default xmlrpc/client.rb impl. doesn't support client certificates based https connections. If you want it you must either use different lib or patch client.rb with something like:
# HG changeset patch
# User Anonymous Coward <anonymous#gmail.com>
# Date 1338149770 -10800
# Node ID f0557306c8e4f113507fb3bab8567391949fa302
# Parent 3eae8e8f9e065ff6cdf1c95092ad5cca635c9eac
patch client.rb to support https with client certificate.
diff -r 3eae8e8f9e06 -r f0557306c8e4 client.rb
--- a/client.rb Sun May 27 22:20:18 2012 +0300
+++ b/client.rb Sun May 27 23:16:10 2012 +0300
## -292,8 +292,8 ##
# Constructors -------------------------------------------------------------------
- def initialize(host=nil, path=nil, port=nil, proxy_host=nil, proxy_port=nil,
- user=nil, password=nil, use_ssl=nil, timeout=nil)
+ def initialize(host=nil, path=nil, port=nil, proxy_host=nil, proxy_port=nil,
+ user=nil, password=nil, use_ssl=nil, timeout=nil, cacert=nil, cert=nil, key=nil)
#http_header_extra = nil
#http_last_response = nil
## -311,6 +311,10 ##
if use_ssl
require "net/https"
#port = port || 443
+ #cacert = cacert
+ #cert = cert
+ #key = key
+
else
#port = port || 80
end
## -325,8 +329,19 ##
# HTTP object for synchronous calls
Net::HTTP.version_1_2
- #http = Net::HTTP.new(#host, #port, #proxy_host, #proxy_port)
- #http.use_ssl = #use_ssl if #use_ssl
+ #http = Net::HTTP.new(#host, #port, #proxy_host, #proxy_port)
+ if #use_ssl
+ #http.use_ssl = #use_ssl
+ if nil != #cacert
+ #http.ca_file = #cacert
+ #http.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ #http.verify_depth = 5
+ else
+ #http.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ end
+ #http.cert = #cert
+ #http.key = #key
+ end
#http.read_timeout = #timeout
#http.open_timeout = #timeout
## -366,7 +381,7 ##
hash.each { |k,v| h[k.to_s.downcase] = v }
self.new(h['host'], h['path'], h['port'], h['proxy_host'], h['proxy_port'], h['user'], h['password'],
I think this could help:
http://www.ntecs.de/ruby/xmlrpc4r/howto.html
#server.rb
require "xmlrpc/server"
server = XMLRPC::Server.new( 1234 )
class MyHandler
def test(msg)
"message #{msg}"
end
end
server.add_handler(XMLRPC::iPIMethods("my_test"), MyHandler.new)
server.serve
#client.rb
require "xmlrpc/client"
server = XMLRPC::Client.new( "localhost", "/", 1234 )
s = server.call('my_test.test','hello!')