From: Phil Mayers Date: Fri, 21 Sep 2012 12:45:22 +0000 (+0100) Subject: add the ability to persist SSL session cache to disk across server restarts X-Git-Tag: release_3_0_0_beta1~1683^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d5e02c4ae3e6624cc20a4beff7876c6135837e5e;p=thirdparty%2Ffreeradius-server.git add the ability to persist SSL session cache to disk across server restarts --- diff --git a/configure.in b/configure.in index e31292a4821..aabf7ae311b 100644 --- a/configure.in +++ b/configure.in @@ -604,6 +604,7 @@ AC_CHECK_HEADERS( \ sys/types.h \ sys/socket.h \ winsock.h \ + utime.h \ sys/time.h \ sys/wait.h \ sys/security.h \ diff --git a/raddb/mods-available/eap b/raddb/mods-available/eap index 5ec36148293..35e39833810 100644 --- a/raddb/mods-available/eap +++ b/raddb/mods-available/eap @@ -388,6 +388,34 @@ eap { # who are logged in... which can be a LOT. # max_entries = 255 + + # + # Internal "name" of the session cache. + # Used to distinguish which TLS context + # sessions belong to. + # + # The server will generate a random value + # if unset. This will change across server + # restart so you MUST set the "name" if you + # want to persist sessions (see below). + # + #name = "EAP module" + + # + # Simple directory-based storage of sessions. + # Two files per session will be written, the SSL + # state and the cached VPs. This will persist session + # across server restarts. + # + # The server will need write perms, and the directory + # should be secured from anyone else. You might want + # a script to remove old files from here periodically: + # + # find ${logdir}/tlscache -mtime +2 -exec rm -f {} \; + # + # This feature REQUIRES "name" option be set above. + # + #persist_dir = "${logdir}/tlscache" } # diff --git a/src/include/tls.h b/src/include/tls.h index 22f27fbaab3..18997d9c744 100644 --- a/src/include/tls.h +++ b/src/include/tls.h @@ -359,6 +359,7 @@ struct fr_tls_server_conf_t { int session_timeout; int session_cache_size; char *session_id_name; + char *session_cache_path; char session_context_id[SSL_MAX_SSL_SESSION_ID_LENGTH]; time_t session_last_flushed; diff --git a/src/main/tls.c b/src/main/tls.c index 44fdb217fdf..0650e3b99d2 100644 --- a/src/main/tls.c +++ b/src/main/tls.c @@ -34,6 +34,14 @@ RCSID("$Id$") #include #endif +#ifdef HAVE_FCNTL_H +#include +#endif + +#ifdef HAVE_UTIME_H +#include +#endif + #ifdef WITH_TLS #ifdef HAVE_OPENSSL_RAND_H #include @@ -756,6 +764,8 @@ static CONF_PARSER cache_config[] = { offsetof(fr_tls_server_conf_t, session_cache_size), NULL, "255" }, { "name", PW_TYPE_STRING_PTR, offsetof(fr_tls_server_conf_t, session_id_name), NULL, NULL}, + { "persist_dir", PW_TYPE_STRING_PTR, + offsetof(fr_tls_server_conf_t, session_cache_path), NULL, NULL}, { NULL, -1, 0, NULL, NULL } /* end the list */ }; @@ -957,6 +967,10 @@ static int generate_eph_rsa_key(SSL_CTX *ctx) return 0; } +/* index we use to store cached session VPs + * needs to be dynamic so we can supply a "free" function + */ +static int FR_TLS_EX_INDEX_VPS = -1; /* * Print debugging messages, and free data. @@ -967,10 +981,11 @@ static int generate_eph_rsa_key(SSL_CTX *ctx) */ #define MAX_SESSION_SIZE (256) -static void cbtls_remove_session(UNUSED SSL_CTX *ctx, SSL_SESSION *sess) +static void cbtls_remove_session(SSL_CTX *ctx, SSL_SESSION *sess) { size_t size; char buffer[2 * MAX_SESSION_SIZE + 1]; + fr_tls_server_conf_t *conf; size = sess->session_id_length; if (size > MAX_SESSION_SIZE) size = MAX_SESSION_SIZE; @@ -978,14 +993,35 @@ static void cbtls_remove_session(UNUSED SSL_CTX *ctx, SSL_SESSION *sess) fr_bin2hex(sess->session_id, buffer, size); DEBUG2(" SSL: Removing session %s from the cache", buffer); + conf = (fr_tls_server_conf_t *)SSL_CTX_get_app_data(ctx); + if (conf && conf->session_cache_path) { + int rv; + char filename[256]; + + /* remove session and any cached VPs */ + rv = snprintf(filename, sizeof(filename), "%s%c%s.asn1", + conf->session_cache_path, FR_DIR_SEP, buffer + ); + rv = unlink(filename); + if (rv != 0) { + DEBUG2(" SSL: could not remove persisted session file %s: %s", filename, strerror(errno)); + } + /* VPs might be absent; might not have been written to disk yet */ + rv = snprintf(filename, sizeof(filename), "%s%c%s.vps", + conf->session_cache_path, FR_DIR_SEP, buffer + ); + rv = unlink(filename); + } return; } -static int cbtls_new_session(UNUSED SSL *s, SSL_SESSION *sess) +static int cbtls_new_session(SSL *ssl, SSL_SESSION *sess) { size_t size; char buffer[2 * MAX_SESSION_SIZE + 1]; + fr_tls_server_conf_t *conf; + unsigned char *sess_blob = NULL; size = sess->session_id_length; if (size > MAX_SESSION_SIZE) size = MAX_SESSION_SIZE; @@ -994,26 +1030,162 @@ static int cbtls_new_session(UNUSED SSL *s, SSL_SESSION *sess) DEBUG2(" SSL: adding session %s to cache", buffer); + conf = (fr_tls_server_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF); + if (conf && conf->session_cache_path) { + int fd, rv, todo, blob_len; + char filename[256]; + unsigned char *p; + + /* find out what length data we need */ + blob_len = i2d_SSL_SESSION(sess, NULL); + if (blob_len < 1) { + /* something went wrong */ + DEBUG2(" SSL: could not find buffer length to persist session"); + return 0; + } + + /* alloc and convert to ASN.1 */ + sess_blob = malloc(blob_len); + if (!sess_blob) { + DEBUG2(" SSL: could not allocate buffer len=%d to persist session", blob_len); + return 0; + } + /* openssl mutates &p */ + p = sess_blob; + rv = i2d_SSL_SESSION(sess, &p); + if (rv != blob_len) { + DEBUG2(" SSL: could not persist session"); + goto error; + } + + /* open output file */ + rv = snprintf(filename, sizeof(filename), "%s%c%s.asn1", + conf->session_cache_path, FR_DIR_SEP, buffer + ); + fd = open(filename, O_RDWR|O_CREAT|O_EXCL, 0600); + if (fd < 0) { + DEBUG2(" SSL: could not open session file %s: %s", filename, strerror(errno)); + goto error; + } + + todo = blob_len; + p = sess_blob; + while (todo > 0) { + rv = write(fd, p, todo); + if (rv < 1) { + DEBUG2(" SSL: failed writing session: %s", strerror(errno)); + close(fd); + goto error; + } + p += rv; + todo -= rv; + } + close(fd); + DEBUG2(" SSL: wrote session %s to %s len=%d", buffer, filename, blob_len); + } + +error: + if (sess_blob) free(sess_blob); + return 0; } -static SSL_SESSION *cbtls_get_session(UNUSED SSL *s, +static SSL_SESSION *cbtls_get_session(SSL *ssl, unsigned char *data, int len, int *copy) { size_t size; char buffer[2 * MAX_SESSION_SIZE + 1]; + fr_tls_server_conf_t *conf; + + SSL_SESSION *sess = NULL; + unsigned char *sess_data = NULL; + PAIR_LIST *pairlist = NULL; size = len; if (size > MAX_SESSION_SIZE) size = MAX_SESSION_SIZE; fr_bin2hex(data, buffer, size); - DEBUG2(" SSL: Client requested nonexistent cached session %s", - buffer); + DEBUG2(" SSL: Client requested cached session %s", buffer); + + conf = (fr_tls_server_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF); + if (conf && conf->session_cache_path) { + int rv, fd, todo; + char filename[256]; + unsigned char *p; + struct stat st; + VALUE_PAIR *vp; + + /* read in the cached VPs from the .vps file */ + rv = snprintf(filename, sizeof(filename), "%s%c%s.vps", + conf->session_cache_path, FR_DIR_SEP, buffer + ); + rv = pairlist_read(filename, &pairlist, 1); + if (rv < 0) { + /* not safe to un-persist a session w/o VPs */ + DEBUG2(" SSL: could not load persisted VPs for session %s", buffer); + goto err; + } + + /* load the actual SSL session */ + rv = snprintf(filename, sizeof(filename), "%s%c%s.asn1", + conf->session_cache_path, FR_DIR_SEP, buffer + ); + fd = open(filename, O_RDONLY); + if (fd == -1) { + DEBUG2(" SSL: could not find persisted session file %s: %s", filename, strerror(errno)); + goto err; + } + + rv = fstat(fd, &st); + if (rv == -1) { + DEBUG2(" SSL: could not stat persisted session file %s: %s", filename, strerror(errno)); + close(fd); + goto err; + } + + sess_data = malloc(st.st_size); + if (!sess_data) { + DEBUG2(" SSL: could not alloc buffer for persisted session len=%d", st.st_size); + close(fd); + goto err; + } + + p = sess_data; + todo = st.st_size; + while (todo > 0) { + rv = read(fd, p, todo); + if (rv < 1) { + DEBUG2(" SSL: could not read from persisted session: %s", strerror(errno)); + close(fd); + goto err; + } + todo -= rv; + p += rv; + } + close(fd); + + /* openssl mutates &p */ + p = sess_data; + sess = d2i_SSL_SESSION(NULL, &p, st.st_size); + + if (!sess) { + DEBUG2(" SSL: OpenSSL failed to load persisted session: %s", ERR_error_string(ERR_get_error(), NULL)); + goto err; + } + + /* cache the VPs into the session */ + vp = paircopy(pairlist->reply); + SSL_SESSION_set_ex_data(sess, FR_TLS_EX_INDEX_VPS, vp); + DEBUG2(" SSL: Successfully restored session %s", buffer); + } +err: + if (sess_data) free(sess_data); + if (pairlist) pairlist_free(&pairlist); *copy = 0; - return NULL; + return sess; } #ifdef HAVE_OPENSSL_OCSP_H @@ -1680,11 +1852,6 @@ static int set_ecdh_curve(SSL_CTX *ctx, const char *ecdh_curve) #endif #endif -/* index we use to store cached session VPs - * needs to be dynamic so we can supply a "free" function - */ -static int FR_TLS_EX_INDEX_VPS = -1; - /* * DIE OPENSSL DIE DIE DIE * @@ -1739,6 +1906,12 @@ static SSL_CTX *init_tls_ctx(fr_tls_server_conf_t *conf, int client) ctx = SSL_CTX_new(TLSv1_method()); + /* + * Save the config on the context so that callbacks which + * only get SSL_CTX* e.g. session persistence, can get it + */ + SSL_CTX_set_app_data(ctx, conf); + /* * Identify the type of certificates that needs to be loaded */ @@ -2317,6 +2490,28 @@ int tls_success(tls_session_t *ssn, REQUEST *request) RDEBUG2("Saving session %s vps %p in the cache", buffer, vps); SSL_SESSION_set_ex_data(ssn->ssl->session, FR_TLS_EX_INDEX_VPS, vps); + if (conf->session_cache_path) { + /* write the VPs to the cache file */ + char filename[256], buf[1024]; + FILE *vp_file; + + snprintf(filename, sizeof(filename), "%s%c%s.vps", + conf->session_cache_path, FR_DIR_SEP, buffer + ); + vp_file = fopen(filename, "w"); + if (vp_file == NULL) { + RDEBUG2("Could not write session VPs to persistent cache: %s", strerror(errno)); + } else { + /* generate a dummy user-style entry which is easy to read back */ + fprintf(vp_file, "# SSL cached session\n"); + fprintf(vp_file, "%s\n", buffer); + for (vp=vps; vp; vp = vp->next) { + vp_prints(buf, sizeof(buf), vp); + fprintf(vp_file, "\t%s%s\n", buf, vp->next ? "," : ""); + } + fclose(vp_file); + } + } } else { RDEBUG2("WARNING: No information to cache: session caching will be disabled for session %s", buffer); SSL_CTX_remove_session(ssn->ctx, @@ -2364,6 +2559,20 @@ int tls_success(tls_session_t *ssn, REQUEST *request) } } + if (conf->session_cache_path) { + /* "touch" the cached session/vp file */ + char filename[256]; + + snprintf(filename, sizeof(filename), "%s%c%s.asn1", + conf->session_cache_path, FR_DIR_SEP, buffer + ); + utime(filename, NULL); + snprintf(filename, sizeof(filename), "%s%c%s.vps", + conf->session_cache_path, FR_DIR_SEP, buffer + ); + utime(filename, NULL); + } + /* * Mark the request as resumed. */