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

Example Servers - OpenSSL Echo Server

This example shows you how to build an SSL enabled server using OpenSSL. This example uses our OpenSSL Tools Library to integrate the OpenSSL 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 (OpenSSL)" 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 project assumes that you have OpenSSL 0.9.8e (or later) installed and that the environment variable OPEN_SSL_ROOT is set to point to the root directory of your OpenSSL installation. If you have any problems please contact us for help. You can download an OpenSSL source tree to build against from here if required.

This server uses a custom command line parsing class which extends the standard command line parsing class and adds support for SSL options, -certificatePath and -requireClientCerts.

      try
       {
          COpenSSLServerCommandLine commandLine;

          if (commandLine.Parse())
          {
             CUsesOpenSSL usesSSL(true);

             CContext sslContext(CreateSSLContext(commandLine.CertificatePath()));

             CSharedCriticalSection lockFactory(
                commandLine.NumberOfLocks());

The first thing that we do differently is that we initialise the OpenSSL libraries by creating an instance of the CUsesOpenSSL class. This sets us up for using OpenSSL in a multi-threaded environment and cleans up resources when the object goes out of scope; it's an scope based library initialisation object.

Next we create an SSL Context which contains various SSL configuration options. This needs to be passed in to the code that performs the SSL work and it's created in the CreateSSLContext() function, shown below. This is all standard OpenSSL stuff and it's adapted from the standard OpenSSL examples. You should adjust this as you see fit to configure OpenSSL to your requirements. This function and the other OpenSSL 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 SSL_CTX *CreateSSLContext(
    const _tstring certificatePath)
 {
    SSL_METHOD *pMethod = SSLv23_method();

    SSL_CTX *pContext = SSL_CTX_new(pMethod);

    RAND_seed(rnd_seed, sizeof rnd_seed);

    const string serverPemFile = CStringConverter::TtoA(certificatePath) + "server.pem";

    if(!(SSL_CTX_use_certificate_chain_file(pContext, serverPemFile.c_str())))
    {
       throw CException(_T("CreateSSLContext()"), _T("Failed to load server certificate file: \"") + CStringConverter::AtoT(serverPemFile) + _T("\""));
    }

    SSL_CTX_set_default_passwd_cb(pContext, password_cb);

    if(!(SSL_CTX_use_PrivateKey_file(pContext, serverPemFile.c_str(), SSL_FILETYPE_PEM)))
    {
       throw CException(_T("CreateSSLContext()"), _T("Failed to load private key file: \"") + CStringConverter::AtoT(serverPemFile) + _T("\""));
    }

    DH *dh = get_dh1024();

    SSL_CTX_set_tmp_dh(pContext, dh);

    DH_free(dh);

    SSL_CTX_set_tmp_rsa_callback(pContext,tmp_rsa_cb);

    const string rootPemFile = CStringConverter::TtoA(certificatePath) + "root.pem";

    /* Load the CAs we trust*/
    if(!(SSL_CTX_load_verify_locations(pContext, rootPemFile.c_str(), 0)))
    {
       throw CException(_T("CreateSSLContext()"), _T("Failed to load root CA file: \"") + CStringConverter::AtoT(rootPemFile) + _T("\""));
    }

    if (!SSL_CTX_set_default_verify_paths(pContext))
    {
       throw CException(_T("CreateSSLContext()"), _T("Failed to set verification paths"));
    }

 #if (OPENSSL_VERSION_NUMBER < 0x00905100L)
     SSL_CTX_set_verify_depth(m_pContext,1);
 #endif

    return pContext;
 }

The code above requires that you have server.pem and root.pem files in your 'certificate path'. The test harness that ships with the example contains demonstration files and the OpenSSL documentation should explain how to create your own.

Once the context is 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 OpenSSL 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.

             CSharedCriticalSection lockFactory(
                commandLine.NumberOfLocks());

             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();

             CSocketServer server(
                sslContext,
                commandLine.RequireClientCerts(),
                address,
                listenBacklog,
                ioPool,
                socketAllocator,
                bufferAllocator);

Our CSocketServer class should look pretty familiar, but you'll notice that it implements methods from the JetByteTools::OpenSSL::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 OpenSSL enabled servers.

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

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

       virtual int Verify(
          int preverify_ok,
          X509_STORE_CTX *pContext);

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.

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 place where this server differs from the standard server example is in its constructor.

 CSocketServer::CSocketServer(
    const CContext &sslContext,
    const bool verifyPeer,
    const IFullAddress &address,
    const ListenBacklog listenBacklog,
    IIOPool &pool,
    IAllocateSequencedStreamSockets &socketAllocator,
    IAllocateBuffers &bufferAllocator)
    :  JetByteTools::OpenSSL::CStreamSocketServer(sslContext, verifyPeer, address, listenBacklog, *this, pool, socketAllocator, bufferAllocator)
 {

 }

And those differences are purely to do with passing the sslContext and verifyPeer flag through to the SSL enabled base class.

A major part of our design descisions for the OpenSSL 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::OpenSSL::CStreamSocketServer class you use the same server for both SSL enabled and clear-text servers.

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