Sockets 2 - SSL sockets binary protocol

Content:

  1. Introduction
  2. TCP and socket API
  3. Example protocol
  4. SSL/TLS
  5. Client
  6. Singlethreaded server
  7. Multithreaded server
  8. Asynchroneous server

For other articles in series see:

Introduction:

TCP sockets is the foundation for most network communication today. And even though applications often use higher level API's, then understanding the foundation is beneficial. Both to be able to understand why higher level API's are as they are and to be able to use TCP sockets directly if necessary.

TCP and socket API:

One very important aspect to understand is that the TCP protocol and the socket API are stream oriented.

This mean that sender write chunks of bytes to the stream and receiver read chunks of bytes from the stream, but the receivers chunks can and often are different from the senders chunks.

Example:

This means that receiver can not use chunk sizes read to determine any type of data lengths.

Instead more explict management of data length has to be used:

The current socket API is very old. It goes back to Berkely sockets in BSD Unix in 1983. Since then it has been implemented on most platforms including other Unix, Linux, VMS etc.. Even Windows has a 99% compatible implementation Winsocket.

Example protocol:

The binary protocol used in all examples are very simple:

  1. client connect to server
  2. client send 4 byte integer in network order (big endian)
  3. client send 4 byte integer in network order (big endian)
  4. server send 4 byte integer with sum of clients two integers in network order (big endian)
  5. client send 1 byte with string length in bytes
  6. client send bytes of string
  7. server send 1 byte with duplicated string length in bytes
  8. server send bytes of duplicated string
  9. client and server closes connection

SSL/TLS:

A SSL socket is a socket that encrypts all traffic.

As part of establishing connection a SSL handshake is done that exchange keys to allow all following traffic to be encrypted.

There are actually multiple SSL protocols:

Protocol Year
SSL 2.0 1995
SSL 3.0 1996
TLS 1.0 1999
TLS 1.1 2006
TLS 1.2 2008
TLS 1.3 2018

SSL = Secure Sockets Layer

TLS = Transport Layer Security

Only use TLS 1.2 or newer. Earlier versions have various security vulnerabilities.

The term SSL is usually used to indicate all SSL and TLS protocols with an expectation of newer secure TLS actually being used.

The server examples use a PKCS#12 keystore with a certficate.

Such a keystore with a selfsigned certificate can be created as:

keytool -genkey -keyalg RSA -keysize 2048 -sigalg SHA256withRSA -storetype PKCS12 -keystore sslserver.pfx -dname "Bla bla bla" -alias myserver -storepass topsecret -keypass topsecret

And for OpenSSL usage:

openssl pkcs12 -in sslserver.pfx -out sslserver.pem

Client:

Client logic is:

connect
send/write
receive/read
close

Socket and ServerSocket classes has been in Java since version 1.0. Java also have DataInputStream and DataOutputStream classes for convenient reading and writing binary data.

Note that DataInputStream and DataOutputStream always use big endian.

package socket.ssl.binary.traditional;

import java.io.DataInputStream;
import java.io.DataOutputStream;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.X509TrustManager;

import java.security.cert.X509Certificate;

public class Client {
    public static class MyTrustManager implements X509TrustManager
    {
        public void checkClientTrusted(X509Certificate[] chain, String authType) {
        }
        public void checkServerTrusted(X509Certificate[] chain, String authType) {
            System.out.printf("(server is %s)\n",  chain[0].getIssuerX500Principal().getName());
        }
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
    }
    private static SSLContext setupSSLContext() throws Exception {
        SSLContext sslctx = SSLContext.getInstance("TLSv1.2");
        sslctx.init(null, new X509TrustManager[] { new MyTrustManager() }, null);
        return sslctx;
    }
    private static final String HOST = "localhost";
    private static final int PORT = 12345;
    public static void main(String[] args) {
        try {
            // get SSL context
            SSLContext sslctx = setupSSLContext();
            // open connection
            SSLSocket s = (SSLSocket) sslctx.getSocketFactory().createSocket(HOST, PORT);
            // only use TLS 1.2
            s.setEnabledProtocols(new String[] { "TLSv1.2" });
            // get streams
            DataInputStream dis = new DataInputStream(s.getInputStream());
            DataOutputStream dos = new DataOutputStream(s.getOutputStream());
            System.out.println("(connected)");
            // send a and b
            int a = 123;
            int b = 456;
            dos.writeInt(a);
            dos.writeInt(b);
            dos.flush();
            // receive c and print
            int c = dis.readInt();
            System.out.println(c);
            // send v
            String v = "ABC";
            byte[] vb = v.getBytes("UTF-8");
            dos.writeByte(vb.length);
            dos.write(vb);
            dos.flush();
            // receive v2 and print
            int buflen = dis.readByte();
            byte[] buf = new byte[buflen];
            int ix = 0;
            while(ix < buf.length) {
                ix += dis.read(buf, ix, buf.length - ix);
            }
            String v2 = new String(buf, "UTF-8");
            System.out.println(v2);
            // close connection
            dis.close();
            dos.close();
            s.close();
        } catch (Exception e) {
            // TODO
            e.printStackTrace();
        }
    }
}

Note that this code accept all server certficates. This may not be wise from a security perspective.

Java 1.4 added several new IO features called NIO including a SocketChannel. It is not that much of an improvement for client except that is supports both little and big endian.

It is possible to do SSL with NIO by using the SSLEngine class. But it requires some code to do the SSL handshake. It is strongly recommended to use an existing library instead of writing the code yourself. And therefor no code example is provides.

.NET provide two levels of socket API:

Only the high level API is easy to use for SSL.

using System;
using System.Security.Cryptography.X509Certificates;
using System.IO;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Text;

namespace SocketDemo.Binary.SecureHigh.Client
{
    public class Program
    {
        private static int HTON(int v)
        {
            if(BitConverter.IsLittleEndian)
            {
                byte[] buf = BitConverter.GetBytes(v);
                Array.Reverse(buf);
                return BitConverter.ToInt32(buf, 0);
            }
            else
            {
                return v;
            }
        }
        private static int NTOH(int v)
        {
            if(BitConverter.IsLittleEndian)
            {
                byte[] buf = BitConverter.GetBytes(v);
                Array.Reverse(buf);
                return BitConverter.ToInt32(buf, 0);
            }
            else
            {
                return v;
            }
        }
        private static bool CertCheck(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            Console.WriteLine("(server is {0})", certificate.Subject);
            return true;
        }
        private const string HOST = "localhost";
        private const int PORT = 12345;
        public static void Main(string[] args)
        {
            // open connection
            TcpClient cli = new TcpClient(HOST, PORT);
            // get reader and writer
            NetworkStream stm = cli.GetStream();
            SslStream sslstm = new SslStream(stm, false, CertCheck);
            sslstm.AuthenticateAsClient("CN=Arne Vajhoej,OU=Demo,O=Personal,L=Coventry,ST=RI,C=US", null, SslProtocols.Tls12, true);
            BinaryReader br = new BinaryReader(sslstm);
            BinaryWriter bw = new BinaryWriter(sslstm);
            Console.WriteLine("(connected)");
            // send a and b
            int a = 123;
            int b = 456;
            bw.Write(HTON(a));
            bw.Write(HTON(b));
            bw.Flush();
            // receive c and print
            int c = NTOH(br.ReadInt32());
            Console.WriteLine(c);
            // send v
            string v = "ABC";
            byte[] vb = Encoding.UTF8.GetBytes(v);
            bw.Write((byte)vb.Length);
            bw.Write(vb);
            bw.Flush();
            // receive v2 and print
            int buflen = br.ReadByte();
            byte[] buf = new byte[buflen];
            int ix = 0;
            while (ix < buf.Length) {
                ix += br.Read(buf, ix, buf.Length - ix);
            }
            string v2 = Encoding.UTF8.GetString(buf);
            Console.WriteLine(v2);
            // close connection
            br.Close();
            bw.Close();
            cli.Close();
        }
    }
}

Note that this code accept all server certficates that expose expected name. This may not be wise from a security perspective.

.NET provide two levels of socket API:

Only the high level API is easy to use for SSL.

Imports System
Imports System.Security.Cryptography.X509Certificates
Imports System.IO
Imports System.Net.Security
Imports System.Net.Sockets
Imports System.Security.Authentication
Imports System.Text

Namespace SocketDemo.Binary.SecureHigh.Client
    Public Class Program
        Private Shared Function HTON(v As Integer) As Integer
            If BitConverter.IsLittleEndian Then
                Dim buf As Byte() = BitConverter.GetBytes(v)
                Array.Reverse(buf)
                Return BitConverter.ToInt32(buf, 0)
            Else
                Return v
            End If
        End Function
        Private Shared Function NTOH(v As Integer) As Integer
            If BitConverter.IsLittleEndian Then
                Dim buf As Byte() = BitConverter.GetBytes(v)
                Array.Reverse(buf)
                Return BitConverter.ToInt32(buf, 0)
            Else
                Return v
            End If
        End Function
        Private Shared Function CertCheck(sender As Object, certificate As X509Certificate, chain As X509Chain, sslPolicyErrors As SslPolicyErrors) As Boolean
            Console.WriteLine("(server is {0})", certificate.Subject)
            Return True
        End Function
        Private Const HOST As String = "localhost"
        Private Const PORT As Integer = 12345
        Public Shared Sub Main(args As String())
            ' open connection
            Dim cli As New TcpClient(HOST, PORT)
            ' get reader and writer
            Dim stm As NetworkStream = cli.GetStream()
            Dim sslstm As New SslStream(stm, False, AddressOf CertCheck)
            sslstm.AuthenticateAsClient("Bla bla bla", Nothing, SslProtocols.Tls12, True)
            Dim br As New BinaryReader(sslstm)
            Dim bw As New BinaryWriter(sslstm)
            Console.WriteLine("(connected)")
            ' send a and b
            Dim a As Integer = 123
            Dim b As Integer = 456
            bw.Write(HTON(a))
            bw.Write(HTON(b))
            bw.Flush()
            ' receive c and print
            Dim c As Integer = NTOH(br.ReadInt32())
            Console.WriteLine(c)
            ' send v
            Dim v As String = "ABC"
            Dim vb As Byte() = Encoding.UTF8.GetBytes(v)
            bw.Write(CByte(vb.Length))
            bw.Write(vb)
            bw.Flush()
            ' receive v2 and print
            Dim buflen As Integer = br.ReadByte()
            Dim buf As Byte() = New Byte(buflen - 1) {}
            Dim ix As Integer = 0
            While ix < buf.Length
                ix += br.Read(buf, ix, buf.Length - ix)
            End While
            Dim v2 As String = Encoding.UTF8.GetString(buf)
            Console.WriteLine(v2)
            ' close connection
            br.Close()
            bw.Close()
            cli.Close()
            Console.ReadKey()
        End Sub
    End Class
End Namespace

Note that this code accept all server certficates that expose expected name. This may not be wise from a security perspective.

To do SSL in C the most common way is to use OpenSSL.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <netdb.h>
#endif

#include <errno.h>

#include <openssl/ssl.h>
#include <openssl/x509.h>

#define HOST "localhost"
#define PORT "12345"

int verify_callback(int preverify, X509_STORE_CTX *x509ctx)
{
    printf("(server is %s)\n",  x509ctx->cert->name);
    return 1;
}

int main()
{
    int sd, status, ix, buflen;
    long int a, b, c, tmp;
    char len, *buf, *v, *v2;
    SSL_CTX *sslctx;
    SSL *ssl;
    struct addrinfo hints, *res;
#ifdef WIN32
    WSADATA WSAData;
    WSAStartup(0x0101, &WSAData);
#endif
    /* setup SSL */
    SSL_library_init();
    sslctx = SSL_CTX_new(TLSv1_2_client_method());
    if(sslctx == NULL)
    {
        printf("Error creating SSL context\n");
        exit(0);
    }
    SSL_CTX_set_verify(sslctx, SSL_VERIFY_PEER, verify_callback);
    SSL_CTX_set_verify_depth(sslctx, 4);
    SSL_CTX_set_options(sslctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1);
    /* lookup host */
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET; //AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    hints.ai_flags = 0;
    status = getaddrinfo(HOST, PORT, &hints, &res);
    if(status != 0)
    {
        printf("Error looking up host: %s\n", HOST);
        exit(0);
    }
    /* create socket */
    sd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    if(sd < 0)
    {
        printf("Error creating socket: %s\n", strerror(errno));
        exit(0);
    }
    /* connect to host */
    status = connect(sd, res->ai_addr, res->ai_addrlen);
    if(status != 0)
    {
        printf("Error connecting to host %s port %s: %s\n", HOST, PORT, strerror(errno));
        exit(0);
    }
    freeaddrinfo(res);
    /* SSL handshake */
    ssl = SSL_new(sslctx);
    if(ssl == NULL)
    {
        printf("Error initalizing SSL\n");
        exit(0);
    }
    SSL_set_fd(ssl, sd);
    status = SSL_connect(ssl);
    if(status <= 0)
    {
        printf("Error connecting SSL handshake\n");
        exit(0);
    }
    /* send a and b */
    a = 123;
    b = 456;
    tmp = htonl(a);
    status = SSL_write(ssl, (char *)&tmp, sizeof(tmp));
    if(status < 0) {
        printf("Error sending a\n");
        exit(0);
    }
    tmp = htonl(b);
    status = SSL_write(ssl, (char *)&tmp, sizeof(tmp));
    if(status < 0) {
        printf("Error sending b\n");
        exit(0);
    }
    /* receive c and print */
    buflen = sizeof(tmp);
    ix = 0;
    while(ix < buflen)
    {
        ix = ix + SSL_read(ssl, (char *)&tmp + ix, buflen - ix);
    }
    c = ntohl(tmp);
    printf("%ld\n", c);
    /* send v */
    v = "ABC";
    len = strlen(v);
    status = SSL_write(ssl, &len, sizeof(len));
    if(status < 0) {
        printf("Error sending v\n");
        exit(0);
    }
    status = SSL_write(ssl, v, len);
    if(status < 0)
    {
        printf("Error sending v\n");
        exit(0);
    }
    /* receive v2 and print */
    SSL_read(ssl, &len, sizeof(len));
    buflen = len;
    buf = malloc(buflen);
    ix = 0;
    while(ix < buflen)
    {
        ix = ix + SSL_read(ssl, buf + ix, buflen - ix);
    }
    v2 = malloc(buflen + 1);
    memcpy(v2, buf, buflen);
    v2[buflen] = '\0';
    printf("%s\n", v2);
    free(buf);
    free(v2);
    /* close socket */
    SSL_free(ssl);
    SSL_CTX_free(sslctx);
#ifdef WIN32
    closesocket(sd);
    WSACleanup();
#else
    close(sd);
#endif
    return 0;
}

Indy 10 has a convenient TIdTcpClient class that also supports SSL.

program SecureBinClient;

uses
  IdTCPClient, IdSSLOpenSSL;

const
  HOST = 'localhost';
  PORT = 12345;

var
  ssl : TIdSSLIOHandlerSocketOpenSSL;
  cli : TIdTCPClient;
  v, v2 : string;
  a, b, c, len : integer;

begin
  (* open connection *)
  ssl := TIdSSLIOHandlerSocketOpenSSL.Create;
  ssl.sslOptions.VerifyMode := [];
  ssl.sslOptions.VerifyDepth := 0;
  ssl.sslOptions.SSLVersions := [sslvTLSv1_2];
  ssl.sslOptions.Mode := sslmClient;
  cli := TIdTCPClient.Create;
  cli.IOHandler := ssl;
  cli.Connect(HOST, PORT);
  (* send a and b *)
  a := 123;
  b := 456;
  cli.Socket.Write(a, true);
  cli.Socket.Write(b, true);
  (* receive c and print *)
  c := cli.Socket.ReadInt32(true);
  writeln(c);
  (* send v *)
  v := 'ABC';
  cli.Socket.Write(Byte(length(v)));
  cli.Socket.Write(v);
  (* received v2 and print *)
  len := cli.Socket.ReadByte;
  v2 := cli.Socket.ReadString(len);
  writeln(v2);
  (* close connection *)
  cli.Disconnect;
  cli.Free;
end.

Singlethreaded server:

Singlethreaded server logic is:

listen
while
    accept
    receive/read
    send/write
    close
endwhile

Socket and ServerSocket classes has been in Java since version 1.0. Java also have DataInputStream and DataOutputStream classes for convenient reading and writing binary data.

Note that DataInputStream and DataOutputStream always use big endian.

package socket.ssl.binary.traditional;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLSocket;

public class SingleServer {
    private static SSLContext setupSSLContext(String keystore, String passphrase) throws Exception {
        KeyStore ks = KeyStore.getInstance("pkcs12");
        InputStream is = new FileInputStream(keystore);
        ks.load(is, passphrase.toCharArray());
        is.close();
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(ks, passphrase.toCharArray());
        SSLContext sslctx = SSLContext.getInstance("TLSv1.2");
        sslctx.init(kmf.getKeyManagers(), null, null);
        return sslctx;
    }
    private static final int PORT = 12345;
    public static void main(String[] args) {
        try {
            // get SSL context
            SSLContext sslctx = setupSSLContext("/work/sslserver.pfx", "topsecret");
            // listen on port
            SSLServerSocket ss = (SSLServerSocket) sslctx.getServerSocketFactory().createServerSocket(PORT);
            // only use TLS 1.2
            ss.setEnabledProtocols(new String[] { "TLSv1.2" });
            // do not use client certificate
            ss.setNeedClientAuth(false);
            ss.setWantClientAuth(false);
            while(true) {
                // accept connection
                SSLSocket s = (SSLSocket) ss.accept();
                System.out.printf("(connection from %s)\n", s.getRemoteSocketAddress());
                // get streams
                DataInputStream dis = new DataInputStream(s.getInputStream());
                DataOutputStream dos = new DataOutputStream(s.getOutputStream());
                // read a and b
                int a = dis.readInt();
                int b = dis.readInt();
                // calculate c and send
                int c = a + b;
                dos.writeInt(c);
                dos.flush();
                // read v
                int buflen = dis.readByte();
                byte[] buf = new byte[buflen];
                int ix = 0;
                while(ix < buf.length) {
                    ix += dis.read(buf, ix, buf.length - ix);
                }
                String v = new String(buf, "UTF-8");
                // calculate v2 and send
                String v2 = v + v;
                byte[] v2b = v2.getBytes("UTF-8");
                dos.writeByte(v2b.length);
                dos.write(v2b);
                dos.flush();
                // close connection
                dis.close();
                dos.close();
                s.close();
            }
        } catch (Exception e) {
            // TODO
            e.printStackTrace();
        }
    }
}

Java 1.4 added several new IO features called NIO including a SocketChannel. It is not that much of an improvement for single threaded server except that is supports both little and big endian.

It is possible to do SSL with NIO by using the SSLEngine class. But it requires some code to do the SSL handshake. It is strongly recommended to use an existing library instead of writing the code yourself. And therefor no code example is provides.

.NET provide two levels of socket API:

Only the high level API is easy to use for SSL.

using System;
using System.Security.Cryptography.X509Certificates;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Text;

namespace SocketDemo.Binary.SecureHigh.SingleServer
{
    public class Program
    {
        private static int HTON(int v)
        {
            if(BitConverter.IsLittleEndian)
            {
                byte[] buf = BitConverter.GetBytes(v);
                Array.Reverse(buf);
                return BitConverter.ToInt32(buf, 0);
            }
            else
            {
                return v;
            }
        }
        private static int NTOH(int v)
        {
            if(BitConverter.IsLittleEndian)
            {
                byte[] buf = BitConverter.GetBytes(v);
                Array.Reverse(buf);
                return BitConverter.ToInt32(buf, 0);
            }
            else
            {
                return v;
            }
        }
        private const int PORT = 12345;
        private const int BACKLOG = 100;
        public static void Main(string[] args)
        {
            // listen on port
            TcpListener srv = new TcpListener(IPAddress.Any, PORT);
            srv.Start(BACKLOG);
            while(true) {
                // accept connection
                TcpClient cli = srv.AcceptTcpClient();
                // get reader and writer
                NetworkStream stm = cli.GetStream();
                SslStream sslstm = new SslStream(stm);
                X509Certificate cert = new X509Certificate2(@"\work\sslserver.pfx", "topsecret");
                sslstm.AuthenticateAsServer(cert, false, SslProtocols.Tls12, true);
                BinaryReader br = new BinaryReader(sslstm);
                BinaryWriter bw = new BinaryWriter(sslstm);
                Console.WriteLine("(connection from {0})", cli.Client.RemoteEndPoint);
                // read a and b
                int a = NTOH(br.ReadInt32());
                int b = NTOH(br.ReadInt32());
                // calculate c and send
                int c = a + b;
                bw.Write(HTON(c));
                bw.Flush();
                // read v
                int buflen = br.ReadByte();
                byte[] buf = new byte[buflen];
                int ix = 0;
                while(ix < buf.Length)
                {
                    ix += br.Read(buf, ix, buf.Length - ix);                
                }
                string v = Encoding.UTF8.GetString(buf);
                // calculate v2 and send
                string v2 = v + v;
                byte[] v2b = Encoding.UTF8.GetBytes(v2);
                bw.Write((byte)v2b.Length);
                bw.Write(v2b);
                bw.Flush();
                // close connection
                br.Close();
                bw.Close();
                cli.Close();
            }
        }
    }
}

.NET provide two levels of socket API:

Only the high level API is easy to use for SSL.

Imports System
Imports System.Security.Cryptography.X509Certificates
Imports System.IO
Imports System.Net
Imports System.Net.Security
Imports System.Net.Sockets
Imports System.Security.Authentication
Imports System.Text

Namespace SocketDemo.Binary.SecureHigh.SingleServer
    Public Class Program
        Private Shared Function HTON(v As Integer) As Integer
            If BitConverter.IsLittleEndian Then
                Dim buf As Byte() = BitConverter.GetBytes(v)
                Array.Reverse(buf)
                Return BitConverter.ToInt32(buf, 0)
            Else
                Return v
            End If
        End Function
        Private Shared Function NTOH(v As Integer) As Integer
            If BitConverter.IsLittleEndian Then
                Dim buf As Byte() = BitConverter.GetBytes(v)
                Array.Reverse(buf)
                Return BitConverter.ToInt32(buf, 0)
            Else
                Return v
            End If
        End Function
        Private Const PORT As Integer = 12345
        Private Const BACKLOG As Integer = 100
        Public Shared Sub Main(args As String())
            ' listen on port
            Dim srv As New TcpListener(IPAddress.Any, PORT)
            srv.Start(BACKLOG)
            While True
                ' accept connection
                Dim cli As TcpClient = srv.AcceptTcpClient()
                ' get reader and writer
                Dim stm As NetworkStream = cli.GetStream()
                Dim sslstm As New SslStream(stm)
                Dim cert As X509Certificate = New X509Certificate2("\work\sslserver.pfx", "topsecret")
                sslstm.AuthenticateAsServer(cert, False, SslProtocols.Tls12, True)
                Dim br As New BinaryReader(sslstm)
                Dim bw As New BinaryWriter(sslstm)
                Console.WriteLine("(connection from {0})", cli.Client.RemoteEndPoint)
                ' read a and b
                Dim a As Integer = NTOH(br.ReadInt32())
                Dim b As Integer = NTOH(br.ReadInt32())
                ' calculate c and send
                Dim c As Integer = a + b
                bw.Write(HTON(c))
                bw.Flush()
                ' read v
                Dim buflen As Integer = br.ReadByte()
                Dim buf As Byte() = New Byte(buflen - 1) {}
                Dim ix As Integer = 0
                While ix < buf.Length
                    ix += br.Read(buf, ix, buf.Length - ix)
                End While
                Dim v As String = Encoding.UTF8.GetString(buf)
                ' calculate v2 and send
                Dim v2 As String = v & v
                Dim v2b As Byte() = Encoding.UTF8.GetBytes(v2)
                bw.Write(CByte(v2b.Length))
                bw.Write(v2b)
                bw.Flush()
                ' close connection
                br.Close()
                bw.Close()
                cli.Close()
            End While
        End Sub
    End Class
End Namespace

To do SSL in C the most common way is to use OpenSSL.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <netdb.h>
#endif

#include <errno.h>

#include <openssl/ssl.h>

int get_password(char *buf, int size, int rwflag, void *userdata)
{
    strcpy(buf, "topsecret");
    return strlen(buf);
}

void client_to_ssl(int sd2)
{
    int status, ix, buflen;
    long int a, b, c, tmp;
    char *buf, *v, *v2, len;
    SSL_CTX *sslctx;
    SSL *ssl;
    /* setup SSL */
    SSL_library_init();
    sslctx = SSL_CTX_new(TLSv1_2_server_method());
    if(sslctx == NULL)
    {
        printf("Error creating SSL context\n");
        exit(0);
    }
    status = SSL_CTX_use_certificate_file(sslctx, "/work/sslserver.pem", SSL_FILETYPE_PEM);
    if(status <= 0)
    {
        printf("Error reading certificate file\n");
        exit(0);
    }
    SSL_CTX_set_default_passwd_cb(sslctx, get_password);
    status = SSL_CTX_use_PrivateKey_file(sslctx, "/work/sslserver.pem", SSL_FILETYPE_PEM);
    if(status <= 0)
    {
        printf("Error reading private key file\n");
        exit(0);
    }
    SSL_CTX_set_options(sslctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_COMPRESSION | SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1);
    /* SSL handshake */
    ssl = SSL_new(sslctx);
    if(ssl == NULL)
    {
        printf("Error initalizing SSL\n");
        exit(0);
    }
    SSL_set_fd(ssl, sd2); 
    status = SSL_accept(ssl);
    if(status <= 0)
    {
        printf("Error accepting SSL handshake\n");
        exit(0);
    }
    /* read a and b */
    buflen = sizeof(tmp);
    ix = 0;
    while(ix < buflen)
    {
        ix = ix + SSL_read(ssl, (char *)&tmp + ix, buflen - ix);
    }
    a = ntohl(tmp);
    buflen = sizeof(tmp);
    ix = 0;
    while(ix < buflen)
    {
        ix = ix + SSL_read(ssl, (char *)&tmp + ix, buflen - ix);
    }
    b = ntohl(tmp);
    /* calculate c and send */
    c = a + b;
    tmp = htonl(c);
    status = SSL_write(ssl, (char *)&tmp, sizeof(tmp));
    if(status < 0) {
        printf("Error sending c\n");
        exit(0);
    }
    /* read v */
    SSL_read(ssl, &len, sizeof(len));
    buflen = len;
    buf = malloc(buflen);
    ix = 0;
    while(ix < buflen)
    {
        ix = ix + SSL_read(ssl, buf + ix, buflen - ix);
    }
    v = malloc(buflen + 1);
    memcpy(v, buf, buflen);
    v[buflen] = '\0';
    free(buf);
    /* calculate v2 and send */
    v2 = malloc(2 * strlen(v) + 1);
    strcpy(v2, v);
    strcat(v2, v);
    len = strlen(v2);
    status = SSL_write(ssl, &len, sizeof(len));
    if(status < 0) {
        printf("Error sending v2\n");
        exit(0);
    }
    status = SSL_write(ssl, v2, len);
    if(status < 0) {
        printf("Error sending v2\n");
        exit(0);
    }
    free(v);
    free(v2);
    /* close socket */
    SSL_free(ssl);
    SSL_CTX_free(sslctx);
#ifdef WIN32
    closesocket(sd2);
#else
    close(sd2);
#endif
}

#define PORT "12345"
#define BACKLOG 100

int main()
{
    int sd, sd2, status, slen;
    char addr[47];
    struct sockaddr remote;
    struct addrinfo hints, *res;
#ifdef WIN32
    WSADATA WSAData;
    if(WSAStartup(0x0101, &WSData) != 0)
    {
        printf("Error initializing\n");
        exit(0);
    }
#endif
    /* find port */
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET; //AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    hints.ai_flags = AI_PASSIVE; 
    status = getaddrinfo(NULL, PORT, &hints, &res);
    if(status != 0)
    {
        printf("Error finding port: %s\n", PORT);
        exit(0);
    }
    /* create socket */
    sd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    if(sd < 0)
    {
        printf("Error creating socket: %s\n", strerror(errno));
        exit(0);
    }
    /* bind */
    status = bind(sd, res->ai_addr, res->ai_addrlen);
    if(status < 0)
    {
        printf("Error binding socket: %s\n", strerror(errno));
        exit(0);
    }
    freeaddrinfo(res);
    /* listen */
    status = listen(sd, BACKLOG);
    if(status < 0)                            
    {
        printf("Error listening socket: %s\n", strerror(errno));
        exit(0);
    }
    for(;;)
    {
        /* accept */
        sd2 = accept(sd, 0, 0);
        if(sd2 < 0)
        {
            printf("Error accepting socket: %s\n", strerror(errno));
            exit(0);
        }
        slen = sizeof(remote);
        getpeername(sd2, &remote, &slen);
        if(remote.sa_family == AF_INET)
            printf("(connection from %s)\n", inet_ntop(AF_INET, &((struct sockaddr_in *)&remote)->sin_addr, addr, sizeof(addr)));
        if(remote.sa_family == AF_INET6)
            printf("(connection from %s)\n", inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&remote)->sin6_addr, addr, sizeof(addr)));
        /* switch client to SSL */
        client_to_ssl(sd2);
    }
    return 0;
}

Indy 10 has a convenient TIdTcpServer class that also supports SSL.

Indy does server sligtly different than other libraries.

program SecureBinSingleServer;

uses
  IdTCPServer, IdContext, IdSSL, IdSSLOpenSSL;

const
  PORT = 12345;
  BACKLOG = 100;

type
  ClientHandler = class
    public
      procedure Run(ctx : TIdContext);
      procedure GetPassword(var pw: string);
  end;

procedure ClientHandler.Run(ctx : TIdContext);

var
  v, v2 : string;
  a, b, c, len : integer;

begin
  (* this is necesarry tro make it work *)
  writeln('(connection from ', ctx.Connection.Socket.Binding.PeerIP, ':', ctx.Connection.Socket.Binding.PeerPort, ')');
  (* this is necessary to make it work *)
  if ctx.Connection.IOHandler is TIdSSLIOHandlerSocketBase then
    TIdSSLIOHandlerSocketBase(ctx.Connection.IOHandler).PassThrough := false;
  (* read a and b *)
  a := ctx.Connection.Socket.ReadInt32(true);
  b := ctx.Connection.Socket.ReadInt32(true);
  (* calculate c and send *)
  c := a + b;
  ctx.Connection.Socket.Write(c, true);
  (* read v *)
  len := ctx.Connection.Socket.ReadByte;
  v :=  ctx.Connection.Socket.ReadString(len);
  (* calculate v2 and send *)
  v2 := v + v;
  ctx.Connection.Socket.Write(Byte(length(v2)));
  ctx.Connection.Socket.Write(v2);
  (* close connection *)
  ctx.Connection.Disconnect;
end;

procedure ClientHandler.GetPassword(var pw: string);

begin
 pw := 'topsecret';
end;

var
  ssl : TIdServerIOHandlerSSLOpenSSL;
  srv : TIdTCPServer;
  h : ClientHandler;

begin
  h := ClientHandler.Create;
  (* listen on port *)
  ssl := TIdServerIOHandlerSSLOpenSSL.Create;
  ssl.SSLOptions.CertFile := 'C:\work\sslserver.pem';
  ssl.SSLOptions.KeyFile := 'C:\work\sslserver.pem';
  ssl.SSLOptions.Mode := sslmServer;
  ssl.SSLOptions.VerifyMode := [];
  ssl.SSLOptions.VerifyDepth  := 0;
  ssl.SSLOptions.SSLVersions := [sslvTLSv1_2];
  ssl.OnGetPassword := @h.GetPassword;
  srv := TIdTCPServer.Create();
  srv.IOHandler := ssl;
  srv.ListenQueue := BACKLOG;
  srv.Bindings.Add.Port := PORT;
  srv.StartListening;
  (* accept connection *)
  srv.OnExecute := @h.Run;
  srv.Active := true;
  readln;
  (* close connection *)
  srv.Free;
end.

Multithreaded server:

Multithreaded server logic is:

function handler
    receive/read
    send/write
    close
endfunction

listen
while
    accept
    start thread with handler
endwhile

Socket and ServerSocket classes has been in Java since version 1.0. Java also have DataInputStream and DataOutputStream classes for convenient reading and writing binary data.

Note that DataInputStream and DataOutputStream always use big endian.

package socket.ssl.binary.traditional;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLSocket;

public class MultiServer {
    public static class ClientHandler extends Thread {
        private SSLSocket s;
        public ClientHandler(SSLSocket s) {
            this.s = s;
        }
        @Override
        public void run() {
            try {
                System.out.printf("(handler in thread %d)\n", Thread.currentThread().getId());
                // get streams
                DataInputStream dis = new DataInputStream(s.getInputStream());
                DataOutputStream dos = new DataOutputStream(s.getOutputStream());
                // read a and b
                int a = dis.readInt();
                int b = dis.readInt();
                // calculate c and send
                int c = a + b;
                dos.writeInt(c);
                dos.flush();
                // read v
                int buflen = dis.readByte();
                byte[] buf = new byte[buflen];
                int ix = 0;
                while(ix < buf.length) {
                    ix += dis.read(buf, ix, buf.length - ix);
                }
                String v = new String(buf, "UTF-8");
                // calculate v2 and send
                String v2 = v + v;
                byte[] v2b = v2.getBytes("UTF-8");
                dos.writeByte(v2b.length);
                dos.write(v2b);
                dos.flush();
                // close connection
                dis.close();
                dos.close();
                s.close();
            } catch (IOException e) {
                // TODO
                e.printStackTrace();
            }
        }
    }
    private static SSLContext setupSSLContext(String keystore, String passphrase) throws Exception {
        KeyStore ks = KeyStore.getInstance("pkcs12");
        InputStream is = new FileInputStream(keystore);
        ks.load(is, passphrase.toCharArray());
        is.close();
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(ks, passphrase.toCharArray());
        SSLContext sslctx = SSLContext.getInstance("TLSv1.2");
        sslctx.init(kmf.getKeyManagers(), null, null);
        return sslctx;
    }
    private static final int PORT = 12345;
    public static void main(String[] args) {
        try {
            // get SSL context
            SSLContext sslctx = setupSSLContext("/work/sslserver.pfx", "topsecret");
            // listen on port
            SSLServerSocket ss = (SSLServerSocket) sslctx.getServerSocketFactory().createServerSocket(PORT);
            // only use TLS 1.2
            ss.setEnabledProtocols(new String[] { "TLSv1.2" });
            // do not use client certificate
            ss.setNeedClientAuth(false);
            ss.setWantClientAuth(false);
            while(true) {
                // accept connection
                SSLSocket s = (SSLSocket) ss.accept();
                System.out.printf("(connection from %s)\n", s.getRemoteSocketAddress());
                // start new thread to handle connection
                (new ClientHandler(s)).start();
            }
        } catch (Exception e) {
            // TODO
            e.printStackTrace();
        }
    }
}

Java 1.4 added several new IO features called NIO including a SocketChannel. It is not that much of an improvement for multithreaded server except that is supports both little and big endian.

It is possible to do SSL with NIO by using the SSLEngine class. But it requires some code to do the SSL handshake. It is strongly recommended to use an existing library instead of writing the code yourself. And therefor no code example is provides.

.NET provide two levels of socket API:

Only the high level API is easy to use for SSL.

using System;
using System.Security.Cryptography.X509Certificates;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Text;
using System.Threading;

namespace SocketDemo.Binary.SecureHigh.MultiServer
{
    public class Program
    {
        private static int HTON(int v)
        {
            if(BitConverter.IsLittleEndian)
            {
                byte[] buf = BitConverter.GetBytes(v);
                Array.Reverse(buf);
                return BitConverter.ToInt32(buf, 0);
            }
            else
            {
                return v;
            }
        }
        private static int NTOH(int v)
        {
            if(BitConverter.IsLittleEndian)
            {
                byte[] buf = BitConverter.GetBytes(v);
                Array.Reverse(buf);
                return BitConverter.ToInt32(buf, 0);
            }
            else
            {
                return v;
            }
        }
        private class ClientHandler
        {
            private TcpClient cli;
            public ClientHandler(TcpClient cli)
            {
                this.cli = cli;
            }
            public void Run()
            {
                Console.WriteLine("(handler in thread {0})", Thread.CurrentThread.ManagedThreadId);   
                // get reader and writer
                NetworkStream stm = cli.GetStream();
                SslStream sslstm = new SslStream(stm);
                X509Certificate cert = new X509Certificate2(@"\work\sslserver.pfx", "topsecret");
                sslstm.AuthenticateAsServer(cert, false, SslProtocols.Tls12, true);
                BinaryReader br = new BinaryReader(sslstm);
                BinaryWriter bw = new BinaryWriter(sslstm);
                // read a and b
                int a = NTOH(br.ReadInt32());
                int b = NTOH(br.ReadInt32());
                // calculate c and send
                int c = a + b;
                bw.Write(HTON(c));
                bw.Flush();
                // read v
                int buflen = br.ReadByte();
                byte[] buf = new byte[buflen];
                int ix = 0;
                while(ix < buf.Length)
                {
                    ix += br.Read(buf, ix, buf.Length - ix);                
                }
                string v = Encoding.UTF8.GetString(buf);
                // calculate v2 and send
                string v2 = v + v;
                byte[] v2b = Encoding.UTF8.GetBytes(v2);
                bw.Write((byte)v2b.Length);
                bw.Write(v2b);
                bw.Flush();
                // close connection
                br.Close();
                bw.Close();
                cli.Close();
            }
        }
        private const int PORT = 12345;
        private const int BACKLOG = 100;
        public static void Main(string[] args)
        {
            // listen on port
            TcpListener srv = new TcpListener(IPAddress.Any, PORT);
            srv.Start(BACKLOG);
            while(true) {
                // accept connection
                TcpClient cli = srv.AcceptTcpClient();
                Console.WriteLine("(connection from {0})", cli.Client.RemoteEndPoint);
                (new Thread((new ClientHandler(cli)).Run)).Start();
            }
        }
    }
}

.NET provide two levels of socket API:

Only the high level API is easy to use for SSL.

Imports System
Imports System.Security.Cryptography.X509Certificates
Imports System.IO
Imports System.Net
Imports System.Net.Security
Imports System.Net.Sockets
Imports System.Security.Authentication
Imports System.Text
Imports System.Threading

Namespace SocketDemo.Binary.SecureHigh.MultiServer
    Public Class Program
        Private Shared Function HTON(v As Integer) As Integer
            If BitConverter.IsLittleEndian Then
                Dim buf As Byte() = BitConverter.GetBytes(v)
                Array.Reverse(buf)
                Return BitConverter.ToInt32(buf, 0)
            Else
                Return v
            End If
        End Function
        Private Shared Function NTOH(v As Integer) As Integer
            If BitConverter.IsLittleEndian Then
                Dim buf As Byte() = BitConverter.GetBytes(v)
                Array.Reverse(buf)
                Return BitConverter.ToInt32(buf, 0)
            Else
                Return v
            End If
        End Function
        Private Class ClientHandler
            Private cli As TcpClient
            Public Sub New(cli As TcpClient)
                Me.cli = cli
            End Sub
            Public Sub Run()
                Console.WriteLine("(handler in thread {0})", Thread.CurrentThread.ManagedThreadId)
                ' get reader and writer
                Dim stm As NetworkStream = cli.GetStream()
                Dim sslstm As New SslStream(stm)
                Dim cert As X509Certificate = New X509Certificate2("\work\sslserver.pfx", "topsecret")
                sslstm.AuthenticateAsServer(cert, False, SslProtocols.Tls12, True)
                Dim br As New BinaryReader(sslstm)
                Dim bw As New BinaryWriter(sslstm)
                ' read a and b
                Dim a As Integer = NTOH(br.ReadInt32())
                Dim b As Integer = NTOH(br.ReadInt32())
                ' calculate c and send
                Dim c As Integer = a + b
                bw.Write(HTON(c))
                bw.Flush()
                ' read v
                Dim buflen As Integer = br.ReadByte()
                Dim buf As Byte() = New Byte(buflen - 1) {}
                Dim ix As Integer = 0
                While ix < buf.Length
                    ix += br.Read(buf, ix, buf.Length - ix)
                End While
                Dim v As String = Encoding.UTF8.GetString(buf)
                ' calculate v2 and send
                Dim v2 As String = v & v
                Dim v2b As Byte() = Encoding.UTF8.GetBytes(v2)
                bw.Write(CByte(v2b.Length))
                bw.Write(v2b)
                bw.Flush()
                ' close connection
                br.Close()
                bw.Close()
                cli.Close()
            End Sub
        End Class
        Private Const PORT As Integer = 12345
        Private Const BACKLOG As Integer = 100
        Public Shared Sub Main(args As String())
            ' listen on port
            Dim srv As New TcpListener(IPAddress.Any, PORT)
            srv.Start(BACKLOG)
            While True
                ' accept connection
                Dim cli As TcpClient = srv.AcceptTcpClient()
                Console.WriteLine("(connection from {0})", cli.Client.RemoteEndPoint)
                Dim t As Thread = New Thread(AddressOf (New ClientHandler(cli)).Run)
                t.Start()
            End While
        End Sub
    End Class
End Namespace

I will not show C examples for multithreaded server. Multithreading is not standardized in older versions of C. Windows uses Windows threads. Most other OS'es use POSIX threads. An example would be more a threading example than a socket example.

To read about threads see here.

Asynchroneous server:

Aynschroneous server logic is:

function accepthandler
    accept
    at read call inputhandler
endfunction

function inputhandler
    receive/read
    send/write
    close
endfunction

listen
at accept call accepthandler

Note that an asynchroneous server is using threads behind the scene.

The Java library Netty provide an async framework on top of NIO.

package socket.ssl.binary.netty;

import java.io.FileInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.KeyStore;

import javax.net.ssl.KeyManagerFactory;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;

public class AsyncServer {
    public static enum NextPhase { WAIT_INTADD, WAIT_STRDUP, DONE }
    private static class ReadHandler extends ChannelInboundHandlerAdapter {
        private ByteBuf inbb;
        private NextPhase state;
        public ReadHandler(Channel ch) {
            inbb = ch.alloc().buffer();
            state = NextPhase.WAIT_INTADD;
        }
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws UnsupportedEncodingException, InterruptedException {
            // append bytes read read from socket to buffer
            inbb.writeBytes((ByteBuf)msg);
            // act based on current state
            switch(state) {
                case WAIT_INTADD:
                    // test if received a and b
                    if(inbb.readableBytes() >= 8) {
                        // read a and b
                        int a = inbb.getInt(0);
                        int b = inbb.getInt(4);
                        // calculate c and send
                        int c = a + b;
                        ByteBuf outbb = ctx.channel().alloc().buffer();
                        outbb.writeInt(c);
                        ctx.channel().writeAndFlush(outbb);
                        // update state
                        state = NextPhase.WAIT_STRDUP;
                    }
                    break;
                case WAIT_STRDUP:
                    // test if received buflen
                    if(inbb.readableBytes() >= 9) {
                        // read v
                        int buflen = inbb.getByte(8);
                        if(inbb.readableBytes() < 9 + buflen) return; // test if received buf
                        byte[] buf = new byte[buflen];
                        inbb.getBytes(9, buf, 0, buflen);
                        String v = new String(buf, "UTF-8");
                        // calculate v2 and send
                        String v2 = v + v;
                        ByteBuf outbb = ctx.channel().alloc().buffer();
                        outbb.writeByte(v2.length());
                        outbb.writeBytes(v2.getBytes("UTF-8"));
                        ctx.channel().writeAndFlush(outbb);
                        // update state
                        state = NextPhase.DONE;
                    }
                    break;
                case DONE:
                    // ignore
                    break;
            }
        }
    }
    private static SslContext setupSSLContext(String keystore, String passphrase) throws Exception {
        KeyStore ks = KeyStore.getInstance("pkcs12");
        InputStream is = new FileInputStream(keystore);
        ks.load(is, passphrase.toCharArray());
        is.close();
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        kmf.init(ks, passphrase.toCharArray());
        return SslContextBuilder.forServer(kmf).protocols("TLSv1.2").build();
    }
    private static final int PORT = 12345;
    private static final int BACKLOG = 100;
    public static void main(String[] args) throws Exception {
        // setup context
        SslContext sslctx = setupSSLContext("/work/sslserver.pfx", "topsecret");
        EventLoopGroup g = new NioEventLoopGroup(1);
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(g).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, BACKLOG);
            // define handler
            b.childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     System.out.printf("(connection from %s)\n", ch.remoteAddress());
                     ch.pipeline().addLast(sslctx.newHandler(ch.alloc()))
                                  .addLast(new ReadHandler(ch));
                 }
             });
            // listen on port
            ChannelFuture f = b.bind(PORT).sync();
            // close connection when done (never)
            f.channel().closeFuture().sync();
        } finally {
            // teardown context
            g.shutdownGracefully();
        }       
    }
}

Article history:

Version Date Description
1.0 May 2nd 2018 Initial version
1.1 July 29th 2018 Add Delphi/Lazarus Indy examples
1.2 June 28th 2022 Add Java Netty example

Other articles:

See list of all articles here

Comments:

Please send comments to Arne Vajhøj