Client Side Session Cache in OpenSSL

Building on Server Side Session Cache in OpenSSL we need to deal with the Client side. The OpenSSL documentation for SSL_CTX_set_session_cache_mode has an option for client caching. However, it states that, “the application must select the session to be reused by using the SSL_set_session(3) function.” It also states that the client cache is not enabled by default.

Due to the listed limitations of the internal client cache it’s a better idea to maintain the cache externally from OpenSSL. A good way to handle the cache is to use a hastable. The key is a combination of the host and port, “host:port” as a string. The value is the associated SSL_SESSION pointer. This allows for O(1) lookup of stored sessions, it also allows for wrapping access in a mutex in multi-thread application. Further, a hashtable implementation could set an upper limit on the maximum number of sessions it will hold or it could have some sort of expiration as to how long it will allow a session to be reused. Basically using an external cache allows a lot of flexibility.

For the following client example I am not using a hashtable (even though I said it was a good idea). This is a simple example showing how to store and set a client session and verify that it was indeed reused.

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

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

int main(int argc, char **argv)
{
    SSL                *ssl     = NULL;
    SSL_CTX            *ctx     = NULL;
    SSL_SESSION        *session = NULL;
    int                 clntfd  = -1;
    struct sockaddr_in  clnt;
    size_t              i;

    /* Initialize OpenSSL. */
    SSL_library_init();
    SSL_load_error_strings();
    OpenSSL_add_all_algorithms();

    /* Generate an SSL client context. */
    ctx = SSL_CTX_new(SSLv23_client_method());
    if (ctx == NULL) {
        printf("Failed to create SSL client contextn");
        goto cleanup;
    }

    /* Set some options and the session id.
     * SSL_OP_NO_SSLv2: SSLv2 is insecure, disable it.
     */
    SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2);

    /* We're going to connect and disconnect multiple times. */
    for (i=0; i<5; i++) {
        /* Setup the socket to connect to localhost. */
        memset(&clnt, 0, sizeof(clnt));
        clnt.sin_family      = AF_INET;
        clnt.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
        clnt.sin_port        = htons(4433);
        clntfd               = socket(AF_INET, SOCK_STREAM, 0);
        if (clntfd == -1) {
            printf("Failed to create socketn");
            return 0;
        }
        if (connect(clntfd, (struct sockaddr *)&clnt, sizeof(struct sockaddr)) != 0) {
            printf("Failed to connectn");
            goto cleanup;
        }

        /* Create an ssl object and bind it to our socket. */
        ssl = SSL_new(ctx);
        SSL_set_fd(ssl, clntfd);

        /* If we have a stored session set it as the ssl's session so it will be reused. */
        if (session != NULL) {
            SSL_set_session(ssl, session);
        }

        /* Connect via SSL. */
        if (SSL_connect(ssl) < 1) {
            printf("Failed to connect (SSL)n");
            goto cleanup;
        }

        /* Was the stored session reused? */
        if (SSL_session_reused(ssl)) {
            printf("REUSED SESSIONn");
        } else {
            printf("NEW SESSIONn");
        }

        /* Save client session. Sever session is saves as part of shutdown.
         * The saved session is destroyed and stored from the ssl object
         * before shutdown in case a renegotation took place and a new
         * session was created. */
        if (session != NULL)
            SSL_SESSION_free(session);
        session = SSL_get1_session(ssl);

        /* Disconnect. */
        SSL_shutdown(ssl);
        SSL_free(ssl);
        ssl = NULL;
        close(clntfd);
        clntfd = -1;
    }

cleanup:
    if (session != NULL)
        SSL_SESSION_free(session);
    if (ssl != NULL)
        SSL_free(ssl);
    if (clntfd != -1)
        close(clntfd);
    if (ctx != NULL)
        SSL_CTX_free(ctx);
    return 0;
}

In this example we only load a stored session if one has already been saved. SSL_set_session(…) is the key function for this. The session is only stored if it wasn’t previously. SSL_get1_session(…) can pull the session out of the ssl object for use later. If we use SSL_get1_session(…) we need a call to SSL_SESSION_free(…) to destroy the session that was returned.

Build and run the above code (with the server from the above referenced server session cache post) and we’ll see the below output.

$ gcc main.c -Wno-deprecated-declarations -lssl -lcrypto -o client
$ ./client
NEW SESSION
REUSED SESSION
REUSED SESSION
REUSED SESSION
REUSED SESSION

The client and server save the session differently. The client pulls out and stores the session explicitly into an external cache. The server relies on an internal cache which in turn relies on the SSL_shutdown(…) call. If the client does not call SSL_shutdown the server will still store the session due to the use of SSL_set_shutdown(…). This allows a poorly behaved client (one that does not call SSL_shutdown when it needs to disconnect) to still reuse session with the server. If that call is removed from the server then a session will never be reused with a poorly behaving client.