Click here to Skip to main content
15,867,308 members
Articles / Programming Languages / VC++

A Working TCP Client and Server With SSL

Rate me:
Please Sign up or sign in to vote.
4.94/5 (55 votes)
24 Sep 2018CPOL27 min read 264.3K   13.1K   177   137
A working example of a Windows client and server using SSL over TCP.

Introduction

This is a project (several, technically) to demonstrate how to use the Microsoft implementation of SSL (called SCHANNEL). This is a working example of a multithreaded server using SSL and a client which can connect to it. There's also a very simple example client which connects to a commodity web server, just to show the simplest possible usage - I won't discuss it any further here but the sample (called SimpleClient) is available if you want it.

For the SSL savvy, be aware that this sample supports SNI and wildcard SAN certificates on both the server and, optionally, the client, as well as providing flexible certificate selection and acceptance. If you're unfamiliar with the world of SSL, this sample provides some code that's intended to be easy to reuse for a simple, or complex, client and server.

Background

SSL is a means by which data can be moved between two endpoints securely and reliably. Using it, you can verify the other end of a connection is who it claims to be, and/or encrypt data as it passes between the two endpoints. TLS (the successor to SSL) is what is actually used here, but I use the more familiar term SSL throughout this document.

SSL relies on a public key infrastructure, and generally those keys live in a certificate store. In the Microsoft world, the usual store is some encrypted portions of the registry (Java and OpenSSL do it differently). There's a set of named stores for the whole machine and a set for each user, all viewable using an MMC snap-in.

Image 1

Generally, servers get their certificate (which contains the public key and points to a private one) from the machine store called "personal" (oddly, it's called "my" in code) and clients get their certificate from the per-user store called "personal" (likewise "my" in code). Clients generally do not use certificates at all, since client certificates are optional unless the server says it requires one; this sample server does require one by default, but the sample client can create a suitable one if necessary. The server certificate identifies the system, and the client certificate (if there is one) usually identifies a particular user. That is the most common setup, but the minimum requirement is merely that the server supply a certificate. For more details, see Creating Digital Certificates.

Client Overview

The client tries to open a TCP socket to port number 41000 on a host you specify using a command line parameter (the name defaults to the DNS name of the local host). Once that socket is open, the client initiates an SSL handshake (an initial exchange of data to agree on the options used for communication) and validates the certificate the server provides. The server requests a client certificate so the client selects one likely to be trusted by the server if possible, otherwise it selects one (somewhat arbitrarily) from the user personal store and if it cannot find one, it creates one. The client is a console application, so it displays progress on the console. If you run a debug version in the debugger, there's also a lot of detailed information in the output window.

Server Overview

The server waits on an incoming connection on port number 41000, and when one arrives, it hands the socket for the connection to a newly initiated thread for processing (this is a common pattern for servers which do significant work for each new socket). Like the client, it is a console application, so it displays progress on the console. If you run a debug build and attach a debugger, there's also a lot of detailed output.

Once a thread is initiated, it waits for the client at the other end of the socket to initiate an SSL handshake (or until it eventually times out). As part of the handshake, the client will use SNI to tell the server what server name it is trying to connect to, and armed with that information, each thread will look for an appropriately named certificate in the machine store (subsequent connections requesting the same host name use the same certificate). The chosen certificate must meet other requirements too, such as having a private key and being marked as usable for server identification. The server requests a certificate from the client and validates that before marking the connection as successful.

The server has a bit of code at the end to automatically initiate a couple of client instances, one connecting to the server name "localhost" the other is just allowed to default to the local host name. This makes testing a bit easier and allows you to see certificates being selected and/or created.

Build Environment

Most of the code compiles under Visual Studio 2010, 2012, 2013 and 2015, but when I added the SAN and wildcard certificate matching, I used more modern C++ constructs, requiring at least VS2013, and upgraded the project to use the VS 2015 (version 140) toolchain, the September 2018 version uses VS 2017 (the version 141 toolchain) but could  be compiled with VS 2015 with minor changes. It is a 32 bit Unicode build, in that release there is no ANSI alternative although the sample data transferred is a byte stream. I'd expect it to run on any Windows version beyond Windows Vista. The July 2019 updates include an optional 64 bit build and the August (version 2.1.0) update upgrades to VS2019 and the version 142 toolchain (though it will still work with 2017 and 141).

In January 2020 someone needed to integrate SSL into some existing ANSI (aka multibyte) code, so I created an ANSI build and eliminated the client use of MFC to make the code more broadly compatible in version 2.1.1. The SSLClient and SSLServer code is always Unicode but the resulting library and header files can be used with multibyte callers if necessary. The Unicode interfaces are unchanged.

In March 2022 the SSLClient and SSLServer projects were combined in a StreamSSL project. This was released in 2.1.4 with the SSLClient and SSLServer still in place for anyone who still uses them. I anticipate deleting them in 2.1.5.

Prior to 2.1.4 the source uses some ATL utility functions and classes but neither the client nor the server use the ATL framework as a whole.

The 2.1.4 release uses the version 143 toolchain becuse VS2022 was the newest release at that time but it still works with the 141 toolchain (VS 2017).

Creating Digital Certificates

In production environments, you would get a certificate from a trusted authority (a "Certificate Authority" or CA). The CA would be responsible for ensuring that you were authorized to request the certificate you asked for. For example, if you were requesting a server certificate for SSL, the CA would ensure you were the owner of the domain the server was in. Such a certificate would usually identify the server by its fully qualified domain name, something like hostname.companyname.com. If a random person asks for a certificate in the "microsoft.com" domain, for example, a responsible CA should not issue one. Although mistakes do occasionally happen, they are rare and you can generally trust certificates issued by a legitimate CA.

Each certificate has many attributes, but the ones of most importance for SSL are the "Subject Name" (what entity the certificate is for, like a specific server or a particular user), the "Key Usage" and "Enhanced Key Usage" describing how the certificate is intended to be used ("Server Authentication" for example), and whether you have a private key for the certificate or just a public one. Other attributes like the validity period or issuer name don't matter much in this sample, but would in production (because then you'd care about who issued the certificate and whether it was currently valid). Client certificates usually identify a particular user, so they often have an e-mail address as a subject name.

Newer certificate standards allow for a Subject Alternative Name (SAN) on each certificate, which allows the same certificate to be used for a list of names. Another way of allowing for multiple names is "wildcard" certificates, which allow multiple servers in the same domain to be protected by the same certificate. For this example only wildcards of the form *.<domain name> are supported.

For test purposes, you don't need CA issued certificates; you can use ones you generate yourself (so-called "self signed certificates"). If it doesn't find suitable server and/or client certificates, this sample will make them for you using subject names it provides (the host name and user name basically), the code to do this is in CreateCertificate in CertHelper.cpp. If you prefer to provide your own certificate, the easiest way is to ask IIS to create one for you or create one in PowerShell using the New-SelfSignedCertificate command (see https://blog.davidchristiansen.com/2016/09/howto-create-self-signed-certificates-with-powershell/ for advanced examples) Leon Finker's article (see below) describes other alternatives that I have not tried. For the client end, this sample code will use any certificate with a private key it can find, or make one if it has to.

Client Details

The client application is called StreamClient and the basic flow it uses is fairly simple:

  • declare a TCP connection (a socket)
  • connect it
  • negotiate SSL over the TCP connection
  • send and receive a couple of test messages
  • stop using SSL, but keep the TCP connection open
  • send a couple of plaintext test messages with a delay between them
  • shut down the SSL connection
  • Send an unencrypted message
  • shut down the socket

If all you need is a straightforward client and you are not too concerned with the details of SSL or TCP, just read "Main Program" below, then skip forward to Server Details if you need a server too.

Main Program

This is all you really need to make a connection work. It's in StreamClient.cpp and just declares a few objects, opens a TCP connection, and passes it to an object that implements ISocketStream so that the connection can be completed. The essence of the code looks like this:

CActiveSock * pActiveSock = new CActiveSock(ShutDownEvent);
CSSLClient * pSSLClient = nullptr;
pActiveSock->SetRecvTimeoutSeconds(30);
pActiveSock->SetSendTimeoutSeconds(60);
bool b = pActiveSock->Connect(HostName.c_str(), Port);
if (b)
   {
   char Msg[100];
   pSSLClient = new CSSLClient(pActiveSock);
   b = SUCCEEDED(pSSLClient->Initialize(HostName));
   if (b)
      {
      cout << "Connected, cert name matches=" << pSSLClient->getServerCertNameMatches()
         << ", cert is trusted=" << pSSLClient->getServerCertTrusted() << endl;
      if (pSSLClient->Send("Hello from client", 17) != 17)
         cout << "Wrong number of characters sent" << endl;
      int len = 0;
      while (0 < (len = pSSLClient->Recv(Msg, sizeof(Msg))))
         cout << "Received " << CStringA(Msg, len) << endl;
      }
   else
      cout << "SSL client initialize failed" << endl;
   ::SetEvent(ShutDownEvent);
   pSSLClient->Close();
   }

Notice the getServerCertNameMatches and getServerCertTrusted methods; they tell the client how much trust to put in the certificate.

If you compile up the client and run it, it will connect to the server and show something like this in a console window:

Connecting to localhost:41000
Socket connected to server, initializing SSL
Optional client certificate requested (without issuer list), no certificate found.
Client certificate required, issuer list is empty, selected name: david.maw@unisys.com
A trusted server certificate called "localhost" was returned with a name match
Connected, cert name matches=1, cert is trusted=1
Sending greeting
Sending second greeting
Listening for message from server
Received 'Hello from server'
Received 'Goodbye from server'
Shutting down SSL
Sending first unencrypted data message
Sleeping before sending second unencrypted data message
Sending second unencrypted data message
Sleeping before sending termination to give the last message time to arrive
Press any key to pause, Q to exit immediately

A debug build can also be changed to display the details of the received server certificate. It will look something like this:

Certificate sample

To make it display the certificate, change the sample client to set g_ShowCertInfo and it will show the info in:

if (g_ShowCertInfo && debug && pCertContext)
    ShowCertInfo(pCertContext, L"Client Received Server Certificate");

If all you need is a simple SSL client, just alter this code so it does what you want and go no further. If you are interested in more control or how it all works, read on...

Controlling Certificates

There are a couple of things you can do to give you more control over the connection, you can provide a CertAcceptable function to evaluate the certificate provided by the server and reject it if you do not like it (which closes the connection). You can also provide a SelectClientCertificate function to allow you to control what client certificate (if any) is sent to the server. The code to use these looks like this:

pSSLClient->ServerCertAcceptable = CertAcceptable;
pSSLClient->SelectClientCertificate = SelectClientCertificate;

Examples of both of these are in the sample source, but you need not use either of them, if you do, you can assign lambda expressions, or, as the sample does, define functions with appropriate parameters, and just assign those.

TCP Client (CActiveSock)

The TCP client connection is abstracted into a class called CActiveSock defined in ActiveSock.cpp and declared in ActiveSock.h. This class implements a few simple functions (Connect, Disconnect, Send and Receive, for example) that abstract away the details of interfacing with WinSock so that the main caller need not deal with them. To use it, you declare a CActiveSock object, optionally set some timeouts on it, than ask it to connect to a particular endpoint (a hostname and port number).

SSL Client (CSSLClient)

The code to implement the SSL connection is within the CSSLClient object with some helper functions in SSLHelper.cpp. The CSSLClient constructor needs a connected CActiveSock to provide it with a channel over which to communicate. Once constructed, you call its Initialize method, and once that completes successfully, you have an open channel over which you can communicate using Send and Recv. TCP may not send the whole message you request (it may run out of buffer space, for example) or deliver the bytes you sent in a single message (messages can be split or joined), but Send and Recv take care of that using multiple network calls if necessary. In practice, SSL does not exhibit this property -- it sends what you request in a single message and delivers exactly what's sent in a single message.

Validating the Server Certificate

As part of the SSL negotiation, the client tells the server what server name it thinks it is connecting to using a mechanism called Server Name Indication or SNI, which is a feature of the TLS protocol (technically, it provides a list, but there's only ever one name in the list). Once the server knows what name the client is looking for (and there might not be one, in which case the local host name is used), it can provide a certificate with a matching Subject Name. This sample code does that by default, but if no matching certificate is found, it will use a self signed one with the wrong name if there is one. Each time a connection is made, the code looks in a hash table to see if it already has used a certificate for that server name (see GetCredHandleFor), if it has one, the same certificate context will be reused for the connection. If the server name has not been seen before then a certificate is selected from the certificate store and also added to the hash table so it can be used next time.

The important assessments the client must make about the certificate the server provides are "is it valid" (not expired, for example), "was it issued by a trusted CA", and "does the subject name match what's expected". Once the SSL handshake completes, each CSSLClient object implements two methods the caller can use to answer these questions: getServerCertTrusted and getServerCertNameMatches. Generally, you won't want to use the connection unless both return true, because otherwise the communication channel might be compromised (that is, read, changed or even redirected before reaching the desired endpoint).

As an alternative, you can provide your own CertAcceptable function to evaluate the certificate provided by the server and reject it if you do not like it (which closes the connection). If you do decide to provide that function, it gets given a pointer to a server handle, and two boolean values to indicate if the certificate is trusted and has a subject name matching the one that the client requested.

Selecting a Client Certificate

As part of the SSL negotiation, the server sends a list of acceptable certificate issuers to the client unless a particular registry key is set (see below) . This allows the client to sort through a list of possible certificates to find one from an issuer that is acceptable to the server, and then offer that certificate to the server. If no acceptable certificate can be found, the sample uses any certificate it can find, or even creates one if it cannot find one. The server code can easily be changed to display the certificate it received from the client if it gets one, to make it display the certificate, change ClientCertAcceptable in the sample server to insert:

C++
ShowCertInfo(pCertContext, _T("Server Received Client Certificate"));

Optionally, you can provide your own SelectClientCertificate function in the client to allow you to control what client certificate (if any) is sent to the server.

Interestingly, this function is called twice, once early in the negotiation to optionally provide a client certificate if you want to - if the server approves of this, you're done. If a client certificate is required and you don't provide one, the connection will fail, so there may be a second call requiring a certificate to be provided - this call may come with a list of acceptable certificate issuers.

BEWARE - The server will NOT send an "acceptable issuer" list if the registry key HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL has a DWORD value called SendTrustedIssuerList set to 0.

Server Details

The server is considerably more complex than the client - the SSL part is mostly the same, but there's a lot more complexity in allowing it to accept multiple simultaneous connections over TCP.

Main Program

Just as for the client, this is all you really need to make a connection work; it's in StreamServer.cpp and just declares a CListener then tells it to start listening on a particular port and define what's going to happen once a connection is made. You define that in a lambda expression, and you can use the ISocketStream interface pointer that's passed to it in order to send or receive messages across the SSL connection. Simplified sample code looks as follows (the full sample is a bit more complex because it handles turning off SSL on an in-use socket) :

C++
Listener->Initialize(Port);
Listener->BeginListening([](ISocketStream * const StreamSock){
   // This is the code to be executed each time a socket is opened
   StreamSock->Send("Hello from server", 17);
   int len = StreamSock->Recv(MsgText, sizeof(MsgText) - 1);
   if (len > 0)
      StreamSock->Send("Goodbye from server", 19);
   });
Listener->EndListening();

If you compile up the server and run it, it will wait for connections from clients (it will run a couple to simplify testing) and show this sort of console output (this example shows two clients connecting in succession):

WARNING: The server is not running as an administrator.
Starting to listen on port 41000, will find certificate for first connection.
Listening for client connections.

Initiating a client instance for testing.

Waiting on StreamClient to localhost
Server certificate requested for localhost, found "localhost"
An untrusted client certificate was returned for "david.maw@unisys.com"
A connection has been made, worker started, sending 'Hello from server'
Received Hello from client
Sending 'Goodbye from server' and listening for client messages
Waited 1 seconds
Received 'Hello again from client'
Recv returned notification that SSL shut down
Received plaintext 'First block of unencrypted data from client'
Waited 4 seconds
Initial receive timed out, retrying
Waited 2 seconds
Received plaintext 'Second block of unencrypted data from client'
Waited 3 seconds
socket shutting down
Exiting worker

Listening for client connections, press enter key to terminate.

Client completed.
Initiating a client instance for testing.

Additional test clients initiated, press enter key to terminate server.

Server certificate requested for usmv-dgm-home, found "SSLStream Testing"
An untrusted client certificate was returned for "david.maw@unisys.com"
A connection has been made, worker started, sending 'Hello from server'
Waited 63 seconds
Received Hello from client
Sending 'Goodbye from server' and listening for client messages
Waited 1 seconds
Received 'Hello again from client'
Recv returned notification that SSL shut down
Received plaintext 'First block of unencrypted data from client'
Waited 4 seconds
Initial receive timed out, retrying
Waited 2 seconds
Received plaintext 'Second block of unencrypted data from client'
Waited 3 seconds
socket shutting down
Exiting worker

Listening for client connections, press enter key to terminate.

A debug build can be easily modified to show UI to display the details of the received client certificate, if there is one, to make it display the certificate, change the sample server ClientCertAcceptable function (see below) to include:

C++
if (pCertContext)
   ShowCertInfo(pCertContext, "Client Certificate Returned");

If all you need is a fairly simple server, just amend this code to do what you need and ignore the gory detail below. If you want to exert more control or know more, read on...

Controlling Certificates

There are a couple of things you can do to give you more control over the connection, you can provide a ClientCertAcceptable function to evaluate the certificate provided by the client (if any) and reject it if you do not like it (which closes the connection). You can also provide a SelectServerCert function to allow you to control what server certificate is sent to the client. The code to use these looks like this:

C++
Listener->SelectServerCert = SelectServerCert;
Listener->ClientCertAcceptable = ClientCertAcceptable;

Examples of both of these are in the sample source, but you need not use either of them, if you do, you can assign lambda expressions, or, as the sample does, define functions with appropriate parameters, and just assign those.

The sample SelectServerCert function makes use of CertFindServerByName in order to select a matching certificate, that, in its turn, calls MatchCertHostName in order to examine the certificate SAN or Subject fields, and then it calls HostNameMatches to validate that a name from the certificate matches the hostname being requested.

TCP Listener (CListen)

One of the first things you usually need to do in a server is figure out what port you'll be listening on for connections, and tell Winsock to start listening on that port. It's complicated a bit by what protocols you might want to listen on (TCP over IPv4 and/or IPv6 usually) and how you want to handle blocking a thread while waiting for the next socket to open (the sample handles this by running the "listen" loop on a separate thread).

That's all handled in a CListen class which implements a multi threaded listener. It can do some simple things when a connection is opened, such as start up a thread to process it, but then it needs to be told what the server actually does. The lambda expression passed into BeginListening is what provides that information, but normally there's a CSSLServer in the path to add SSL capability on top of the basic TCP transport. The lambda expression in the call on BeginListening gets passed an ISocketstream interface it can use to send and receive messages and a CSSLServer can provide an SSL capable version of that interface, as opposed to a CPassiveSock which can only provide an unencrypted one.

TCP Server (CPassiveSock)

The TCP server connection is defined by a class called CPassiveSock, defined in PassiveSock.h and declared in PassiveSock.cpp - this class implements a few simple functions (Disconnect, Send and Receive, for example) that abstract away the details of interfacing with WinSock so that the caller need not deal with them. Notice there's no "connect"; that's because this class only deals with already-open sockets delivered as a result of a connection made by a client, mediated by a CListen object.

SSL Server (CSSLServer)

The code to implement the SSL connection is within the CSSLServer object. Its constructor needs a ISocketstream to provide it with a channel over which to communicate. Once constructed, you call its Initialize method, and once that completes successfully, you have an open channel over which you will communicate. The CSSLServer object is SSLServer.cpp and is fairly heavyweight, so some of the work is offloaded to code in SSLHelper.cpp.

The transition from a simple TCP server to a TCP server with SSL capability is handled by a CSSLServer object. A CListener takes the SOCKET object delivered by Winsock accept and requests a CSSLServer for it, the CSSLServer creates a CPassiveSock from the SOCKET and then attaches that to the CSSLServer in order to create a server-side SSL socket. The CSSLServer makes available an ISocketStream interface which is passed to the lambda function that was originally passed into BeginListening.

The ISocketStream interface exists mostly so that it is possible to choose NOT to use SSL and still have Clistener::Work use the same interface (because the server likely does not change its behavior whether the channel is encrypted or not). ISocketStream also abstracts away the details of the implementation so the lambda cannot mess with them, either accidentally or on purpose. There's no logic to support running completely without SSL in the example, but if you wanted to do it, it's a fairly simple change to CListener to not use CSSLServer at all.

What About SCHANNEL?

The actual interface to the Microsoft SCHANNEL implementation of SSL is a bit arcane, partly because the same interface (the Security Support Provider Interface - SSPI) is used to reach a replaceable provider which might do many different things. So pretty much the first thing you must do is get a PSecurityFunctionTable object pointed at an SSPI implementation by calling InitSecurityInterface. Once you do that, around 20 methods become available, such as EncryptMessage or QuerySecurityContext.

Many of the parameters to the various SSPI functions have no meaning in the SCHANNEL implementation and must be NULL.

The SSPI interface is designed to be consumed by C rather than requiring C++, which makes it rather cumbersome to use. It also provides pretty tight control over buffer usage, which furthers adds to the complexity.

The Basic SSL Handshake

In order to start an SSL session, the first step is to establish a communication channel between the client and the server (a TCP connection in most cases) and then perform an "SSL Handshake" over the channel (Google "SSL Handshake", there are lots of good explanations). The handshake is initiated by the client sending a message to the server, the server replies and provides a certificate, the client responds (possibly with a certificate of its own), cipher suites are negotiated, SSL/TLS levels are negotiated, and so on. After both ends have sent their messages, a secure connection is made. The details of this handshake either from a client or server perspective are what SCHANNEL implements. In this example, the server implementation is in CSSLServer and the client's is in CSSLClient.

What the Caller Provides

The SSPI (SCHANNEL) caller has to provide a means to send and receive messages, some memory buffers, and a state machine to handle stepping through the handshake until SSPI reports that the handshake is complete or has failed. You can see this logic in CSSLServer::SSPINegotiateLoop, which basically just keeps calling the SSPI AcceptSecurityContext procedure until it returns SEC_E_OK. Other return values indicate a variety of things, such as a need for more data, a response that should be sent, or a failure.

Once the handshake is complete, you have an encrypted connection that can be used for send or receive operations. Send ultimately calls SSPI EncryptMessage in a similar loop to the one in SSPINegotiate, and Receive calls SSPI DecryptMessage in the same way. The article by Leon Finker referred to below has a nice overview of this, and some further references if you're interested in more details.

Controlling SSL Protocol Versions

SSL has evolved through many protocol versions over the years: SSL 1.0, 2.0, 3.0, then TLS 1.0, 1.1 and 1.2 (which is the latest in general use at the time of writing). Do not use anything earlier than TLS 1.0 if you can possibly avoid it. SSL 3.0 and earlier have been compromised in a variety of ways. The SSL handshake is supposed to negotiate mechanisms (like the latest protocol level, or encryption algorithms) that both client and server can handle. This sample uses only TLS 1.2 but if you want to change that, look for a setting of SchannelCred.grbitEnabledProtocols (it will be something like SP_PROT_TLS1_2_CLIENT) and change it to an earlier version if you need that. 

WARNING: Changing to TLS 1.3 is non-trivial and will probably take a new version of the software which doesn't exist as of February 2024. TLS 1.3 requires (at least) an SCH_CREDENTIALS parameter to AcquireCredentialsHandle instead of the SCHANNEL_CRED that's currently used and the handling of renegotiating the TLS session after the initial handshake. Unfortunately, the existing code specifically does not handle that (the SEC_I_RENEGOTIATE case).

Some Notes on Implementation

Originally, a number of files were duplicated between the client and server, these tended to differ, but only slightly, so in September of 2018 I bit the bullet and moved all the shared files into the server part of the tree and updated the client to point to them, in July 2019 they were moved again to a common ..\Common\ folder. The only thing to look out for is the fact that #include first looks in the same directory as the source file, so when StreamClient.cpp uses CertRAII.cpp for example it picks up ..\Common\CertRAII.cpp and THAT references pch.h - which will be ..\SteamClient\pch.h. In 2022 (the 2.1.4 release) the client and server code was changed to permit coexistence in a single executable.

MFC used to be required to build the solution but as of 2022 (the 2.1.4 release) it is not.

This implementation demonstrates how to stop using SSL and still use the underlying connection for unencrypted messages. To demonstrate this it sends an unencrypted message from the client to the server. I've never seen a real implementation that works like this (turning off SSL on an active connection, turning it ON is common, it's turning it off that is rare). So this code does not support sending unencrypted messages from the server to the client - if you have a valid use case for this it's relatively easy to implement, I just haven't, so ask.

The timeout handling might seem overly complex. It's basically set up so a single receive times out at the same time regardless of the number of segments TCP might choose to deliver the message in. Note also that callers can elect to try again if a receive times out (the sample shows this in use). 

Source and Object Files

The source files are all in Codeproject and a release build of the examples is usually stored with the latest release which is at https://github.com/david-maw/StreamSSL/releases/latest (earlier releases are there too).

Open Issues

  1. Buffer sizes are all fixed (see MaxMsgSize which is currently set to 16000 -- the current SCHANNEL limit is 16384); it would be better if they were variable.
  2. It might be helpful to make use of the Async support in modern C++ to move the threading logic out of this code and take advantage of services provided by the system instead.
  3. There's no implementation support for TLS 1.3.

Acknowledgements

The SSL portion of this code was inspired by an article by Leon Finker published on CodeProject in 2003 (it is here) which I recommend as a starting point for someone who wants to know more about SSL and SCHANNEL. It was also inspired by an old Microsoft SDK sample (which I can no longer find) showing how to call SCHANNEL.

The code to create a certificate is based on the sample "How to create a self-signed certificate with CryptoAPI" code in the blog of Alejandro Campos Magencio, it can be found here.

In July and August 2019 Thomas Hasse provided a number of fixes, a 64 bit build, and some cleanup code, as well as the impetus to refactor some of the code.

In March 2022 Jac Goudsmit made changes to permit client and server code to coexist, removed the last of the MFC references and generally improved the build.

History

This code is maintained in GitHub at https://github.com/david-maw/StreamSSL I usually update the code on GitHub first, and then this article, once a significant change is made or a few accumulate. So, if you want the absolute latest source, look on GitHub at https://github.com/david-maw/StreamSSL/releases. Here's a brief timeline of major changes: 

  • June 2015 Original article published
  • June 27. 2015 - Source added to GitHub
  • July 6, 2015 - Provided the client and server code with more control over the certificates being used as well as the ability to reject them
  • January 2016 - Added code to create a client certificate if none is available and use the host name rather than "localhost" by default
  • February 2016 - Updated the source so it compiles with the VS2015 toolchain as well as VS2010
  • April 9, 2016 - Updated the code to handle SAN and Wildcard certificates, require a VS 2015 Unicode build
  • September 15, 2016 - Fixed some grammar and typos
  • August 26, 2018 - New keywords and fixed confusing timing code (it worked, but by accident)
  • September 2018 - RAII throughout, much cleanup and a new certificate cache
  • June 2019 - Remove all the w4 warnings and set warnings as errors, implement correct SSL shutdown.
  • July 2019 - Update and refactoring 
    • Enhance sample to show stopping SSL but continuing unencrypted connection.
    • Use more modern C++ (use nullptr, const and true/false, eliminate "(void)"...)
    • Refactor CActiveSock and CPassiveSock to inherit from a commom CBaseSock
    • SendPartial and RecvPartial are no longer public, Send and Recv replace them
    • The precompiled header mechanism is changed to match current practice using pch.h
    • Shared files (cpp and h) are moved to a shared folder
    • Add a 64 bit build
    • Eliminated the CWorker class - it didn't do much and made the code harder to understand
  • August 2019 - release 2.1.0
    • Split solution into 2 lib projects and 2 samples which use them (in a "Samples" folder). Users who choose to can then distribute lib and h files without distributing sources.
    • Various updates to modern C++ including converting to VS2019 and using the latest C++ conformance (which is parts of C++20) in builds.
    • The codebase now supports versioning and exports a GetVersionText method that callers can use (the samples also use the information in AppVersion.h to create version resources).
    • A license file is added to the solution, and referred to in the readme.
    • The precompiled header names are changed to use what a newly created project would (pch.h).
  • February 2019 - release 2.1.1
    • Allow for an ANSI (meaning multibyte, not Unicode) caller, eliminate the use of MFC.
    • Add missing x64 configurations.
    • Add a minimal sample client program to show how to connect to a server with little code
  • October 2020 - release 2.1.2
    • This corrects a syntax error when compiling with VS2019 v 16.8 or later.
    • Note that this release has a bug in it causing it to self-identify as 2.1.1
  • March 2021 - release 2.1.3
    • The same as 2.1.2, but with the version set to 2.1.3 (instead of the erroneous 2.1.1)
  • June 2022 - release 2.1.4
    • SSLServer and SSLClient can now coexist in a single code file
    • Two projects (SSLClient and SSLServer) combined into StreamSSL
    • MFC is no longer needed

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Retired Unisys Corporation
United States United States
I programmed professionally for several decades, first on Burroughs mainframes, then, after it became Unisys, on Windows systems, first using C++ and COM on Win32, then C# on .NET on desktop and phone platforms. My professional life eventually did not involve much programming and now I'm retired, so programming is more of a personal passion.

Comments and Discussions

 
QuestionAny plans to update the server to handle TLS 1.3? Pin
BrianCharles29-Feb-24 6:08
BrianCharles29-Feb-24 6:08 
AnswerRe: Any plans to update the server to handle TLS 1.3? Pin
David Maw4-Mar-24 9:17
David Maw4-Mar-24 9:17 
QuestionHow make the same to support TLS v1.3 Pin
sanjay m 20225-Oct-23 17:55
sanjay m 20225-Oct-23 17:55 
AnswerRe: How make the same to support TLS v1.3 Pin
David Maw4-Mar-24 9:22
David Maw4-Mar-24 9:22 
QuestionLNK1104 cannot open file '...\StreamSSL-2.1.4\Win32\Debug\StreamSSL.lib' Pin
Member 1589438219-Apr-23 21:01
Member 1589438219-Apr-23 21:01 
AnswerRe: LNK1104 cannot open file '...\StreamSSL-2.1.4\Win32\Debug\StreamSSL.lib' Pin
David Maw22-Apr-23 15:59
David Maw22-Apr-23 15:59 
QuestionCMake instead of VS solutions Pin
Brian Sullender28-Jan-23 1:34
Brian Sullender28-Jan-23 1:34 
AnswerRe: CMake instead of VS solutions Pin
David Maw28-Jan-23 8:02
David Maw28-Jan-23 8:02 
GeneralRe: CMake instead of VS solutions Pin
Brian Sullender28-Jan-23 13:45
Brian Sullender28-Jan-23 13:45 
GeneralRe: CMake instead of VS solutions Pin
David Maw29-Jan-23 9:27
David Maw29-Jan-23 9:27 
QuestionSupport Windows XP Pin
Member 148118572-Jan-23 21:40
Member 148118572-Jan-23 21:40 
AnswerRe: Support Windows XP Pin
David Maw3-Jan-23 7:09
David Maw3-Jan-23 7:09 
QuestionHow do you debug StrealSSL apps ? Pin
jean-paul lamontre11-Jun-22 4:04
jean-paul lamontre11-Jun-22 4:04 
AnswerRe: How do you debug StrealSSL apps ? Pin
David Maw11-Jun-22 6:06
David Maw11-Jun-22 6:06 
GeneralRe: How do you debug StrealSSL apps ? Pin
jean-paul lamontre11-Jun-22 6:12
jean-paul lamontre11-Jun-22 6:12 
GeneralRe: How do you debug StrealSSL apps ? Pin
David Maw11-Jun-22 11:54
David Maw11-Jun-22 11:54 
GeneralRe: How do you debug StrealSSL apps ? Pin
jean-paul lamontre11-Jun-22 21:39
jean-paul lamontre11-Jun-22 21:39 
Questionissue MSB8041 when compile with VS2019 Pin
jean-paul lamontre1-Jun-22 22:32
jean-paul lamontre1-Jun-22 22:32 
AnswerRe: issue MSB8041 when compile with VS2019 Pin
David Maw3-Jun-22 18:47
David Maw3-Jun-22 18:47 
GeneralRe: issue MSB8041 when compile with VS2019 Pin
David Maw3-Jun-22 19:12
David Maw3-Jun-22 19:12 
GeneralRe: issue MSB8041 when compile with VS2019 Pin
Member 133297347-Jun-22 21:32
Member 133297347-Jun-22 21:32 
GeneralRe: issue MSB8041 when compile with VS2019 Pin
David Maw8-Jun-22 7:50
David Maw8-Jun-22 7:50 
GeneralRe: issue MSB8041 when compile with VS2019 Pin
jean-paul lamontre11-Jun-22 3:03
jean-paul lamontre11-Jun-22 3:03 
GeneralRe: issue MSB8041 when compile with VS2019 Pin
David Maw11-Jun-22 5:36
David Maw11-Jun-22 5:36 
QuestionWhat about pre-shared keys? Pin
Wilhelm Nöker14-Jan-21 2:54
Wilhelm Nöker14-Jan-21 2:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.