The current implementation of i2d_SSL_SESSION() leaves the client's chain (of 
intermediate certificates) out of the produced ASN1 representation, causing an 
external session cache implementation to loose the intermediate certificates 
between SSL connections.

I assume this was done to save memory, and it is typically not a problem, as 
the intermediate certificates are generally only needed when validating the 
client's certificate, which is generally only performed during the handshake of 
the first SSL connection in the session.

However, this can cause problems for applications (particularly web 
applications) which independently validate the client's certificate, and may 
need to do so in a later SSL connection within the session.  For example, if 
you have an HTTPS web application which performs additional certificate 
validation checks beyond what OpenSSL performs natively, and a redirect is used 
to access this application, then the application will not have access to the 
client's intermediate certificates if an external SSL session cache was used, 
which may prevent the application from properly validating the client's 
certificate.  In the case of web applications, simply sticking with OpenSSL's 
internal session caching instead of using an external session cache is 
generally not an option, as the web server will usually have several server 
processes running, and there is no way to guarantee that all SSL connections 
within a session will be processed by the same web server process.

So, I am attaching a patch which applies against OpenSSL 1.0.0 and adds a new 
function, i2d_full_SSL_SESSION(), which does include the client's chain 
certificates in the produced ASN1 representation.  This allows existing 
applications to continue using i2d_SSL_SESSION() with the existing behavior, 
while allowing new applications to choose whether or not they want to include 
the intermediate certificates in their external session cache.

For reference only, I am also attaching a patch (which applies against the 
current Apache HTTPD trunk that will become version 2.2.3) which adds an option 
to mod_ssl allowing the user to decide whether or not intermediate certificates 
should be cached using i2d_full_SSL_SESSION(). (If the above patch is 
incorporated into OpenSSL, I will submit this patch to Apache for inclusion as 
well.)

Thanks for your consideration.

diff -Naur openssl-1.0.0/doc/ssl/d2i_SSL_SESSION.pod openssl-1.0.0.patched/doc/ssl/d2i_SSL_SESSION.pod
--- openssl-1.0.0/doc/ssl/d2i_SSL_SESSION.pod	2005-03-30 06:50:14.000000000 -0500
+++ openssl-1.0.0.patched/doc/ssl/d2i_SSL_SESSION.pod	2010-06-04 16:04:50.000000000 -0400
@@ -2,7 +2,7 @@
 
 =head1 NAME
 
-d2i_SSL_SESSION, i2d_SSL_SESSION - convert SSL_SESSION object from/to ASN1 representation
+d2i_SSL_SESSION, i2d_SSL_SESSION, i2d_full_SSL_SESSION - convert SSL_SESSION object from/to ASN1 representation
 
 =head1 SYNOPSIS
 
@@ -10,6 +10,7 @@
 
  SSL_SESSION *d2i_SSL_SESSION(SSL_SESSION **a, const unsigned char **pp, long length);
  int i2d_SSL_SESSION(SSL_SESSION *in, unsigned char **pp);
+ int i2d_full_SSL_SESSION(SSL_SESSION *in, unsigned char **pp);
 
 =head1 DESCRIPTION
 
@@ -17,10 +18,12 @@
 session, stored as binary data at location B<pp> with length B<length>, into
 an SSL_SESSION object.
 
-i2d_SSL_SESSION() transforms the SSL_SESSION object B<in> into the ASN1
-representation and stores it into the memory location pointed to by B<pp>.
-The length of the resulting ASN1 representation is returned. If B<pp> is
-the NULL pointer, only the length is calculated and returned.
+i2d_SSL_SESSION() and i2d_full_SSL_SESSION() transform the SSL_SESSION object
+B<in> into the ASN1 representation and store it into the memory location
+pointed to by B<pp>. i2d_SSL_SESSION() does not store the peer's certificate
+chain, while i2d_full_SSL_SESSION() does. In either case, the length of the
+resulting ASN1 representation is returned. If B<pp> is the NULL pointer, only
+the length is calculated and returned.
 
 =head1 NOTES
 
@@ -42,8 +45,9 @@
 only be used with one SSL_CTX object (and the SSL objects created
 from this SSL_CTX object).
 
-When using i2d_SSL_SESSION(), the memory location pointed to by B<pp> must be
-large enough to hold the binary representation of the session. There is no
+When using i2d_SSL_SESSION() or i2d_full_SSL_SESSION(), the memory location
+pointed to by B<pp> must be large enough to hold the binary representation of
+the session (either with or without the peer's certificate chain). There is no
 known limit on the size of the created ASN1 representation, so the necessary
 amount of space should be obtained by first calling i2d_SSL_SESSION() with
 B<pp=NULL>, and obtain the size needed, then allocate the memory and
@@ -55,8 +59,9 @@
 object. In case of failure the NULL-pointer is returned and the error message
 can be retrieved from the error stack.
 
-i2d_SSL_SESSION() returns the size of the ASN1 representation in bytes.
-When the session is not valid, B<0> is returned and no operation is performed.
+i2d_SSL_SESSION() and i2d_SSL_SESSION() return the size of the ASN1i
+representation in bytes. When the session is not valid, B<0> is returned and no
+operation is performed.
 
 =head1 SEE ALSO
 
diff -Naur openssl-1.0.0/doc/ssl/SSL_SESSION_get_ex_new_index.pod openssl-1.0.0.patched/doc/ssl/SSL_SESSION_get_ex_new_index.pod
--- openssl-1.0.0/doc/ssl/SSL_SESSION_get_ex_new_index.pod	2005-03-30 06:50:14.000000000 -0500
+++ openssl-1.0.0.patched/doc/ssl/SSL_SESSION_get_ex_new_index.pod	2010-06-04 16:05:55.000000000 -0400
@@ -48,9 +48,9 @@
 
 The application data is only maintained for sessions held in memory. The
 application data is not included when dumping the session with
-i2d_SSL_SESSION() (and all functions indirectly calling the dump functions
-like PEM_write_SSL_SESSION() and PEM_write_bio_SSL_SESSION()) and can
-therefore not be restored.
+i2d_SSL_SESSION() or i2d_full_SSL_SESSION() (and all functions indirectly
+calling the dump functions like PEM_write_SSL_SESSION() and
+PEM_write_bio_SSL_SESSION()) and can therefore not be restored.
 
 =head1 SEE ALSO
 
diff -Naur openssl-1.0.0/doc/ssleay.txt openssl-1.0.0.patched/doc/ssleay.txt
--- openssl-1.0.0/doc/ssleay.txt	2009-04-16 13:22:48.000000000 -0400
+++ openssl-1.0.0.patched/doc/ssleay.txt	2010-06-04 15:57:54.000000000 -0400
@@ -5689,11 +5689,12 @@
 does not add it to the cache.  Just call SSL_CTX_add_session() if you do want the
 session added.  For a 'client' this would not normally be the case.
 SSL_CTX_add_session() is not normally ever used, except for doing 'evil' things
-which the next 2 funtions help you do.
+which the next 3 functions help you do.
 
 int     i2d_SSL_SESSION(SSL_SESSION *in,unsigned char **pp);
+int     i2d_full_SSL_SESSION(SSL_SESSION *in,unsigned char **pp);
 SSL_SESSION *d2i_SSL_SESSION(SSL_SESSION **a,unsigned char **pp,long length);
-These 2 functions are in the standard ASN1 library form and can be used to
+These 3 functions are in the standard ASN1 library form and can be used to
 load and save to a byte format, the SSL_SESSION structure.
 With these functions, you can save and read these structures to a files or
 arbitary byte string.
@@ -6214,6 +6215,7 @@
 SSL_SESSION_print
 SSL_SESSION_free
 i2d_SSL_SESSION
+i2d_full_SSL_SESSION
 d2i_SSL_SESSION
 
 SSL_get_time
diff -Naur openssl-1.0.0/ssl/ssl_asn1.c openssl-1.0.0.patched/ssl/ssl_asn1.c
--- openssl-1.0.0/ssl/ssl_asn1.c	2010-02-01 11:49:42.000000000 -0500
+++ openssl-1.0.0.patched/ssl/ssl_asn1.c	2010-06-04 19:15:43.000000000 -0400
@@ -89,6 +89,10 @@
 #include <openssl/objects.h>
 #include <openssl/x509.h>
 
+#define I2D_FLAGS_NONE	0x00
+#define I2D_FLAGS_FULL	0x01
+int i2d_SSL_SESSION_real(SSL_SESSION *in, unsigned char **pp, int flags);
+
 typedef struct ssl_session_asn1_st
 	{
 	ASN1_INTEGER version;
@@ -118,8 +122,18 @@
 
 int i2d_SSL_SESSION(SSL_SESSION *in, unsigned char **pp)
 	{
+	return(i2d_SSL_SESSION_real(in, pp, I2D_FLAGS_NONE));
+	}
+
+int i2d_full_SSL_SESSION(SSL_SESSION *in, unsigned char **pp)
+	{
+	return(i2d_SSL_SESSION_real(in, pp, I2D_FLAGS_FULL));
+	}
+
+int i2d_SSL_SESSION_real(SSL_SESSION *in, unsigned char **pp, int flags)
+	{
 #define LSIZE2 (sizeof(long)*2)
-	int v1=0,v2=0,v3=0,v4=0,v5=0,v7=0,v8=0;
+	int v1=0,v2=0,v3=0,v4=0,v5=0,v7=0,v8=0,v12=0;
 	unsigned char buf[4],ibuf1[LSIZE2],ibuf2[LSIZE2];
 	unsigned char ibuf3[LSIZE2],ibuf4[LSIZE2],ibuf5[LSIZE2];
 #ifndef OPENSSL_NO_TLSEXT
@@ -285,6 +299,8 @@
 		M_ASN1_I2D_len_EXP_opt(&(a.timeout),i2d_ASN1_INTEGER,2,v2);
 	if (in->peer != NULL)
 		M_ASN1_I2D_len_EXP_opt(in->peer,i2d_X509,3,v3);
+	if (flags & I2D_FLAGS_FULL && in->sess_cert != NULL && in->sess_cert->cert_chain != NULL)
+		M_ASN1_I2D_len_EXP_SEQUENCE_opt_type(X509,in->sess_cert->cert_chain,i2d_X509,12,V_ASN1_SEQUENCE,v12);
 	M_ASN1_I2D_len_EXP_opt(&a.session_id_context,i2d_ASN1_OCTET_STRING,4,v4);
 	if (in->verify_result != X509_V_OK)
 		M_ASN1_I2D_len_EXP_opt(&(a.verify_result),i2d_ASN1_INTEGER,5,v5);
@@ -327,6 +343,8 @@
 		M_ASN1_I2D_put_EXP_opt(&(a.timeout),i2d_ASN1_INTEGER,2,v2);
 	if (in->peer != NULL)
 		M_ASN1_I2D_put_EXP_opt(in->peer,i2d_X509,3,v3);
+	if (flags & I2D_FLAGS_FULL && in->sess_cert != NULL && in->sess_cert->cert_chain != NULL)
+		M_ASN1_I2D_put_EXP_SEQUENCE_opt_type(X509,in->sess_cert->cert_chain,i2d_X509,12,V_ASN1_SEQUENCE,v12);
 	M_ASN1_I2D_put_EXP_opt(&a.session_id_context,i2d_ASN1_OCTET_STRING,4,
 			       v4);
 	if (in->verify_result != X509_V_OK)
@@ -491,6 +509,15 @@
 		}
 	M_ASN1_D2I_get_EXP_opt(ret->peer,d2i_X509,3);
 
+	if (ret->sess_cert == NULL)
+		ret->sess_cert = ssl_sess_cert_new();
+	if (ret->sess_cert != NULL && ret->sess_cert->cert_chain != NULL)
+		{
+		sk_X509_pop_free(ret->sess_cert->cert_chain,X509_free);
+		ret->sess_cert->cert_chain=NULL;
+		}
+	M_ASN1_D2I_get_EXP_set_opt_type(X509,ret->sess_cert->cert_chain,d2i_X509,X509_free,12,V_ASN1_SEQUENCE);
+
 	os.length=0;
 	os.data=NULL;
 	M_ASN1_D2I_get_EXP_opt(osp,d2i_ASN1_OCTET_STRING,4);
diff -Naur openssl-1.0.0/ssl/ssl.h openssl-1.0.0.patched/ssl/ssl.h
--- openssl-1.0.0/ssl/ssl.h	2010-01-06 12:37:38.000000000 -0500
+++ openssl-1.0.0.patched/ssl/ssl.h	2010-06-03 19:56:56.000000000 -0400
@@ -435,7 +435,8 @@
  *	HostName [ 6 ] EXPLICIT OCTET STRING,   -- optional HostName from servername TLS extension 
  *	ECPointFormatList [ 7 ] OCTET STRING,     -- optional EC point format list from TLS extension
  *	PSK_identity_hint [ 8 ] EXPLICIT OCTET STRING, -- optional PSK identity hint
- *	PSK_identity [ 9 ] EXPLICIT OCTET STRING -- optional PSK identity
+ *	PSK_identity [ 9 ] EXPLICIT OCTET STRING, -- optional PSK identity
+ *	Peer_chain [ 12 ] EXPLICIT SEQUENCE OF X509 -- optional Peer Certificate Chain
  *	}
  * Look in ssl/ssl_asn1.c for more details
  * I'm using EXPLICIT tags so I can read the damn things using asn1parse :-).
@@ -469,13 +470,13 @@
 #endif
 	int not_resumable;
 
-	/* The cert is the certificate used to establish this connection */
+	/* The cert is the certificate used to establish this connection.
+	 * This may or may not be retained in the external representation of
+	 * sessions, see ssl_asn1.c. */
 	struct sess_cert_st /* SESS_CERT */ *sess_cert;
 
 	/* This is the cert for the other end.
-	 * On clients, it will be the same as sess_cert->peer_key->x509
-	 * (the latter is not enough as sess_cert is not retained
-	 * in the external representation of sessions, see ssl_asn1.c). */
+	 * On clients, it will be the same as sess_cert->peer_key->x509. */
 	X509 *peer;
 	/* when app_verify_callback accepts a session where the peer's certificate
 	 * is not ok, we must remember the error for session reuse: */
@@ -1558,6 +1559,7 @@
 #endif
 void	SSL_SESSION_free(SSL_SESSION *ses);
 int	i2d_SSL_SESSION(SSL_SESSION *in,unsigned char **pp);
+int	i2d_full_SSL_SESSION(SSL_SESSION *in,unsigned char **pp);
 int	SSL_set_session(SSL *to, SSL_SESSION *session);
 int	SSL_CTX_add_session(SSL_CTX *s, SSL_SESSION *c);
 int	SSL_CTX_remove_session(SSL_CTX *,SSL_SESSION *c);
diff -Naur httpd-trunk/docs/conf/extra/httpd-ssl.conf.in httpd-trunk.cachechain/docs/conf/extra/httpd-ssl.conf.in
--- httpd-trunk/docs/conf/extra/httpd-ssl.conf.in	2010-06-07 12:12:43.000000000 -0400
+++ httpd-trunk.cachechain/docs/conf/extra/httpd-ssl.conf.in	2010-06-07 15:18:24.000000000 -0400
@@ -51,10 +51,12 @@
 
 #   Inter-Process Session Cache:
 #   Configure the SSL Session Cache: First the mechanism 
-#   to use and second the expiring timeout (in seconds).
+#   to use, second the expiring timeout (in seconds), and 
+#   third whether to cache the client's certificate chain.
 #SSLSessionCache         "dbm:@exp_runtimedir@/ssl_scache"
 SSLSessionCache        "shmcb:@exp_runtimedir@/ssl_scache(512000)"
 SSLSessionCacheTimeout  300
+SSLSessionCacheChain    Off
 
 ##
 ## SSL Virtual Host Context
diff -Naur httpd-trunk/docs/manual/mod/mod_ssl.xml httpd-trunk.cachechain/docs/manual/mod/mod_ssl.xml
--- httpd-trunk/docs/manual/mod/mod_ssl.xml	2010-06-07 12:13:02.000000000 -0400
+++ httpd-trunk.cachechain/docs/manual/mod/mod_ssl.xml	2010-06-08 14:08:36.000000000 -0400
@@ -457,6 +457,45 @@
 </directivesynopsis>
 
 <directivesynopsis>
+<name>SSLSessionCacheChain</name>
+<description>SSL Client Certificate Chain Caching Switch</description>
+<syntax>SSLSessionCacheChain <em>on|off</em></syntax>
+<default>SSLSessionCacheChain off</default>
+<contextlist><context>server config</context>
+<context>virtual host</context></contextlist>
+
+<usage>
+<p>
+This directive toggles the caching of client chain certificates in the
+global/inter-process SSL Session Cache.  This directive has no effect if
+<directive module="mod_ssl">SSLSessionCache</directive> is set to <em>none</em>
+or <em>nonenotnull</em> (OpenSSL's internal memory cache will always store the
+client's chain certificates).
+</p>
+<p>
+Client certificate validation is generally performed only when a client first
+connects to the server. Subsequent SSL connections within the same session
+generally do not need to re-validate the client certificate, so the client's
+chain certificates do not need to be stored in the SSL Session Cache. Since
+each certificate can be quite large (2KB), and a client can have several chain
+certificates, it is generally helpful to leave the chain certificates out of the
+SSL Session Cache to save space.
+</p>
+<p>
+However, leaving the chain certificates out of the SSL Session Cache may cause
+problems for web applications that independently validate client certificates.
+For example, if you have an application that redirects to a login
+page that then independently validates the client's certificate, the request for
+the login page would arrive in the second SSL connection within the session, so
+the chain certificates would be unavailable, and the login page would be unable
+to validate the client's certificate.  In this case,
+<code>SSLSessionCacheChain On</code> may be used to ensure that all SSL
+connections within a session are able to access the chain certificates.
+</p>
+</usage>
+</directivesynopsis>
+
+<directivesynopsis>
 <name>SSLEngine</name>
 <description>SSL Engine Operation Switch</description>
 <syntax>SSLEngine on|off|optional</syntax>
diff -Naur httpd-trunk/modules/ssl/mod_ssl.c httpd-trunk.cachechain/modules/ssl/mod_ssl.c
--- httpd-trunk/modules/ssl/mod_ssl.c	2010-06-07 12:13:04.000000000 -0400
+++ httpd-trunk.cachechain/modules/ssl/mod_ssl.c	2010-06-07 16:59:02.000000000 -0400
@@ -121,6 +121,9 @@
     SSL_CMD_SRV(SessionCacheTimeout, TAKE1,
                 "SSL Session Cache object lifetime "
                 "('N' - number of seconds)")
+    SSL_CMD_SRV(SessionCacheChain, FLAG,
+                "SSL Session Cache switch for caching of client chain certificates "
+                "('on', 'off')")
     SSL_CMD_SRV(Protocol, RAW_ARGS,
                 "Enable or disable various SSL protocols"
                 "('[+-][SSLv2|SSLv3|TLSv1] ...' - see manual)")
diff -Naur httpd-trunk/modules/ssl/ssl_engine_config.c httpd-trunk.cachechain/modules/ssl/ssl_engine_config.c
--- httpd-trunk/modules/ssl/ssl_engine_config.c	2010-06-07 12:13:04.000000000 -0400
+++ httpd-trunk.cachechain/modules/ssl/ssl_engine_config.c	2010-06-07 15:20:12.000000000 -0400
@@ -184,6 +184,7 @@
     sc->vhost_id               = NULL;  /* set during module init */
     sc->vhost_id_len           = 0;     /* set during module init */
     sc->session_cache_timeout  = UNSET;
+    sc->session_cache_chain    = FALSE;
     sc->cipher_server_pref     = UNSET;
     sc->insecure_reneg         = UNSET;
     sc->proxy_ssl_check_peer_expire = SSL_ENABLED_UNSET;
@@ -296,6 +297,7 @@
     cfgMerge(enabled, SSL_ENABLED_UNSET);
     cfgMergeBool(proxy_enabled);
     cfgMergeInt(session_cache_timeout);
+    cfgMergeBool(session_cache_chain);
     cfgMergeBool(cipher_server_pref);
     cfgMergeBool(insecure_reneg);
     cfgMerge(proxy_ssl_check_peer_expire, SSL_ENABLED_UNSET);
@@ -1071,6 +1073,17 @@
     return NULL;
 }
 
+const char *ssl_cmd_SSLSessionCacheChain(cmd_parms *cmd,
+                                         void *dcfg,
+                                         int flag)
+{
+    SSLSrvConfigRec *sc = mySrvConfig(cmd->server);
+
+    sc->session_cache_chain = flag ? TRUE : FALSE;
+
+    return NULL;
+}
+
 const char *ssl_cmd_SSLOptions(cmd_parms *cmd,
                                void *dcfg,
                                const char *arg)
diff -Naur httpd-trunk/modules/ssl/ssl_private.h httpd-trunk.cachechain/modules/ssl/ssl_private.h
--- httpd-trunk/modules/ssl/ssl_private.h	2010-06-07 12:13:04.000000000 -0400
+++ httpd-trunk.cachechain/modules/ssl/ssl_private.h	2010-06-07 15:18:24.000000000 -0400
@@ -496,6 +496,7 @@
     const char      *vhost_id;
     int              vhost_id_len;
     int              session_cache_timeout;
+    BOOL             session_cache_chain;
     BOOL             cipher_server_pref;
     BOOL             insecure_reneg;
     modssl_ctx_t    *server;
@@ -565,6 +566,7 @@
 const char  *ssl_cmd_SSLVerifyDepth(cmd_parms *, void *, const char *);
 const char  *ssl_cmd_SSLSessionCache(cmd_parms *, void *, const char *);
 const char  *ssl_cmd_SSLSessionCacheTimeout(cmd_parms *, void *, const char *);
+const char  *ssl_cmd_SSLSessionCacheChain(cmd_parms *, void *, int);
 const char  *ssl_cmd_SSLProtocol(cmd_parms *, void *, const char *);
 const char  *ssl_cmd_SSLOptions(cmd_parms *, void *, const char *);
 const char  *ssl_cmd_SSLRequireSSL(cmd_parms *, void *);
diff -Naur httpd-trunk/modules/ssl/ssl_scache.c httpd-trunk.cachechain/modules/ssl/ssl_scache.c
--- httpd-trunk/modules/ssl/ssl_scache.c	2010-06-07 12:12:51.000000000 -0400
+++ httpd-trunk.cachechain/modules/ssl/ssl_scache.c	2010-06-07 15:28:23.000000000 -0400
@@ -116,26 +116,30 @@
                       apr_time_t expiry, SSL_SESSION *sess,
                       apr_pool_t *p)
 {
+    SSLSrvConfigRec *sc = mySrvConfig(s);
     SSLModConfigRec *mc = myModConfig(s);
     unsigned char encoded[SSL_SESSION_MAX_DER], *ptr;
     unsigned int len;
     apr_status_t rv;
 
-    /* Serialise the session. */
-    len = i2d_SSL_SESSION(sess, NULL);
+    /* Serialize the session. */
+    len = (sc->session_cache_chain ?
+           i2d_full_SSL_SESSION(sess, NULL) :
+           i2d_SSL_SESSION(sess, NULL));
     if (len > sizeof encoded) {
         ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
                      "session is too big (%u bytes)", len);
         return FALSE;
     }
-
     ptr = encoded;
-    len = i2d_SSL_SESSION(sess, &ptr);
+    len = (sc->session_cache_chain ?
+           i2d_full_SSL_SESSION(sess, &ptr) :
+           i2d_SSL_SESSION(sess, &ptr));
 
     if (mc->sesscache->flags & AP_SOCACHE_FLAG_NOTMPSAFE) {
         ssl_mutex_on(s);
     }
-    
+
     rv = mc->sesscache->store(mc->sesscache_context, s, id, idlen, 
                               expiry, encoded, len, p);
 
diff -Naur httpd-trunk/modules/ssl/ssl_util_ssl.h httpd-trunk.cachechain/modules/ssl/ssl_util_ssl.h
--- httpd-trunk/modules/ssl/ssl_util_ssl.h	2010-06-07 12:12:51.000000000 -0400
+++ httpd-trunk.cachechain/modules/ssl/ssl_util_ssl.h	2010-06-07 15:18:24.000000000 -0400
@@ -62,10 +62,10 @@
 
 /**
  *  Maximum length of a DER encoded session.
- *  FIXME: There is no define in OpenSSL, but OpenSSL uses 1024*10,
+ *  FIXME: There is no define in OpenSSL, but OpenSSL uses 0xFF00 (65280),
  *         so this value should be ok. Although we have no warm feeling.
  */
-#define SSL_SESSION_MAX_DER 1024*10
+#define SSL_SESSION_MAX_DER 0xFF00
 
 /** max length for SSL_SESSION_id2sz */
 #define SSL_SESSION_ID_STRING_LEN \

Reply via email to