diff --git a/CHANGES b/CHANGES index b486adf50d..70b3755992 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,8 @@ Changes with Apache 2.3.0 [Remove entries to the current 2.0 and 2.2 section below, when backported] + *) mod_ssl: Add support for caching SSL Sessions in memcached. [Paul Querna] + *) SECURITY: CVE-2007-1862 (cve.mitre.org) mod_mem_cache: Copy headers into longer lived storage; header names and values could previously point to cleaned up storage diff --git a/modules/ssl/config.m4 b/modules/ssl/config.m4 index 3d8b152e37..68cf6f743f 100644 --- a/modules/ssl/config.m4 +++ b/modules/ssl/config.m4 @@ -86,6 +86,56 @@ ap_ssltk_dc="no"]) fi ]) + + +AC_DEFUN([CHECK_SSL_MEMCACHE], [ + AC_MSG_CHECKING(for ssl session caching in memcache) + ap_ssltk_mc="no" + tmp_nomessage="" + tmp_forced="no" + AC_ARG_ENABLE(ssl-memcache, + APACHE_HELP_STRING(--enable-ssl-memcache,Select memcache support in mod_ssl), + ap_ssltk_mc="$enableval" + tmp_nomessage="" + tmp_forced="yes" + if test "x$ap_ssltk_mc" = "x"; then + ap_ssltk_mc="yes" + dnl our "error"s become "tests revealed that..." + tmp_forced="no" + fi + if test "$ap_ssltk_mc" != "yes" -a "$ap_ssltk_mc" != "no"; then + tmp_nomessage="--enable-ssl-cache-memcache had illegal syntax - disabling" + ap_ssltk_mc="no" + fi) + if test "$tmp_forced" = "no"; then + AC_MSG_RESULT($ap_ssltk_mc (default)) + else + AC_MSG_RESULT($ap_ssltk_mc (specified)) + fi + if test "$tmp_forced" = "yes" -a "x$ap_ssltk_mc" = "xno" -a "x$tmp_nomessage" != "x"; then + AC_MSG_ERROR(ssl memcache support failed: $tmp_nomessage) + fi + if test "$ap_ssltk_mc" = "yes"; then + save_cpp=$CPPFLAGS + CPPFLAGS="$CPPFLAGS -I$APR_INCLUDEDIR -I$APU_INCLUDEDIR" + AC_CHECK_HEADER( + [apr_memcache.h], + [], + [tmp_nomessage="can't include apr_memcache headers" + ap_ssltk_mc="no"]) + + CPPFLAGS=$save_cpp + + if test "$tmp_forced" = "yes" -a "x$ap_ssltk_mc" = "xno"; then + AC_MSG_ERROR(ssl memcache support failed: $tmp_nomessage) + fi + fi + if test "$ap_ssltk_mc" = "yes"; then + AC_DEFINE(HAVE_SSL_CACHE_MEMCACHE, 1, [Define if ssl-memcache support is enabled]) + fi +]) + + dnl # start of module specific part APACHE_MODPATH_INIT(ssl) @@ -110,6 +160,7 @@ ssl_scache.lo dnl ssl_scache_dbm.lo dnl ssl_scache_shmcb.lo dnl ssl_scache_dc.lo dnl +ssl_scache_memcache.lo dnl ssl_util.lo dnl ssl_util_ssl.lo dnl " @@ -118,6 +169,7 @@ APACHE_MODULE(ssl, [SSL/TLS support (mod_ssl)], $ssl_objs, , no, [ APACHE_CHECK_SSL_TOOLKIT APR_SETVAR(MOD_SSL_LDADD, [\$(SSL_LIBS)]) CHECK_DISTCACHE + CHECK_SSL_MEMCACHE if test "x$enable_ssl" = "xshared"; then # The only symbol which needs to be exported is the module # structure, so ask libtool to hide everything else: diff --git a/modules/ssl/ssl_engine_config.c b/modules/ssl/ssl_engine_config.c index 12c28b8747..dbd9ef42d5 100644 --- a/modules/ssl/ssl_engine_config.c +++ b/modules/ssl/ssl_engine_config.c @@ -1032,6 +1032,19 @@ const char *ssl_cmd_SSLSessionCache(cmd_parms *cmd, } #else return "SSLSessionCache: distcache support disabled"; +#endif + } + else if ((arglen > 3) && strcEQn(arg, "memcache:", 9)) { +#ifdef HAVE_SSL_CACHE_MEMCACHE + mc->nSessionCacheMode = SSL_SCMODE_MC; + mc->szSessionCacheDataFile = apr_pstrdup(mc->pPool, arg+9); + if (!mc->szSessionCacheDataFile) { + return apr_pstrcat(cmd->pool, + "SSLSessionCache: Invalid memcache config: ", + arg+9, NULL); + } +#else + return "SSLSessionCache: distcache support disabled"; #endif } else { diff --git a/modules/ssl/ssl_private.h b/modules/ssl/ssl_private.h index 75c5ff022a..50f680b688 100644 --- a/modules/ssl/ssl_private.h +++ b/modules/ssl/ssl_private.h @@ -175,6 +175,7 @@ typedef enum { #endif #endif + /** * Define the certificate algorithm types */ @@ -277,6 +278,7 @@ typedef enum { SSL_SCMODE_DBM = 1, SSL_SCMODE_SHMCB = 3, SSL_SCMODE_DC = 4, + SSL_SCMODE_MC = 5, SSL_SCMODE_NONE_NOT_NULL = 5 } ssl_scmode_t; @@ -599,6 +601,15 @@ SSL_SESSION *ssl_scache_dc_retrieve(server_rec *, UCHAR *, int); void ssl_scache_dc_remove(server_rec *, UCHAR *, int); void ssl_scache_dc_status(request_rec *r, int flags, apr_pool_t *pool); +#ifdef HAVE_SSL_CACHE_MEMCACHE +void ssl_scache_mc_init(server_rec *, apr_pool_t *); +void ssl_scache_mc_kill(server_rec *); +BOOL ssl_scache_mc_store(server_rec *, UCHAR *, int, time_t, SSL_SESSION *); +SSL_SESSION *ssl_scache_mc_retrieve(server_rec *, UCHAR *, int); +void ssl_scache_mc_remove(server_rec *, UCHAR *, int); +void ssl_scache_mc_status(request_rec *r, int flags, apr_pool_t *pool); +#endif + /** Proxy Support */ int ssl_proxy_enable(conn_rec *c); int ssl_engine_disable(conn_rec *c); diff --git a/modules/ssl/ssl_scache.c b/modules/ssl/ssl_scache.c index c85e4f19cc..e01e1d1bed 100644 --- a/modules/ssl/ssl_scache.c +++ b/modules/ssl/ssl_scache.c @@ -58,6 +58,10 @@ void ssl_scache_init(server_rec *s, apr_pool_t *p) #ifdef HAVE_DISTCACHE else if (mc->nSessionCacheMode == SSL_SCMODE_DC) ssl_scache_dc_init(s, p); +#endif +#ifdef HAVE_SSL_CACHE_MEMCACHE + else if (mc->nSessionCacheMode == SSL_SCMODE_MC) + ssl_scache_mc_init(s, p); #endif else if (mc->nSessionCacheMode == SSL_SCMODE_SHMCB) { void *data; @@ -84,6 +88,10 @@ void ssl_scache_kill(server_rec *s) #ifdef HAVE_DISTCACHE else if (mc->nSessionCacheMode == SSL_SCMODE_DC) ssl_scache_dc_kill(s); +#endif +#ifdef HAVE_SSL_CACHE_MEMCACHE + else if (mc->nSessionCacheMode == SSL_SCMODE_MC) + ssl_scache_mc_kill(s); #endif return; } @@ -100,6 +108,10 @@ BOOL ssl_scache_store(server_rec *s, UCHAR *id, int idlen, time_t expiry, SSL_SE #ifdef HAVE_DISTCACHE else if (mc->nSessionCacheMode == SSL_SCMODE_DC) rv = ssl_scache_dc_store(s, id, idlen, expiry, sess); +#endif +#ifdef HAVE_SSL_CACHE_MEMCACHE + else if (mc->nSessionCacheMode == SSL_SCMODE_MC) + rv = ssl_scache_mc_store(s, id, idlen, expiry, sess); #endif return rv; } @@ -116,6 +128,10 @@ SSL_SESSION *ssl_scache_retrieve(server_rec *s, UCHAR *id, int idlen) #ifdef HAVE_DISTCACHE else if (mc->nSessionCacheMode == SSL_SCMODE_DC) sess = ssl_scache_dc_retrieve(s, id, idlen); +#endif +#ifdef HAVE_SSL_CACHE_MEMCACHE + else if (mc->nSessionCacheMode == SSL_SCMODE_MC) + sess = ssl_scache_mc_retrieve(s, id, idlen); #endif return sess; } @@ -131,6 +147,10 @@ void ssl_scache_remove(server_rec *s, UCHAR *id, int idlen) #ifdef HAVE_DISTCACHE else if (mc->nSessionCacheMode == SSL_SCMODE_DC) ssl_scache_dc_remove(s, id, idlen); +#endif +#ifdef HAVE_SSL_CACHE_MEMCACHE + else if (mc->nSessionCacheMode == SSL_SCMODE_MC) + ssl_scache_mc_remove(s, id, idlen); #endif return; } @@ -162,6 +182,10 @@ static int ssl_ext_status_hook(request_rec *r, int flags) else if (sc->mc->nSessionCacheMode == SSL_SCMODE_DC) ssl_scache_dc_status(r, flags, r->pool); #endif +#ifdef HAVE_SSL_CACHE_MEMCACHE + else if (sc->mc->nSessionCacheMode == SSL_SCMODE_MC) + ssl_scache_mc_status(r, flags, r->pool); +#endif ap_rputs("\n", r); ap_rputs("\n", r); diff --git a/modules/ssl/ssl_scache_memcache.c b/modules/ssl/ssl_scache_memcache.c new file mode 100644 index 0000000000..f17d21787a --- /dev/null +++ b/modules/ssl/ssl_scache_memcache.c @@ -0,0 +1,289 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + + +/* _ _ + * _ __ ___ ___ __| | ___ ___| | mod_ssl + * | '_ ` _ \ / _ \ / _` | / __/ __| | Apache Interface to OpenSSL + * | | | | | | (_) | (_| | \__ \__ \ | + * |_| |_| |_|\___/ \__,_|___|___/___/_| + * |_____| + * ssl_scache_memcache.c + * Distributed Session Cache on top of memcached + */ + +#include "ssl_private.h" + +#ifdef HAVE_SSL_CACHE_MEMCACHE + +#include "apr_memcache.h" +#include "ap_mpm.h" + +/* + * SSL Session Caching using memcached as a backend. + */ + +/* +** +** High-Level "handlers" as per ssl_scache.c +** +*/ + + +/* The underlying apr_memcache system is thread safe.. */ +static apr_memcache_t *memctxt; + +#define MC_TAG "mod_ssl:" +#define MC_TAG_LEN \ + (sizeof(MC_TAG)) + +#define MC_KEY_LEN 254 + +void ssl_scache_mc_init(server_rec *s, apr_pool_t *p) +{ + apr_status_t rv; + int thread_limit = 0; + int nservers = 0; + char *cache_config; + char *split; + char *tok; + SSLModConfigRec *mc = myModConfig(s); + + ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &thread_limit); + + if (mc->szSessionCacheDataFile == NULL) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "SSLSessionCache required"); + ssl_die(); + } + + /* Find all the servers in the first run to get a total count */ + cache_config = apr_pstrdup(p, mc->szSessionCacheDataFile); + split = apr_strtok(cache_config, ",", &tok); + while (split) { + nservers++; + split = apr_strtok(NULL,",", &tok); + } + + rv = apr_memcache_create(p, nservers, 0, &memctxt); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, + "SSLSessionCache: Failed to create Memcache Object of '%d' size.", + nservers); + ssl_die(); + } + + /* Now add each server to the memcache */ + cache_config = apr_pstrdup(p, mc->szSessionCacheDataFile); + split = apr_strtok(cache_config, ",", &tok); + while (split) { + apr_memcache_server_t *st; + char *host_str; + char *scope_id; + apr_port_t port; + + rv = apr_parse_addr_port(&host_str, &scope_id, &port, split, p); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, + "SSLSessionCache: Failed to Parse Server: '%s'", split); + ssl_die(); + } + + if (host_str == NULL) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, + "SSLSessionCache: Failed to Parse Server, " + "no hostname specified: '%s'", split); + ssl_die(); + } + + if (port == 0) { + port = 11211; /* default port */ + } + + /* Should Max Conns be (thread_limit / nservers) ? */ + rv = apr_memcache_server_create(p, + host_str, port, + 0, + 1, + thread_limit, + 600, + &st); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, + "SSLSessionCache: Failed to Create Server: %s:%d", + host_str, port); + ssl_die(); + } + + rv = apr_memcache_add_server(memctxt, st); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, + "SSLSessionCache: Failed to Add Server: %s:%d", + host_str, port); + ssl_die(); + } + + split = apr_strtok(NULL,",", &tok); + } + + return; +} + +void ssl_scache_mc_kill(server_rec *s) +{ + +} + +static char *mc_session_id2sz(unsigned char *id, int idlen, + char *str, int strsize) +{ + char *cp; + int n; + + cp = apr_cpystrn(str, MC_TAG, MC_TAG_LEN); + for (n = 0; n < idlen && n < (MC_KEY_LEN - MC_TAG_LEN); n++) { + apr_snprintf(cp, strsize - (cp-str), "%02X", id[n]); + cp += 2; + } + *cp = '\0'; + return str; +} + +BOOL ssl_scache_mc_store(server_rec *s, UCHAR *id, int idlen, + time_t timeout, SSL_SESSION *pSession) +{ + char buf[MC_KEY_LEN]; + char *strkey = NULL; + UCHAR ucaData[SSL_SESSION_MAX_DER]; + UCHAR *ucp; + int nData; + apr_status_t rv; + + /* streamline session data */ + if ((nData = i2d_SSL_SESSION(pSession, NULL)) > sizeof(ucaData)) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "scache_mc: streamline session data size too large: %d > " + "%" APR_SIZE_T_FMT, + nData, sizeof(ucaData)); + return FALSE; + } + + ucp = ucaData; + i2d_SSL_SESSION(pSession, &ucp); + + strkey = mc_session_id2sz(id, idlen, buf, sizeof(buf)); + if(!strkey) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "scache_mc: Key generation borked."); + return FALSE; + } + + timeout -= time(NULL); + + timeout = apr_time_sec(timeout); + + rv = apr_memcache_set(memctxt, strkey, (char*)ucp, nData, timeout, 0); + + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, + "scache_mc: error setting key '%s' " + "with %d bytes of data", strkey, nData); + return FALSE; + } + + return TRUE; +} + +SSL_SESSION *ssl_scache_mc_retrieve(server_rec *s, UCHAR *id, int idlen) +{ + SSL_SESSION *pSession; + MODSSL_D2I_SSL_SESSION_CONST unsigned char *pder; + apr_size_t der_len; + SSLModConfigRec *mc = myModConfig(s); + char buf[MC_KEY_LEN]; + char* strkey = NULL; + apr_status_t rv; + + strkey = mc_session_id2sz(id, idlen, buf, sizeof(buf)); + + if(!strkey) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "scache_mc: Key generation borked."); + return NULL; + } + + rv = apr_memcache_getp(memctxt, mc->pPool, strkey, + (char**)&pder, &der_len, NULL); + + if (rv == APR_NOTFOUND) { + /* ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "scache_mc: 'get_session' MISS"); */ + return NULL; + } + + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, + "scache_mc: 'get_session' FAIL"); + return NULL; + } + + if (der_len > SSL_SESSION_MAX_DER) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, + "scache_mc: 'get_session' OVERFLOW"); + return NULL; + } + + pSession = d2i_SSL_SESSION(NULL, &pder, der_len); + + if (!pSession) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, + "scache_mc: 'get_session' CORRUPT"); + return NULL; + } + + /* ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, + "scache_mc: 'get_session' HIT"); */ + + return pSession; +} + +void ssl_scache_mc_remove(server_rec *s, UCHAR *id, int idlen) +{ + char buf[MC_KEY_LEN]; + char* strkey = NULL; + apr_status_t rv; + + strkey = mc_session_id2sz(id, idlen, buf, sizeof(buf)); + if(!strkey) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "scache_mc: Key generation borked."); + return; + } + + rv = apr_memcache_delete(memctxt, strkey, 0); + + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, + "scache_mc: error deleting key '%s' ", + strkey); + return; + } +} + +void ssl_scache_mc_status(request_rec *r, int flags, apr_pool_t *pool) +{ + /* SSLModConfigRec *mc = myModConfig(r->server); */ + /* TODO: Make a mod_status handler. meh. */ +} + +#endif