Server Side Session Cache in OpenSSL

At work (information posed with permission from my employer) we’ve been looking into session caching with OpenSSL. We started this by looking at the server and found that by default OpenSSL will enable and use a session cache when acting as the server. However, there are two major things we found in how it works.

Sessions will only be cached server side upon SSL_Shutdown. Meaning SSL_Shutdown (or similar) must be called otherwise the session will never be cached. For shutdown the or similar is for cases where the caller did not cleanly shutdown but is no longer connected. A bad client or other network issue could cause this situation.

Technically you could call SSL_Shutdown anyway and it should do the right thing in this case. However, I’m worried that since SSL_Shutdown could block that this could stop program execution. You can use SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN); to mark the connection as having been shutdown and the session will be added to the cache. However, this may or may not be desirable; you might not want to cache on improper disconnection.

The other big thing we found is SSL_CTX_set_session_id_context(…). According to the documentation this is required when using client certificates. I’m not sure why this isn’t set by default by OpenSSL…

I’ve written a simple sample server application for testing server side sessions. First we need to generate an SSL certificate and remove the password from it so it can be loaded without interaction.

$ openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 999
$ openssl rsa -in key.pem -out key.pem.new
$ mv key.pem.new key.pem

Next is the sample application.

#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>

static int ssl_session_ctx_id = 1;

int main(int argc, char **argv)
{
    SSL                *ssl    = NULL;
    SSL_CTX            *ctx    = NULL;
    int                 servfd = -1;
    int                 clntfd = -1;
    struct sockaddr_in  serv;
    struct sockaddr_in  clnt;
    socklen_t           socksize;
    char                buf[128];
    int                 ret;
    int                 clean_disconnect;

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

    /* Setup the socket to listen for incoming connections. */
    memset(&serv, 0, sizeof(serv));
    serv.sin_family      = AF_INET;
    serv.sin_addr.s_addr = htonl(INADDR_ANY);
    serv.sin_port        = htons(4433);
    servfd               = socket(AF_INET, SOCK_STREAM, 0);
    if (servfd == -1) {
        printf("Failed to create socket\n");
        return 0;
    }
    if (bind(servfd, (struct sockaddr *)&serv, sizeof(struct sockaddr)) != 0) {
        printf("Failed to bind\n");
        goto cleanup;
    }
    if (listen(servfd, 512) != 0) {
        printf("Failed to listen\n");
        goto cleanup;
    }

    /* Generate an SSL server context. */
    ctx = SSL_CTX_new(SSLv23_server_method());
    if (ctx == NULL) {
        printf("Failed to create SSL server context\n");
        goto cleanup;
    }

    /* Set some options and the session id.
     * SSL_OP_NO_SSLv2: SSLv2 is insecure, disable it.
     * SSL_OP_NO_TICKET: We don't want TLS tickets used because this is an SSL server caching example.
     *                   It should be fine to use tickets in addition to server side caching.
     */
    SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2|SSL_OP_NO_TICKET);
    SSL_CTX_set_session_id_context(ctx, (void *)&ssl_session_ctx_id, sizeof(ssl_session_ctx_id));

    /* Load certificate. */
    if (SSL_CTX_use_certificate_file(ctx, "./cert.pem", SSL_FILETYPE_PEM) != 1) {
        printf("Failed to load cert.pem\n");
        goto cleanup;
    }
    if (SSL_CTX_use_PrivateKey_file(ctx, "./key.pem", SSL_FILETYPE_PEM) != 1) {
        printf("Failed to load key.pem\n");
        goto cleanup;
    }
    if (!SSL_CTX_check_private_key(ctx)) {
        printf("Failed to validate cert\n");
        goto cleanup;
    }

    /* Wait for and handle connections as they come in. */
    while (1) {
        socksize = sizeof(struct sockaddr_in);
        clntfd   = accept(servfd, (struct sockaddr *)&clnt, &socksize);
        if (clntfd == -1) {
            continue;
        }
        ssl = SSL_new(ctx);
        SSL_set_fd(ssl, clntfd);

        SSL_accept(ssl);

        /* Do any processing... */

        /* Determine if the client disconnected cleanly or not. */
        clean_disconnect = 1;
        ret = SSL_read(ssl, buf, sizeof(buf));
        if (ret <= 0) {
            switch (SSL_get_error(ssl, ret)) {
                case SSL_ERROR_NONE:
                case SSL_ERROR_ZERO_RETURN:
                    break;
                default:
                    clean_disconnect = 0;
                    break;
            }
        }

        if (clean_disconnect) {
            printf("Clean Disconnect do shutdown...\n");
            SSL_shutdown(ssl);
        } else {
            printf("Unclean Disconnect inform that the connection was shutdown...\n");
            SSL_set_shutdown(ssl, SSL_SENT_SHUTDOWN|SSL_RECEIVED_SHUTDOWN);
        }
        SSL_free(ssl);
        close(clntfd);
    }

cleanup:
    if (ctx != NULL)
        SSL_CTX_free(ctx);
    if (servfd != -1)
        close(servfd);
    return 0;
}

Compiling the app:

$ gcc main.c -Wno-deprecated-declarations -lssl -lcrypto -o server

We’ll use the following to test the server session reuse:

$ openssl s_client -connect localhost:4433 -reconnect -no_ticket | grep Session-ID
depth=0 /C=US/ST=Some-State/O=Internet Widgits Pty Ltd/CN=host
verify error:num=18:self signed certificate
verify return:1
depth=0 /C=US/ST=Some-State/O=Internet Widgits Pty Ltd/CN=host
verify return:1
    Session-ID: D78F50100DA1F4582C7CE024280376A86AA796A77B84931CD1C38A06E8FBB0C3
    Session-ID-ctx: 
    Session-ID: D78F50100DA1F4582C7CE024280376A86AA796A77B84931CD1C38A06E8FBB0C3
    Session-ID-ctx: 
    Session-ID: D78F50100DA1F4582C7CE024280376A86AA796A77B84931CD1C38A06E8FBB0C3
    Session-ID-ctx: 
    Session-ID: D78F50100DA1F4582C7CE024280376A86AA796A77B84931CD1C38A06E8FBB0C3
    Session-ID-ctx: 
    Session-ID: D78F50100DA1F4582C7CE024280376A86AA796A77B84931CD1C38A06E8FBB0C3
    Session-ID-ctx: 
    Session-ID: D78F50100DA1F4582C7CE024280376A86AA796A77B84931CD1C38A06E8FBB0C3
    Session-ID-ctx: 
^C

The ^C is CTRL+C used to kill the app (it just sits there waiting). Notice how the same Session-ID is used for each connection. This tells us the sever (and client) are reusing the cached session. Which leads into the server side output:

$ ./server 
Clean Disconnect do shutdown...
Clean Disconnect do shutdown...
Clean Disconnect do shutdown...
Clean Disconnect do shutdown...
Clean Disconnect do shutdown...
Unclean Disconnect inform that the connection was shutdown...