]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
add the ability to persist SSL session cache to disk across server restarts 92/head
authorPhil Mayers <p.mayers@imperial.ac.uk>
Fri, 21 Sep 2012 12:45:22 +0000 (13:45 +0100)
committerPhil Mayers <p.mayers@imperial.ac.uk>
Fri, 21 Sep 2012 12:45:22 +0000 (13:45 +0100)
configure.in
raddb/mods-available/eap
src/include/tls.h
src/main/tls.c

index e31292a48213a221758f14ee0722ce8796bc16a5..aabf7ae311b4f12f6d17a044a779bf07988fdec8 100644 (file)
@@ -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 \
index 5ec361482939ce1d03f03b4c3d65580460ea0c2b..35e39833810a5961b4c0e6b65f9076d049274cc7 100644 (file)
@@ -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"
                }
 
                #
index 22f27fbaab323d69cb3a3116e57135528ad42632..18997d9c7446c7f2c41be57c7a7dcfb474065b54 100644 (file)
@@ -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;
 
index 44fdb217fdfacd6bd7d4a99650c73d191cf3570e..0650e3b99d281c96ff85d1bc2356512f441b0f70 100644 (file)
@@ -34,6 +34,14 @@ RCSID("$Id$")
 #include <sys/stat.h>
 #endif
 
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+
+#ifdef HAVE_UTIME_H
+#include <utime.h>
+#endif
+
 #ifdef WITH_TLS
 #ifdef HAVE_OPENSSL_RAND_H
 #include <openssl/rand.h>
@@ -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.
                         */