RSA BSAFE Micro Edition Suite

Streamlined security for mobile and embedded devices

Search  Print

app_cache.c

/* $Id: app_cache.c,v 1.9 2005/02/04 01:56:38 jmckee Exp $ */
/*
 * Copyright (C) 1999-2003 RSA Security Inc. All rights reserved.
 *
 * This work contains proprietary information of RSA Security.
 * Distribution is limited to authorized licensees of RSA
 * Security. Any unauthorized reproduction, distribution or
 * modification of this work is strictly prohibited.
 */

/*
 * Includes
 */

#include "app_cache.h"

/*
 * Macro Constants
 */

/*
 * The maximum number of cache operations before an automatic flush is
 * performed
 */
#define APP_CACHE_MAX_FLUSH_COUNT 32

/* The fixed size of the session cache */
#define APP_CACHE_MAX_SIZE        10

/* The name for the session cache lock */
#define APP_CACHE_LOCK_NAME       "EXT_SESS_CACHE"

/*
 * Typedefs
 */

/* A collection of session usage values */
typedef struct cache_count_st
{
    unsigned int add_call;             
    unsigned int add_good;               
    unsigned int get_call;             
    unsigned int get_good;              
    unsigned int del_call;          
    unsigned int del_good;               
    unsigned int overflow;                    
} CACHE_COUNT;

/* Basic session structure */
struct sess_cache_st
{
    SSL_SESSION *cache[APP_CACHE_MAX_SIZE];        
    int cache_size;                        
    int op_count;    
    int cache_lock_id;                
    CACHE_COUNT count;                      
};

/*
 * Local Prototypes
 */

static int array_cache_add(SESS_CACHE *sess_cache, SSL_SESSION *session);
static int array_cache_delete(SESS_CACHE *sess_cache, SSL_SESSION *session);
static void array_cache_remove_indexed_session(SESS_CACHE *sess_cache,
    int index);
static int array_cache_flush(SESS_CACHE *sess_cache);
static SSL_SESSION *array_cache_retrieve(SESS_CACHE *sess_cache,
    SSL_SESSION *session);

/*
 * Local Functions
 */

int APP_session_cache_init(SESS_CACHE **sess_cache)
{
    int ret = 0;                 /* Function return, initialized to failure */
    SESS_CACHE *acache;

    if (sess_cache != NULL)
    {
        /* Allocate the session cache memory */
        acache = (SESS_CACHE *)Malloc(sizeof(SESS_CACHE));

        if (acache != NULL)
        {
            /* Initialize the session cache array */
            Memset(acache, 0, sizeof(SESS_CACHE));

            /* Set the cache size */
            acache->cache_size = APP_CACHE_MAX_SIZE;

            /*
             * Set up a lock identifier for multithreaded access to
             * the cache
             */
            acache->cache_lock_id = R_lockid_new(APP_CACHE_LOCK_NAME);

            /* Reassign the return pointer */
            *sess_cache = acache;

            ret = 1;
        }
    }

    return(ret);
}

int APP_session_cache_final(SESS_CACHE *sess_cache)
{
    int ret = 0;                 /* Function return, initialized to success */
    int i;                                                  /* Loop counter */

    if (sess_cache != NULL)
    {
        /* Free all sessions remaining in the cache */
        for (i = 0; i < sess_cache->cache_size; i++)
        {
            /* A NULL session indicates the end of the cache */
            if (sess_cache->cache[i] == NULL)
            {
                break;
            }

            /* Free the session in this cache location */
            SSL_SESSION_free(sess_cache->cache[i]);
        }

        /*
         * Free the application lock that was set up for the cache. Note this
         * call will free ALL application locks.
         */
        R_lockids_free();

        /* Free the session cache memory allocated by the initialization */
        Free(sess_cache);

        /* Function success code */
        ret = 1;
    }

    return(ret);
}

int APP_session_cache_stats(SESS_CACHE *sess_cache, BIO *bio)
{
    int status = 0;              /* Function return, initialized to failure */

    if ((sess_cache != NULL) && (bio != NULL))
    {
        BIO_printf(bio, "\nSession Cache Statistics -------------------\n\n");
        BIO_printf(bio, "Cache add requests      : %u\n",
            sess_cache->count.add_call);
        BIO_printf(bio, "Cache additions         : %u\n",
            sess_cache->count.add_good);
        BIO_printf(bio, "Cache overflows         : %u\n",
            sess_cache->count.overflow);
        BIO_printf(bio, "Cache retrieve requests : %u\n",
            sess_cache->count.get_call);
        BIO_printf(bio, "Cache retrievals        : %u\n",
            sess_cache->count.get_good);
        BIO_printf(bio, "Cache delete requests   : %u\n",
            sess_cache->count.del_call);
        BIO_printf(bio, "Cache deletions         : %u\n",
            sess_cache->count.del_good);
        BIO_printf(bio, "\nEnd of Statistics --------------------------\n\n");

        status = 1;
    }

    return(status);
}

int APP_session_cache_cb(SSL_CTX *ctx, int cmd, SSL_SESSION **session,
    void *my_arg)
{
    int status = 0;                  /* Return code, initialize to failure */
    SESS_CACHE *my_cache = NULL;                  /* Local cache reference */

    /* The callback argument is needed to provide the cache structure */
    if (my_arg == NULL)
    {
        goto end;
    }

    /* Assign the BIO from the application-defined argument */
    my_cache = (SESS_CACHE *)my_arg;

    /* End processing immediately if there is no session identifier data */
    if ((session == NULL) || (*session == NULL) ||
        (SSL_SESSION_get_session_id_length(*session) == 0))
    {
        goto end;
    }

    /*
     * Check for cache flushing status. This check must occur before any
     * other operation on the session cache to avoid end up using a dangling
     * session which has been freed by the flush operation
     */
    if (!(SSL_CTX_get_session_cache_mode(ctx) & SSL_SESS_CACHE_NO_AUTO_CLEAR))
    {
        /* Flush the cache and ignore the return code */
        array_cache_flush(my_cache);
    }

    /* Determine the operation type */
    switch (cmd)
    {
    case SSL_EXT_SESS_CACHE_ADD:
        status = array_cache_add(my_cache, *session);
        break;
    case SSL_EXT_SESS_CACHE_DEL:
        status = array_cache_delete(my_cache, *session);
        break;
    case SSL_EXT_SESS_CACHE_FIND:
        if ((*session = array_cache_retrieve(my_cache, *session)) != NULL)
        {
            status = 1;
        }
        break;
    default:
        /* Error condition - user feedback required */
        break;
    }

end:

    return(status);
}

static int array_cache_add(SESS_CACHE *sess_cache, SSL_SESSION *session)
{
    int status = 0;       /* Function return status, initialized to failure */
    SSL_SESSION *sess = NULL;                          /* Retrieved session */
    int i;                                                  /* Loop counter */
    int j;                                                  /* Loop counter */

    /* Lock the cache */
    R_lock_w(sess_cache->cache_lock_id);

    /* Update the cache usage count */
    sess_cache->count.add_call++;

    /* Search for the session in the cache */
    for (i = 0; i < sess_cache->cache_size; i++)
    {
        /* We have reached the end of useful data */
        if (sess_cache->cache[i] == NULL)
        {
            break;
        }

        /* Compare with the current session */
        if (SSL_SESSION_cmp(session, sess_cache->cache[i]) == 0)
        {
            /* We have this session already in the array */
            sess = sess_cache->cache[i];
            break;
        }
    }

    if (sess == NULL)
    {
        /* Add the session to the cache */
        if (i == sess_cache->cache_size)
        {
            /* The cache is full so deal with the overflow */

            /*
             * Remove the first session in the cache. This session is not
             * invalid (so do not mark it as non-resumable) but it can no
             * longer be held in the cache.
             */
            SSL_SESSION_free(sess_cache->cache[0]);

            /* Repack the cache so push the sessions up */
            for (j = 1; j < sess_cache->cache_size; j++)
            {
                sess_cache->cache[j-1] = sess_cache->cache[j];
            }

            /* Add the new session at the end */
            sess_cache->cache[j-1] = session;

            /* Update the cache usage count */
            sess_cache->count.overflow++;

            /* Set the success value */
            status = 1;
       }
       else
       {
           /* We found an empty place in the array */
           sess_cache->cache[i] = session;

           /* Set the success value */
           status = 1;
       }
    }
    else /* The session was found in the cache */
    {
        /* Replace the cached session that looks the same */
        SSL_SESSION_set_not_reusable(sess_cache->cache[i]);
        SSL_SESSION_free(sess_cache->cache[i]);
        sess_cache->cache[i] = session;
        status = 1;
    }

    if (status == 1)
    {
        /*
         * Increment the reference to the session because the
         * session cache now refers to it
         */
        SSL_SESSION_reference_inc(session);

        /* Update the cache usage count */
        sess_cache->count.add_good++;
    }

    /* Unlock the cache */
    R_unlock_w(sess_cache->cache_lock_id);

    return (status);
}

static int array_cache_delete(SESS_CACHE *sess_cache, SSL_SESSION *session)
{
    int ret = 0;                 /* Function return, initialized to failure */
    int i;                                                  /* Loop counter */

    /* Obtain a write lock on the session cache then remove the session */
    R_lock_w(sess_cache->cache_lock_id);

    /* Update the cache counts */
    sess_cache->count.del_call++;

    for (i = 0; i < sess_cache->cache_size; i++)
    {
        /* A NULL space in the cache means the end of the cache */
        if (sess_cache->cache[i] == NULL)
        {
            break;
        }

        if (SSL_SESSION_cmp(session, sess_cache->cache[i]) == 0)
        {
            /*
             * Remove the session from the cache and from
             * further use in other SSL connections.
             */
            array_cache_remove_indexed_session(sess_cache, i);

            /* Update the cache counts */
            sess_cache->count.del_good++;

            /* Set the return code to indicate a removal */
            ret = 1;

            /* Removal is finished so exit now */
            break;
        }
    }

    /* Release the write lock on the cache */
    R_unlock_w(sess_cache->cache_lock_id);

    return(ret);
}

static int array_cache_flush(SESS_CACHE *sess_cache)
{
    int status = 0;         /* Function return code, initialized to failure */
    int i;                                                  /* Loop counter */
    R_TIME_T *sess_time = NULL;           /* Time of creation for a session */
    R_TIME_T *now_time = NULL;                              /* The time now */
    R_TIME_T *end_time = NULL;          /* The last valid time of a session */

    /* Retrieve a write lock on the cache */
    R_lock_w(sess_cache->cache_lock_id);

    /* Increment the counter for the cache flush */
    sess_cache->op_count++;

    /* Determine if the cache is due to be flushed before doing other work */
    if (sess_cache->op_count < APP_CACHE_MAX_FLUSH_COUNT)
    {
        goto end;
    }

    /* Reset the counter when making an attempt to flush */
    sess_cache->op_count = 0;

    /* Create a new time structure for retrieving session creation times */
    if ((sess_time = R_time_new()) == NULL)
    {
        goto end;
    }

    /* Create a time structure for the end of the session lifetime */
    if ((end_time = R_time_new()) == NULL)
    {
        goto end;
    }

    /* Create a time that is "now" */
    if ((now_time = R_time_new()) == NULL)
    {
        goto end;
    }

    /* Set the time value */
    R_time(now_time);

    /* Check all sessions in the cache for valid times */
    for (i = 0; i < sess_cache->cache_size; i++)
    {
        /* A null session marks the end of the current cache */
        if (sess_cache->cache[i] == NULL)
        {
            break;
        }

        /* Retrieve the session creation time */
        SSL_SESSION_get_time(sess_cache->cache[i], sess_time);

        /*
         * Find the end of the session's validity period. This is the
         * session creation time offset by the session's validity period.
         */
        R_time_offset(end_time, sess_time,
            SSL_SESSION_get_timeout(sess_cache->cache[i]), 0);

        /*
         * If the current time is equal to or beyond the end of the
         * session's validity period then the session is invalid
         */
        if (R_time_cmp(end_time, now_time) <= 0)
        {
            /* Update the deletion statistics */
            sess_cache->count.del_call++;

            /*
             * Remove the session from the cache so that it is
             * no longer usable
             */
            array_cache_remove_indexed_session(sess_cache, i);

            /* Update the stats for a successful deletion */
            sess_cache->count.del_good++;
        }

        /* Set the flush status */
        status = 1;
    }

end:

    /* Release the write lock on the cache */
    R_unlock_w(sess_cache->cache_lock_id);

    /* Free all of the locally created time structures */
    if (sess_time != NULL)
    {
        R_time_free(sess_time);
    }

    if (now_time != NULL)
    {
        R_time_free(now_time);
    }

    if (end_time != NULL)
    {
        R_time_free(end_time);
    }

    return(status);
}

static void array_cache_remove_indexed_session(SESS_CACHE *sess_cache,
    int index)
{
    int j;                                                  /* Loop counter */

    /* Mark the session as unusable for other connections */
    SSL_SESSION_set_not_reusable(sess_cache->cache[index]);

    /* Free the cache reference */
    SSL_SESSION_free(sess_cache->cache[index]);

    /* Push remaining sessions up in the FIFO */
    for (j = index + 1; j < sess_cache->cache_size; j++)
    {
        sess_cache->cache[j-1] = sess_cache->cache[j];
    }

    /* The final cache position should be reset to empty */
    sess_cache->cache[j-1] = NULL;

    return;
}

static SSL_SESSION *array_cache_retrieve(SESS_CACHE *sess_cache,
    SSL_SESSION *session)
{
    SSL_SESSION *ret = NULL;                           /* Retrieved session */
    int i;                                                  /* Loop counter */

    /* Obtain a read lock on the session cache */
    R_lock_r(sess_cache->cache_lock_id);

    /* Update cache counts */
    sess_cache->count.get_call++;

    /* Search the cache for a session that matches the search template */
    for (i = 0; i < sess_cache->cache_size; i++)
    {
        /* A NULL space in the cache means the end of the cache */
        if (sess_cache->cache[i] == NULL)
        {
            break;
        }

        /* A standard session comparison determines a hit */
        if (SSL_SESSION_cmp(session, sess_cache->cache[i]) == 0)
        {
            ret = sess_cache->cache[i];
            break;
        }
    }

    /* Update the cache counts */
    if (ret != NULL)
    {
        sess_cache->count.get_good++;
    }

    /* Release the read lock on the session cache */
    R_unlock_r(sess_cache->cache_lock_id);

    return(ret);
}

Copyright (c) 1999-2005 RSA Security Inc. All rights reserved. 072-001001-2100-001-000 - 2.1