For other articles in series see:
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.
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.
The text protocol used in all examples are very simple:
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 logic is:
connect send/write receive/read close
Socket and ServerSocket classes has been in Java since version 1.0. Java also makes it easy to wrap the Socket in BufferedReader and PrintStream to read and write text data.
NIO does not does not provide any nice features for reading lines of text.
package socket.ssl.text;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.security.cert.X509Certificate;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.X509TrustManager;
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 reader and writer
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream(), "UTF-8"));
PrintStream ps = new PrintStream(s.getOutputStream(), false, "UTF-8");
System.out.println("(connected)");
// send a and b
int a = 123;
int b = 456;
ps.println(a + " " + b);
ps.flush();
// receive c and print
String line = br.readLine();
int c = Integer.parseInt(line);
System.out.println(c);
// send v
String v = "ABC";
ps.println(v);
// receive v2 and print
String v2 = br.readLine();
System.out.println(v2);
// close connection
br.close();
ps.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.
.NET provide two levels of socket API:
Only the high level API is easy to use for SSL and reading/writing text lines.
using System;
using System.Security.Cryptography.X509Certificates;
using System.IO;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
namespace SocketDemo.Text.SecureHigh.Client
{
public class Program
{
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("Bla bla bla", null, SslProtocols.Tls12, true);
StreamReader sr = new StreamReader(sslstm);
StreamWriter sw = new StreamWriter(sslstm);
Console.WriteLine("(connected)");
// send a and b
int a = 123;
int b = 456;
sw.WriteLine(a + " " + b);
sw.Flush();
// receive c and print
int c = int.Parse(sr.ReadLine());
Console.WriteLine(c);
// send v
string v = "ABC";
sw.WriteLine(v);
sw.Flush();
// receive v2 and print
string v2 = sr.ReadLine();
Console.WriteLine(v2);
// close connection
sr.Close();
sw.Close();
cli.Close();
Console.ReadKey();
}
}
}
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 and reading/writing text lines.
Imports System
Imports System.Security.Cryptography.X509Certificates
Imports System.IO
Imports System.Net.Security
Imports System.Net.Sockets
Imports System.Security.Authentication
Namespace SocketDemo.Text.SecureHigh.Client
Public Class Program
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 sr As New StreamReader(sslstm)
Dim sw As New StreamWriter(sslstm)
Console.WriteLine("(connected)")
' send a and b
Dim a As Integer = 123
Dim b As Integer = 456
sw.WriteLine(a & " " & b)
sw.Flush()
' receive c and print
Dim c As Integer = Integer.Parse(sr.ReadLine())
Console.WriteLine(c)
' send v
Dim v As String = "ABC"
sw.WriteLine(v)
sw.Flush()
' receive v2 and print
Dim v2 As String = sr.ReadLine()
Console.WriteLine(v2)
' close connection
sr.Close()
sw.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"
void recv_line(SSL *ssl, char *buf, int buflen)
{
int ix;
ix = 0;
buf[ix] = '\0';
while(ix < buflen && strstr(buf, "\r\n") == NULL)
{
ix = ix + SSL_read(ssl, buf + ix, buflen - ix);
buf[ix] = '\0';
}
}
int verify_callback(int preverify, X509_STORE_CTX *x509ctx)
{
printf("(server is %s)\n", x509ctx->cert->name);
return 1;
}
int main()
{
int sd, status, buflen;
long int a, b, c;
char *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;
buflen = 24;
buf = malloc(buflen);
sprintf(buf, "%ld %ld\r\n", a, b);
status = SSL_write(ssl, buf, strlen(buf));
if(status < 0)
{
printf("Error sending a and b: %s\n", strerror(errno));
exit(0);
}
free(buf);
/* receive c and print */
buflen = 13;
buf = malloc(buflen);
recv_line(ssl, buf, buflen);
sscanf(buf, "%ld\r\n", &c);
printf("%ld\n", c);
free(buf);
/* send v */
v = "ABC";
buflen = strlen(v) + 3;
buf = malloc(buflen);
sprintf(buf, "%s\r\n", v);
status = SSL_write(ssl, buf, strlen(buf));
if(status < 0)
{
printf("Error sending v\n");
exit(0);
}
free(buf);
/* receive v2 and print */
buflen = 258;
buf = malloc(buflen);
recv_line(ssl, buf, buflen);
v2 = malloc(strlen(buf) + 1);
sscanf(buf, "%s\r\n", v2);
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 SecureTextClientHigh;
uses
IdTCPClient, IdSSLOpenSSL, SysUtils;
const
HOST = 'localhost';
PORT = 12345;
var
ssl : TIdSSLIOHandlerSocketOpenSSL;
cli : TIdTCPClient;
v, v2 : string;
a, b, c : 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.WriteLn(IntToStr(a) + ' ' + IntToStr(b));
(* receive c and print *)
c := StrToint(cli.Socket.ReadLn);
writeln(c);
(* send v *)
v := 'ABC';
cli.Socket.WriteLn(v);
(* received v2 and print *)
v2 := cli.Socket.ReadLn;
writeln(v2);
(* close connection *)
cli.Disconnect;
cli.Free;
end.
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 makes it easy to wrap the Socket in BufferedReader and PrintStream to read and write text data.
NIO does not does not provide any nice features for reading lines of text.
package socket.ssl.text;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
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 reader and writer
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream(), "UTF-8"));
PrintStream ps = new PrintStream(s.getOutputStream(), false, "UTF-8");
// read a and b
String line = br.readLine();
String[] parts = line.split(" ");
int a = Integer.parseInt(parts[0]);
int b = Integer.parseInt(parts[1]);
// calculate c and send
int c = a + b;
ps.println(c);
ps.flush();
// read v
String v = br.readLine();
// calculate v2 and send
String v2 = v + v;
ps.println(v2);
ps.flush();
// close connection
br.close();
ps.close();
s.close();
}
} catch (Exception e) {
// TODO
e.printStackTrace();
}
}
}
.NET provide two levels of socket API:
Only the high level API is easy to use for SSL and reading/writing text lines.
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;
namespace SocketDemo.Text.SecureHigh.SingleServer
{
public class Program
{
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);
StreamReader sr = new StreamReader(sslstm);
StreamWriter sw = new StreamWriter(sslstm);
Console.WriteLine("(connection from {0})", cli.Client.RemoteEndPoint);
// read a and b
string line = sr.ReadLine();
string[] parts = line.Split(' ');
int a = int.Parse(parts[0]);
int b = int.Parse(parts[1]);
// calculate c and send
int c = a + b;
sw.WriteLine(c);
sw.Flush();
// read v
string v = sr.ReadLine();
// calculate v2 and send
string v2 = v + v;
sw.WriteLine(v2);
sw.Flush();
// close connection
sr.Close();
sw.Close();
cli.Close();
}
}
}
}
.NET provide two levels of socket API:
Only the high level API is easy to use for SSL and reading/writing text lines.
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
Namespace SocketDemo.Text.SecureHigh.SingleServer
Public Class Program
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 sr As New StreamReader(sslstm)
Dim sw As New StreamWriter(sslstm)
Console.WriteLine("(connection from {0})", cli.Client.RemoteEndPoint)
' read a and b
Dim line As String = sr.ReadLine()
Dim parts As String() = line.Split(" "C)
Dim a As Integer = Integer.Parse(parts(0))
Dim b As Integer = Integer.Parse(parts(1))
' calculate c and send
Dim c As Integer = a + b
sw.WriteLine(c)
sw.Flush()
' read v
Dim v As String = sr.ReadLine()
' calculate v2 and send
Dim v2 As String = v & v
sw.WriteLine(v2)
sw.Flush()
' close connection
sr.Close()
sw.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>
void recv_line(SSL *ssl, char *buf, int buflen)
{
int ix;
ix = 0;
buf[ix] = '\0';
while(ix < buflen && strstr(buf, "\r\n") == NULL)
{
ix = ix + SSL_read(ssl, buf + ix, buflen - ix);
buf[ix] = '\0';
}
}
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, buflen;
long int a, b, c;
char *buf, *v, *v2;
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 = 24;
buf = malloc(buflen);
recv_line(ssl, buf, buflen);
sscanf(buf, "%ld %ld\r\n", &a, &b);
free(buf);
/* calculate c and send */
c = a + b;
buflen = 13;
buf = malloc(buflen);
sprintf(buf, "%ld\r\n", c);
status = SSL_write(ssl, buf, strlen(buf));
if(status < 0)
{
printf("Error sending c\n");
exit(0);
}
free(buf);
/* read v */
buflen = 258;
buf = malloc(buflen);
recv_line(ssl, buf, buflen);
v = malloc(strlen(buf) + 1);
sscanf(buf, "%s\r\n", v);
free(buf);
/* calculate v2 and send */
v2 = malloc(2 * strlen(v) + 1);
strcpy(v2, v);
strcat(v2, v);
buflen = strlen(v2) + 3;
buf = malloc(buflen);
sprintf(buf, "%s\r\n", v2);
status = SSL_write(ssl, buf, strlen(buf));
if(status < 0)
{
printf("Error sending v2: %s\n", strerror(errno));
exit(0);
}
free(buf);
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, &WSAData) != 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 SecureTextSingleServer;
uses
IdTCPServer, IdContext, IdSSL, IdSSLOpenSSL, SysUtils;
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
line, v, v2 : string;
a, b, c, ix : integer;
begin
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 *)
line := ctx.Connection.Socket.ReadLn;
ix := Pos(' ', line);
a := StrToInt(Copy(line, 1, ix - 1));
b := StrToInt(Copy(line, ix + 1, length(line) - ix));
(* calculate c and send *)
c := a + b;
ctx.Connection.Socket.WriteLn(IntToStr(c));
(* read v *)
v := ctx.Connection.Socket.ReadLn;
(* calculate v2 and send *)
v2 := v + v;
ctx.Connection.Socket.WriteLn(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 *)
h := ClientHandler.Create;
srv.OnExecute := @h.Run;
srv.Active := true;
readln;
(* close connection *)
srv.Free;
end.
: 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 makes it easy to wrap the Socket in BufferedReader and PrintStream to read and write text data.
NIO does not does not provide any nice features for reading lines of text.
package socket.ssl.text;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
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 {
// get reader and writer
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream(), "UTF-8"));
PrintStream ps = new PrintStream(s.getOutputStream(), false, "UTF-8");
// read a and b
String line = br.readLine();
String[] parts = line.split(" ");
int a = Integer.parseInt(parts[0]);
int b = Integer.parseInt(parts[1]);
// calculate c and send
int c = a + b;
ps.println(c);
ps.flush();
// read v
String v = br.readLine();
// calculate v2 and send
String v2 = v + v;
ps.println(v2);
ps.flush();
// close connection
br.close();
ps.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();
}
}
}
.NET provide two levels of socket API:
Only the high level API is easy to use for SSL and reading/writing text lines.
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.Threading;
namespace SocketDemo.Text.SecureHigh.MultiServer
{
public class Program
{
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);
StreamReader sr = new StreamReader(sslstm);
StreamWriter sw = new StreamWriter(sslstm);
// read a and b
string line = sr.ReadLine();
string[] parts = line.Split(' ');
int a = int.Parse(parts[0]);
int b = int.Parse(parts[1]);
// calculate c and send
int c = a + b;
sw.WriteLine(c);
sw.Flush();
// read v
string v = sr.ReadLine();
// calculate v2 and send
string v2 = v + v;
sw.WriteLine(v2);
sw.Flush();
// close connection
sr.Close();
sw.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 and reading/writing text lines.
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.Threading
Namespace SocketDemo.Text.SecureHigh.MultiServer
Public Class Program
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 sr As New StreamReader(sslstm)
Dim sw As New StreamWriter(sslstm)
' read a and b
Dim line As String = sr.ReadLine()
Dim parts As String() = line.Split(" "C)
Dim a As Integer = Integer.Parse(parts(0))
Dim b As Integer = Integer.Parse(parts(1))
' calculate c and send
Dim c As Integer = a + b
sw.WriteLine(c)
sw.Flush()
' read v
Dim v As String = sr.ReadLine()
' calculate v2 and send
Dim v2 As String = v & v
sw.WriteLine(v2)
sw.Flush()
' close connection
sr.Close()
sw.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.
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.text;
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.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.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
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 NextPhase state;
public ReadHandler(Channel ch) {
state = NextPhase.WAIT_INTADD;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws UnsupportedEncodingException, InterruptedException {
String line;
switch(state) {
case WAIT_INTADD:
line = (String)msg;
// read a and b
String[] parts = line.split(" ");
int a = Integer.parseInt(parts[0]);
int b = Integer.parseInt(parts[1]);
// calculate c and send
int c = a + b;
ctx.channel().writeAndFlush(Integer.toString(c) + "\r\n");
// update state
state = NextPhase.WAIT_STRDUP;
break;
case WAIT_STRDUP:
line = (String)msg;
// read v
String v = line;
// calculate v2 and send
String v2 = v + v;
ctx.channel().writeAndFlush(v2 + "\r\n");
// 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 DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()))
.addLast(new StringDecoder())
.addLast(new StringEncoder())
.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();
}
}
}
Version | Date | Description |
---|---|---|
1.0 | May 10th 2018 | Initial version |
1.1 | July 29th 2018 | Add Delphi/Lazarus Indy examples |
1.2 | June 28th 2022 | Add Java Netty example |
See list of all articles here
Please send comments to Arne Vajhøj