Problem
- client authentication in Service Oriented Architecture (SOA)
Solution
Encrypted communication is a must these days, it(TLS) also could be used for client auth.
TLS
Transport Layer Security and its predecessor, Secure Sockets Layer (SSL), are cryptographic protocols which are designed to provide communication security over the Internet.
Authentication
Authentication (from Greek: αὐθεντικός; real or genuine, from αὐθέντης authentes; author) is the act of confirming the truth of an attribute of a datum or entity
TLS authentication is achieved by certificate verification peers provide.
Public Key Certificate
In cryptography, a public key certificate (also known as a digital certificate or identity certificate) is an electronic document that uses a digital signature to bind a public key with an identity — information such as the name of a person or an organization, their address, and so forth. The certificate can be used to verify that a public key belongs to an individual.
The most important part here is that certificate includes an identity; therefore it, just like username, could be used to identify clients.
Bidirectional Authentication
- client verifies server authenticity (typical client-server interaction)
- server verifies client’s authenticity (topic of this post)
Server verifies client’s authenticity
- server acts as a CA (Certificate Authority)
- client provides certificate issued by the CA
- server(as the CA) verifies the certificates
- rejects if invalid
- accepts otherwise
- extracts client info from the cert
- identifies client
Motivation
- available examples on internet aren’t working examples
- wasted too much time trying to get things going
- everything required is here
- all ruby, single file
- applies to other configurations with NGINX, other servers
IMPORTANT
server.crt
in this example is used just for demonstration purposes. Don’t use it production. Get a valid one from a trusted CA- rule: server should use certificates provided by CA your clients use to verify with!
Code
Full source with most up to date certificates and code can be found in gist
require 'socket'
require 'openssl'
Thread.abort_on_exception = true
def client_name(cn)
cn.split('/').map {|l| l.split('=') }.assoc('CN').last
end
def start_server(ca_file)
socket = TCPServer.new('127.0.0.1', 4433)
ssl_context = OpenSSL::SSL::SSLContext.new()
ssl_context.cert = OpenSSL::X509::Certificate.new(File.open("ssl/server.crt"))
ssl_context.key = OpenSSL::PKey::RSA.new(File.open("ssl/server.key"))
ssl_context.ca_file = ca_file
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
ca_cert = OpenSSL::X509::Certificate.new(File.open(ca_file))
ssl_socket = OpenSSL::SSL::SSLServer.new(socket, ssl_context)
Thread.new do
loop do
begin
Thread.start(ssl_socket.accept) do |s|
puts "[Server] connection from #{s.peeraddr.last}"
# get subject from peer certificate
subj = s.peer_cert.subject.to_s
s.puts "[Server] hi #{client_name(subj)}"
# manual certificate verification
if s.peer_cert.verify(ca_cert.public_key)
s.puts "[Server] client certificate verified"
else
s.puts "[Server] client certificate invalid"
end
s.close
end
rescue => e
puts "[Server] ERROR #{e.message}"
end
end
end
end
def ssl_client(crt_file, key_file)
socket = TCPSocket.new('127.0.0.1', 4433)
cssl_context = OpenSSL::SSL::SSLContext.new
cssl_context.cert = OpenSSL::X509::Certificate.new(File.open(crt_file))
cssl_context.key = OpenSSL::PKey::RSA.new(File.open(key_file))
# cssl_context.client_ca = OpenSSL::X509::Certificate.new(File.open("ssl/ca.crt"))
# verify server certificate aswell
cssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
cssl_context.ca_file = 'ssl/ca.crt'
ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, cssl_context)
end
def connect(client)
begin
client.connect
puts client.readlines
rescue => e
puts "[Client] ERROR #{e.message}"
end
end
if __FILE__ == $0
# start server with given CA
start_server('ssl/ca.crt')
# CA2 is not known to the server
# client1.crt used ca.crt as CA, so server recognizes this one
connect(ssl_client('ssl/client1.crt', 'ssl/client1.key'))
# server rejects this one as ca2 CA was used to get the client2.crt ceritifate
# which is not known to server as it's configured with ca.crt
connect(ssl_client('ssl/client2.crt', 'ssl/client2.key'))
end
Running example
git clone https://gist.github.com/e9370b96543d06d168e5.git TLSSOA
cd TLSSOA
patch -p1 < z-example-ssl-stuff.patch
ruby client_server.rb
should yiled something like:
[Server] connection from 127.0.0.1
[Server] hi client1.fqdn
[Server] client certificate verified
[Server] ERROR SSL_accept returned=1 errno=0 state=SSLv3 read client certificate B: no certificate returned
[Client] ERROR SSL_connect returned=1 errno=0 state=SSLv3 read finished A: tlsv1 alert unknown ca
which shows that
- server recognized client1 and took name (client1.fqdn) from the certificate provided
- server rejceted client2 as the client used certificate signed by unknown to the server root certificate ca2
If you change server to use ssl/ca2.crt
instead it’ll be be able to validate client2 rejecting client1