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.