Using a simple private Public Key Infrastructure (PKI), plain TCP sockets can be secured with TLS. This can be demonstrated with trivial examples that run locally on a loopback device.
A simple echo TCP socket server that listens on port 7000 can be created with
socat
.
$ socat PIPE TCP-LISTEN:7000,fork
When data is sent to the socket, it will echo it back. The ncat
tool from
nmap
is used because it has TLS features that will be needed later.
$ echo "hello" | ncat localhost 7000
hello
The socket listening on port 7000 won't actually be secured, sorry, instead a new socket listening on port 6000 will provide a secure TLS tunnel/proxy.
The tunnel is created with stunnel
. It's configured with local file called
stunnel.conf
which contains a section for the tunnel.
[service1]
accept = 6000
connect = 7000
CAfile = ca.crt
cert = service1.crt
key = service1.key
verify = 0
The tunnel is called service1
, it listens on port 6000 and creates a tunnel
to port 7000. The CA and service1
certificates were created by the PKI. The
verify
option is set to 0 meaning that peer (client) certificates will not be
required or verified.
The tunnel is started by running stunnel
with the path to the configuration
file.
$ stunnel service1.conf
Trying to connect port 6000 like before won't work.
$ echo "hello" | ncat localhost 6000
Ncat: Connection reset by peer.
The server is expecting the client to start a TLS negotiation. Instead, the
characters "hello" are being sent, so the server disconnects. The error
logged by stunnel
is "SSL routines::wrong version number" which
isn't particularly helpful.
Fortunately ncat
supports TLS/SSL and it be used with the --ssl
option.
$ echo "hello" | ncat --ssl localhost 6000
hello
A response is now returned through an encrypted TLS connection.
If this connection was made over an insecure network, there is no certainty
that the server being connected to is really service1
. With TLS, certificates
used by 'service1' can be verified to have been signed by a specific CA and
belonging to service1.
To do this with netcat
the --ssl-verify
option tells ncat
to check the
server certificate. The certificate should be signed by a specific CA, so
ca.crt
needs to be specified with the --ssl-trustfile
option. The name in
the certificate will also be validated, so service1
needs to specified with
the --ssl-servername
option.
$ echo "hello" | ncat --ssl --ssl-verify --ssl-servername service1 --ssl-trustfile ca_public_key.pem localhost 6000
hello
Expecting a different server name should result in a certificate error.
echo "hello" | ncat --ssl --ssl-verify --ssl-servername service2 --ssl-trustfile ca_public_key.pem localhost 6000
Ncat: Certificate verification error. QUITTING.
A really useful tool when debugging TLS connections is s_client
the openssl
test client.
openssl s_client -servername service1 -connect localhost:6000 -CAfile ca.crt
This will attempt to negotiate a connection and output a heaps of useful information about the certificate, the signatures, the alorithms used, the validation results and more.
TLS also supports a "peer authentication" mode in which the server checks the client certificates. Servers, or tunnels, can be configured to only allow clients with certificates signed by a specific CA to connect.
A secure tunnel configuration that uses strict peer authentication can be
added to the stunnel.conf
file.
[service1-secure]
accept = 5000
connect = 7000
CAfile = ca.crt
cert = service1.crt
key = service1.key
verify = 2
This tunnel is configuration is similar to the previous one except it'll listen
on a different port (5000) and verify
is set to 2, meaning it will require
client certificates and will validate they are signed by the CA (ca.crt
)
After restarting the stunnel
process with the updated configuration,
connecting as before to the new tunnel on port 5000 does not work.
$ echo "hello" | ncat --ssl --ssl-verify --ssl-servername service1 --ssl-trustfile ca.crt localhost 5000
This time there is no response, this is because no client certificates are
being sent. The error logged by stunnel
is "peer did
not return a certificate".
To connect to this service, a client certificate pair signed by the CA is
required. The process to generate a certificate pair for the client is the same
process used to create the service1
certificate pair, but this certificate
pair will be called client1
.
SUBJECT="/C=DE/ST=Berlin/L=Berlin/O=tarnbarford.net/OU=tarnbarford.net/CN=client1"
openssl ecparam -genkey -name prime256v1 -noout -out client1.key
openssl req -new -key client1.key -subj $SUBJECT -out client1.csr
openssl x509 -req -days 365 -sha256 -in client1.csr -CA ca.crt -CAkey ca.key -set_serial 1 -out client1.crt
The will generate a private key client1.key
and a public key
client1.crt
.
To configure ncat
to use the client certificates,
client1.key
is provided with the --ssl-key
option and
client1.crt
is provided with the --ssl-cert
option.
$ echo "hello" | ncat --ssl --ssl-verify --ssl-servername service1 --ssl-trustfile ca.crt --ssl-cert client1.crt --ssl-key client1.key localhost 5000
hello
A response is now returned, but only if the client and server certificates are signed by the CA.