| RSA BSAFE Micro Edition Suite |
Streamlined security for mobile and embedded devices |
 
![]() |
/* $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); }