diff --git a/ssl/ssl.h b/ssl/ssl.h index de87c2e6c..e80ed9869 100644 --- 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 diff --git a/ssl/ssl_err.c b/ssl/ssl_err.c index 4a4609900..133b6193c 100644 --- a/ssl/ssl_err.c +++ b/ssl/ssl_err.c @@ -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"}, diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c index 21e4d6e92..7864f2f7b 100644 --- a/ssl/ssl_lib.c +++ b/ssl/ssl_lib.c @@ -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)); diff --git a/ssl/ssl_sess.c b/ssl/ssl_sess.c index 9364612e4..a5270ce50 100644 --- a/ssl/ssl_sess.c +++ b/ssl/ssl_sess.c @@ -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 diff --git a/util/ssleay.num b/util/ssleay.num index 26d38fb48..840c92747 100755 --- a/util/ssleay.num +++ b/util/ssleay.num @@ -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: