The C++ framework for developing highly scalable, high performance servers on Windows platforms.

Example Servers - SChannel Echo Server

This example shows you how to build an SSL enabled server using Microsoft's SChannel. This example uses our SChannel Tools Library to integrate the SChannel code with our asynchronous I/O requirements. The basic structure of the server is very similar to the Basic Echo Server example and you should go and read about that first and have a good understanding of how everything fits together. This document will only cover the differences between the Basic Echo Server example and this example.

This example requires the "SSL/TLS (SChannel)" licensing option of The Server Framework and it requires libraries that only ship with that option (see here for licensing options). You can always download the latest version of this example from here; and although you will need the correct libraries to be able to build it you can look at the example code and see how it works and perhaps get ideas from it. A compiled, unicode release, build of this example is available on request if you require it for performance analysis of the framework.

This server uses a custom command line parsing class which extends the standard command line parsing class and adds support for SChannel SSL options, -storeName, certificateName, -useMachineStore -useDefaults and -requireClientCerts.

      try
       {
          CSChannelServerCommandLine commandLine;

          if (commandLine.Parse())
          {
             CCredentials credentials(
                CreateCredentials(
                   commandLine.CertificateStoreName(),
                   commandLine.CertificateName(),
                   commandLine.UseMachineStore(),
                   commandLine.UseDefaultsToLocateCertificate()));

             CSharedCriticalSectionFactory lockFactory(
                commandLine.NumberOfLocks(),
                CSharedCriticalSectionFactory::DefaultMultiplier,
                commandLine.SpinCount());

The first thing that we do differently is to create our SChannel credentials by creating an instance of JetByteTools::SChannel::CCredentials this class contains the certificate that we use to identify the server and also determines the encryption algorithms and other SChannel configuration options used. Its purpose is similar to that of the SSL Context that is used in the OpenSSL examples. This needs to be passed in to the code that performs the SSL work and it's created in the CreateCredentials() function, shown below. This is all standard SChannel stuff and it's adapted from the standard SChannel examples. You should adjust this as you see fit to configure the SChannel connection to your requirements. This function and the other SChannel setup code that lives in ServerMain.cpp is purely to demonstrate how you could configure the system to work, it's not, in any way, an indication of HOW you SHOULD configure the system to work.

 static CCredentials CreateCredentials(
    const _tstring &storeName,
    const _tstring &certificateName,
    const bool useMachineStore,
    const bool attemptDefaults)
 {
    PCCERT_CONTEXT pCertContext = GetCert(storeName, certificateName, useMachineStore);

    if (!pCertContext && attemptDefaults)
    {
       const _tstring computerName = GetComputerName();

       if (computerName != certificateName || storeName != _T("MY"))
       {
          pCertContext = GetCert(storeName, computerName, false);

          if (!pCertContext)
          {
             pCertContext = GetCert(storeName, computerName, true);
          }
       }

       if (!pCertContext)
       {
          const _tstring userName = GetUserName();

          if (userName != certificateName || storeName != _T("MY"))
          {
             pCertContext = GetCert(storeName, userName, false);

             if (!pCertContext)
             {
                pCertContext = GetCert(storeName, computerName, true);
             }
          }
       }
    }

    if (!pCertContext)
    {
       throw CException(_T("CreateCredentials()"), _T("No certificate available"));
    }

    CCredentials::Data data;

    data.cCreds = 1;
    data.paCred = &pCertContext;

    CCredentials credentials(data, SECPKG_CRED_INBOUND);

    if(pCertContext)
    {
       CertFreeCertificateContext(pCertContext);
    }

    return credentials;
 }

The code above requires that you have the appropriate certificates installed in your Certificate Store. You then select which store to use and which certificates to use by passing the appropriate command line arguments. At the point where we set up the CCredentials::Data you may wish to fine tune the encryption algorithms or SSL options available, see the MSDN SChannel documentation for details.

Once the credentials have been created the rest of the server setup code is pretty standard. The only thing that differs between this example and the standard Echo Server example is that we have to use sequenced sockets due to how the SChannel server works. The server class requires an instance of JetByteTools::Socket::IAllocateSequencedStreamSockets rather than just an instance of JetByteTools::Socket::IAllocateStreamSockets as it needs to add its own sequence id's to the sockets used.

             CSharedCriticalSectionFactory lockFactory(
                commandLine.NumberOfLocks(),
                CSharedCriticalSectionFactory::DefaultMultiplier,
                commandLine.SpinCount());

             CIOPool ioPool(
                commandLine.NumberOfIOThreads(),
                commandLine.DisplayDebug());

             ioPool.Start();

             CSequencedStreamSocketAllocator socketAllocator(
                lockFactory,
                commandLine.SocketPoolSize(),
                commandLine.DisplayDebug());

             CBufferAllocator bufferAllocator(
                commandLine.BufferSize(),
                commandLine.BufferPoolSize(),
                commandLine.DisplayDebug());

             const CFullAddress address(
                commandLine.Server(),
                commandLine.Port());

             const ListenBacklog listenBacklog = commandLine.ListenBacklog();

             const unsigned long recvBufferSize = commandLine.RecvBufferSize();

             const unsigned long sendBufferSize = commandLine.SendBufferSize();

             const CStreamSocketConnectionManager::ZeroByteReadConfiguration zeroByteReadConfiguration = commandLine.GetZeroByteReadConfiguration();

             CSocketServer server(
                credentials,
                commandLine.RequireClientCerts(),
                address,
                listenBacklog,
                ioPool,
                socketAllocator,
                bufferAllocator,
                recvBufferSize,
                sendBufferSize,
                zeroByteReadConfiguration);

Our CSocketServer class should look pretty familiar, but you'll notice that it implements methods from the JetByteTools::SChannel::CStreamSocketServerCallback class rather than just from JetByteTools::Socket::CStreamSocketServerCallback. This provides it with the option of dealing with the following events which are unique to SChannel enabled servers.

       virtual void OnSecureConnectionEstablished(
          JetByteTools::Socket::IStreamSocket &socket,
          const JetByteTools::Socket::IAddress &address);

       virtual void OnSecureSocketReleased(
          JetByteTools::Win32::IIndexedOpaqueUserData &userData);

       virtual void FatalError(
          JetByteTools::Socket::IStreamSocket &socket,
          const SECURITY_STATUS status);

       virtual bool Verify(
          JetByteTools::Socket::IStreamSocket &socket,
          const JetByteTools::Win32::_tstring &targetName,
          const CContext &context);

OnSecureConnectionEstablished() is called before the normal OnConnectionEstablished() and OnSecureSocketReleased() is called after the normal OnSocketReleased(). They give you the chance to process secure connections differently to non secure connections. We do nothing special in these callbacks but implement them to output a debug trace when they're called. FatalError() is called if things go horribly wrong with the SChannel data stream and Vefify() is called to allow the server to verify peer's certificate details if required.

In fact, if you ignore the callbacks that are implemented purely to provide informational debug tracing as the server runs then you'll find that the only places where this server differs from the standard server example is in its constructor and where it validates the client's certificate.

 CSocketServer::CSocketServer(
    CCredentials &credentials,
    const bool verifyPeer,
    const IFullAddress &address,
    const ListenBacklog listenBacklog,
    IIOPool &pool,
    IAllocateSequencedStreamSockets &socketAllocator,
    IAllocateBuffers &bufferAllocator,
    const SocketBufferSize recvBufferSize,
    const SocketBufferSize sendBufferSize,
    const ZeroByteReadConfiguration zeroByteReadConfiguration)
    :  JetByteTools::SChannel::CStreamSocketServer(credentials, verifyPeer, address, listenBacklog, *this, pool, socketAllocator, bufferAllocator, recvBufferSize, sendBufferSize, zeroByteReadConfiguration)
 {

 }

As you can see from the above, the constructor differences are purely to do with passing the credentials and verifyPeer</code flag through to the SSL enabled base class.

A major part of our design descisions for the SChannel Tools Library were to make sure that the integration of SSL into a server was painless and didn't affect the design of the server. In fact, if you use the correct constructors for the JetByteTools::SChannel::CStreamSocketServer class you use the same server for both SSL enabled and clear-text servers.

bool CSocketServer::Verify(
   IStreamSocket &socket,
   const _tstring &targetName,
   const CContext &context)
{
   bool ok = true;

   Output(_T("CSocketServer::Verify() - do manual certificate verification here"));

   CERT_CONTEXT *pCertContext = context.GetRemoteCertificate();

   if (pCertContext)
   {
      Output(_T("Verify client certificate here"));

      DisplayCertChain(pCertContext);

      // Attempt to validate client certificate.

      const SECURITY_STATUS status = VerifyClientCertificate(
         pCertContext,
         targetName);

      if (status != SEC_E_OK)
      {
         // The client certificate did not validate correctly. At this
         // point, we cannot tell if we are connecting to the correct
         // client, or if we are connecting to a "man in the middle"
         // attack server.

         // It is therefore best if we abort the connection.

         Output(_T("Error ") + GetLastErrorMessage(status));

         ok = false;
       }

      ::CertFreeCertificateContext(pCertContext);
   }
   else
   {
      Output(_T("Client has no certificate"));

      if (RequiresPeerVerification(socket))
      {
         Output(_T("Client certificate is required"));

         ok = false;
      }
   }

   return ok;
}

The certificate validation code is straight forward SChannel code and should be adjusted to validate the client certificate in the most appropriate way for your usage requirements.

Generated on Sun Sep 12 19:06:45 2021 for The Server Framework - v7.4 by doxygen 1.5.3