This change allows a callback to be used to override the generation of
authorGeoff Thorpe <geoff@openssl.org>
Wed, 21 Feb 2001 18:06:26 +0000 (18:06 +0000)
committerGeoff Thorpe <geoff@openssl.org>
Wed, 21 Feb 2001 18:06:26 +0000 (18:06 +0000)
SSL/TLS session IDs in a server. According to RFC2246, the session ID is an
arbitrary value chosen by the server. It can be useful to have some control
over this "arbitrary value" so as to choose it in ways that can aid in
things like external session caching and balancing (eg. clustering). The
default session ID generation is to fill the ID with random data.

The callback used by default is built in to ssl_sess.c, but registering a
callback in an SSL_CTX or in a particular SSL overrides this. BTW: SSL
callbacks will override SSL_CTX callbacks, and a new SSL structure inherits
any callback set in its 'parent' SSL_CTX. The header comments describe how
this mechanism ticks, and source code comments describe (hopefully) why it
ticks the way it does.

Man pages are on the way ...

[NB: Lutz was also hacking away and helping me to figure out how best to do
this.]

ssl/ssl.h
ssl/ssl_err.c
ssl/ssl_lib.c
ssl/ssl_sess.c
util/ssleay.num

index de87c2e..e80ed98 100644 (file)
--- a/ssl/ssl.h
+++ b/ssl/ssl.h
@@ -391,6 +391,22 @@ typedef struct ssl_session_st
 
 #define SSL_SESSION_CACHE_MAX_SIZE_DEFAULT     (1024*20)
 
+/* This callback type is used inside SSL_CTX, SSL, and in the functions that set
+ * them. It is used to override the generation of SSL/TLS session IDs in a
+ * server. Return value should be zero on an error, non-zero to proceed. Also,
+ * callbacks should themselves check if the id they generate is unique otherwise
+ * the SSL handshake will fail with an error - callbacks can do this using the
+ * 'ssl' value they're passed by;
+ *      SSL_CTX_has_matching_session_id(ssl->ctx, id, *id_len)
+ * The length value passed in is set at the maximum size the session ID can be.
+ * In SSLv2 this is 16 bytes, whereas SSLv3/TLSv1 it is 32 bytes. The callback
+ * can alter this length to be less if desired, but under SSLv2 session IDs are
+ * supposed to be fixed at 16 bytes so the id will be padded after the callback
+ * returns in this case. It is also an error for the callback to set the size to
+ * zero. */
+typedef int (*GEN_SESSION_CB)(const SSL *ssl, unsigned char *id,
+                               unsigned int *id_len);
+
 typedef struct ssl_comp_st
        {
        int id;
@@ -486,6 +502,9 @@ struct ssl_ctx_st
        int purpose;            /* Purpose setting */
        int trust;              /* Trust setting */
 
+       /* Default generate session ID callback. */
+       GEN_SESSION_CB generate_session_id;
+
        /* Default password callback. */
 /**/   pem_password_cb *default_passwd_callback;
 
@@ -674,6 +693,9 @@ struct ssl_st
        /* This can also be in the session once a session is established */
        SSL_SESSION *session;
 
+       /* Default generate session ID callback. */
+       GEN_SESSION_CB generate_session_id;
+
        /* Used in SSL2 and SSL3 */
        int verify_mode;        /* 0 don't care about verify failure.
                                 * 1 fail if verify fails */
@@ -1029,6 +1051,10 @@ int      i2d_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);
+int    SSL_CTX_set_generate_session_id(SSL_CTX *, GEN_SESSION_CB);
+int    SSL_set_generate_session_id(SSL *, GEN_SESSION_CB);
+int    SSL_CTX_has_matching_session_id(const SSL_CTX *ctx, const unsigned char *id,
+                                       unsigned int id_len);
 SSL_SESSION *d2i_SSL_SESSION(SSL_SESSION **a,unsigned char **pp,long length);
 
 #ifdef HEADER_X509_H
@@ -1524,6 +1550,9 @@ int SSL_COMP_add_compression_method(int id,char *cm);
 #define SSL_R_SSL_CTX_HAS_NO_DEFAULT_SSL_VERSION        228
 #define SSL_R_SSL_HANDSHAKE_FAILURE                     229
 #define SSL_R_SSL_LIBRARY_HAS_NO_CIPHERS                230
+#define SSL_R_SSL_SESSION_ID_HAS_BAD_LENGTH             1101
+#define SSL_R_SSL_SESSION_ID_CALLBACK_FAILED            1102
+#define SSL_R_SSL_SESSION_ID_CONFLICT                   1103
 #define SSL_R_SSL_SESSION_ID_CONTEXT_TOO_LONG           273
 #define SSL_R_SSL_SESSION_ID_IS_DIFFERENT               231
 #define SSL_R_TLSV1_ALERT_ACCESS_DENIED                         1049
index 4a46099..133b619 100644 (file)
@@ -366,6 +366,9 @@ static ERR_STRING_DATA SSL_str_reasons[]=
 {SSL_R_SSL_CTX_HAS_NO_DEFAULT_SSL_VERSION,"ssl ctx has no default ssl version"},
 {SSL_R_SSL_HANDSHAKE_FAILURE             ,"ssl handshake failure"},
 {SSL_R_SSL_LIBRARY_HAS_NO_CIPHERS        ,"ssl library has no ciphers"},
+{SSL_R_SSL_SESSION_ID_HAS_BAD_LENGTH     ,"ssl session id has bad length"},
+{SSL_R_SSL_SESSION_ID_CALLBACK_FAILED    ,"ssl session id callback failed"},
+{SSL_R_SSL_SESSION_ID_CONFLICT           ,"ssl session id conflict"},
 {SSL_R_SSL_SESSION_ID_CONTEXT_TOO_LONG   ,"ssl session id context too long"},
 {SSL_R_SSL_SESSION_ID_IS_DIFFERENT       ,"ssl session id is different"},
 {SSL_R_TLSV1_ALERT_ACCESS_DENIED         ,"tlsv1 alert access denied"},
index 21e4d6e..7864f2f 100644 (file)
@@ -218,6 +218,7 @@ SSL *SSL_new(SSL_CTX *ctx)
        s->verify_mode=ctx->verify_mode;
        s->verify_depth=ctx->verify_depth;
        s->verify_callback=ctx->default_verify_callback;
+       s->generate_session_id=ctx->generate_session_id;
        s->purpose = ctx->purpose;
        s->trust = ctx->trust;
        CRYPTO_add(&ctx->references,1,CRYPTO_LOCK_SSL_CTX);
@@ -282,6 +283,41 @@ int SSL_set_session_id_context(SSL *ssl,const unsigned char *sid_ctx,
     return 1;
     }
 
+int SSL_CTX_set_generate_session_id(SSL_CTX *ctx, GEN_SESSION_CB cb)
+       {
+       CRYPTO_w_lock(CRYPTO_LOCK_SSL_CTX);
+       ctx->generate_session_id = cb;
+       CRYPTO_w_unlock(CRYPTO_LOCK_SSL_CTX);
+       return 1;
+       }
+
+int SSL_set_generate_session_id(SSL *ssl, GEN_SESSION_CB cb)
+       {
+       CRYPTO_w_lock(CRYPTO_LOCK_SSL);
+       ssl->generate_session_id = cb;
+       CRYPTO_w_unlock(CRYPTO_LOCK_SSL);
+       return 1;
+       }
+
+int SSL_CTX_has_matching_session_id(const SSL_CTX *ctx, const unsigned char *id,
+                               unsigned int id_len)
+       {
+       /* A quick examination of SSL_SESSION_hash and SSL_SESSION_cmp shows how
+        * we can "construct" a session to give us the desired check - ie. to
+        * find if there's a session in the hash table that would conflict with
+        * any new session built out of this id/id_len and the ssl_version in
+        * use by this SSL_CTX. */
+       SSL_SESSION r, *p;
+       r.ssl_version = ctx->method->version;
+       r.session_id_length = id_len;
+       memcpy(r.session_id, id, id_len);
+
+       CRYPTO_r_lock(CRYPTO_LOCK_SSL_CTX);
+       p = (SSL_SESSION *)lh_retrieve(ctx->sessions, &r);
+       CRYPTO_r_unlock(CRYPTO_LOCK_SSL_CTX);
+       return (p != NULL);
+       }
+
 int SSL_CTX_set_purpose(SSL_CTX *s, int purpose)
 {
        if(X509_PURPOSE_get_by_id(purpose) == -1) {
@@ -1092,6 +1128,11 @@ unsigned long SSL_SESSION_hash(SSL_SESSION *a)
        return(l);
        }
 
+/* NB: If this function (or indeed the hash function which uses a sort of
+ * coarser function than this one) is changed, ensure
+ * SSL_CTX_has_matching_session_id() is checked accordingly. It relies on being
+ * able to construct an SSL_SESSION that will collide with any existing session
+ * with a matching session ID. */
 int SSL_SESSION_cmp(SSL_SESSION *a,SSL_SESSION *b)
        {
        if (a->ssl_version != b->ssl_version)
@@ -1143,6 +1184,7 @@ SSL_CTX *SSL_CTX_new(SSL_METHOD *meth)
        ret->new_session_cb=NULL;
        ret->remove_session_cb=NULL;
        ret->get_session_cb=NULL;
+       ret->generate_session_id=NULL;
 
        memset((char *)&ret->stats,0,sizeof(ret->stats));
 
index 9364612..a5270ce 100644 (file)
@@ -130,11 +130,45 @@ SSL_SESSION *SSL_SESSION_new(void)
        return(ss);
        }
 
+/* Even with SSLv2, we have 16 bytes (128 bits) of session ID space. SSLv3/TLSv1
+ * has 32 bytes (256 bits). As such, filling the ID with random gunk repeatedly
+ * until we have no conflict is going to complete in one iteration pretty much
+ * "most" of the time (btw: understatement). So, if it takes us 10 iterations
+ * and we still can't avoid a conflict - well that's a reasonable point to call
+ * it quits. Either the RAND code is broken or someone is trying to open roughly
+ * very close to 2^128 (or 2^256) SSL sessions to our server. How you might
+ * store that many sessions is perhaps a more interesting question ... */
+
+#define MAX_SESS_ID_ATTEMPTS 10
+static int def_generate_session_id(const SSL *ssl, unsigned char *id,
+                               unsigned int *id_len)
+{
+       unsigned int retry = 0;
+       do
+               RAND_pseudo_bytes(id, *id_len);
+       while(SSL_CTX_has_matching_session_id(ssl->ctx, id, *id_len) &&
+               (++retry < MAX_SESS_ID_ATTEMPTS));
+       if(retry < MAX_SESS_ID_ATTEMPTS)
+               return 1;
+       /* else - woops a session_id match */
+       /* XXX We should also check the external cache --
+        * but the probability of a collision is negligible, and
+        * we could not prevent the concurrent creation of sessions
+        * with identical IDs since we currently don't have means
+        * to atomically check whether a session ID already exists
+        * and make a reservation for it if it does not
+        * (this problem applies to the internal cache as well).
+        */
+       return 0;
+}
+
 int ssl_get_new_session(SSL *s, int session)
        {
        /* This gets used by clients and servers. */
 
+       unsigned int tmp;
        SSL_SESSION *ss=NULL;
+       GEN_SESSION_CB cb = def_generate_session_id;
 
        if ((ss=SSL_SESSION_new()) == NULL) return(0);
 
@@ -173,25 +207,46 @@ int ssl_get_new_session(SSL *s, int session)
                        SSL_SESSION_free(ss);
                        return(0);
                        }
-
-               for (;;)
+               /* Choose which callback will set the session ID */
+               CRYPTO_r_lock(CRYPTO_LOCK_SSL_CTX);
+               if(s->generate_session_id)
+                       cb = s->generate_session_id;
+               else if(s->ctx->generate_session_id)
+                       cb = s->ctx->generate_session_id;
+               CRYPTO_r_unlock(CRYPTO_LOCK_SSL_CTX);
+               /* Choose a session ID */
+               tmp = ss->session_id_length;
+               if(!cb(s, ss->session_id, &tmp))
                        {
-                       SSL_SESSION *r;
-
-                       RAND_pseudo_bytes(ss->session_id,ss->session_id_length);
-                       CRYPTO_r_lock(CRYPTO_LOCK_SSL_CTX);
-                       r=(SSL_SESSION *)lh_retrieve(s->ctx->sessions, ss);
-                       CRYPTO_r_unlock(CRYPTO_LOCK_SSL_CTX);
-                       if (r == NULL) break;
-                       /* else - woops a session_id match */
-                       /* XXX We should also check the external cache --
-                        * but the probability of a collision is negligible, and
-                        * we could not prevent the concurrent creation of sessions
-                        * with identical IDs since we currently don't have means
-                        * to atomically check whether a session ID already exists
-                        * and make a reservation for it if it does not
-                        * (this problem applies to the internal cache as well).
-                        */
+                       /* The callback failed */
+                       SSLerr(SSL_F_SSL_GET_NEW_SESSION,
+                               SSL_R_SSL_SESSION_ID_CALLBACK_FAILED);
+                       SSL_SESSION_free(ss);
+                       return(0);
+                       }
+               /* Don't allow the callback to set the session length to zero.
+                * nor set it higher than it was. */
+               if(!tmp || (tmp > ss->session_id_length))
+                       {
+                       /* The callback set an illegal length */
+                       SSLerr(SSL_F_SSL_GET_NEW_SESSION,
+                               SSL_R_SSL_SESSION_ID_HAS_BAD_LENGTH);
+                       SSL_SESSION_free(ss);
+                       return(0);
+                       }
+               /* If the session length was shrunk and we're SSLv2, pad it */
+               if((tmp < ss->session_id_length) && (s->version == SSL2_VERSION))
+                       memset(ss->session_id + tmp, 0, ss->session_id_length - tmp);
+               else
+                       ss->session_id_length = tmp;
+               /* Finally, check for a conflict */
+               if(SSL_CTX_has_matching_session_id(s->ctx, ss->session_id,
+                                               ss->session_id_length))
+                       {
+                       SSLerr(SSL_F_SSL_GET_NEW_SESSION,
+                               SSL_R_SSL_SESSION_ID_CONFLICT);
+                       SSL_SESSION_free(ss);
+                       return(0);
                        }
                }
        else
index 26d38fb..840c927 100755 (executable)
@@ -208,3 +208,6 @@ kssl_ctx_free                           257 EXIST::FUNCTION:KRB5
 kssl_krb5_free_data_contents            258    EXIST::FUNCTION:KRB5
 print_krb5_data                         259    EXIST::FUNCTION:KRB5
 kssl_ctx_setstring                      260    EXIST::FUNCTION:KRB5
+SSL_CTX_has_matching_session_id         261    EXIST::FUNCTION:
+SSL_set_generate_session_id             262    EXIST::FUNCTION:
+SSL_CTX_set_generate_session_id         263    EXIST::FUNCTION: