TLS auth in SOA

Problem

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

  1. client verifies server authenticity (typical client-server interaction)
  2. server verifies client’s authenticity (topic of this post)

Server verifies client’s authenticity

Motivation

IMPORTANT

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

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

  1. server recognized client1 and took name (client1.fqdn) from the certificate provided
  2. 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

Presentation Slides

References

Comments