# HG changeset patch # User Piotr Sikora <pi...@cloudflare.com> # Date 1380361691 25200 # Sat Sep 28 02:48:11 2013 -0700 # Node ID 6d3710969a18e2d0d817e297c2e17f941a58cd40 # Parent a720f0b0e08345ebb01353250f4031bb6e141385 SSL: added support for TLS Session Tickets (RFC5077).
Signed-off-by: Piotr Sikora <pi...@cloudflare.com> diff -r a720f0b0e083 -r 6d3710969a18 src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c Fri Sep 27 19:39:33 2013 +0400 +++ b/src/event/ngx_event_openssl.c Sat Sep 28 02:48:11 2013 -0700 @@ -38,6 +38,12 @@ static void ngx_ssl_expire_sessions(ngx_ static void ngx_ssl_session_rbtree_insert_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); +#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB +static int ngx_ssl_session_ticket_key_callback(ngx_ssl_conn_t *ssl_conn, + unsigned char *name, unsigned char *iv, EVP_CIPHER_CTX *ectx, + HMAC_CTX *hctx, int enc); +#endif + static void *ngx_openssl_create_conf(ngx_cycle_t *cycle); static char *ngx_openssl_engine(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static void ngx_openssl_exit(ngx_cycle_t *cycle); @@ -84,6 +90,9 @@ int ngx_ssl_server_conf_index; int ngx_ssl_session_cache_index; int ngx_ssl_certificate_index; int ngx_ssl_stapling_index; +#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB +int ngx_ssl_session_ticket_keys_index; +#endif ngx_int_t @@ -155,6 +164,18 @@ ngx_ssl_init(ngx_log_t *log) return NGX_ERROR; } +#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB + + ngx_ssl_session_ticket_keys_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, + NULL, NULL); + if (ngx_ssl_session_ticket_keys_index == -1) { + ngx_ssl_error(NGX_LOG_ALERT, log, 0, + "SSL_CTX_get_ex_new_index() failed"); + return NGX_ERROR; + } + +#endif + return NGX_OK; } @@ -2240,6 +2261,122 @@ ngx_ssl_session_rbtree_insert_value(ngx_ } +#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB + +ngx_int_t +ngx_ssl_session_ticket_keys(ngx_conf_t *cf, ngx_ssl_t *ssl, + ngx_ssl_session_ticket_keys_t *keys, time_t timeout) +{ + if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_session_ticket_keys_index, keys) + == 0) + { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "SSL_CTX_set_ex_data() failed"); + return NGX_ERROR; + } + + SSL_CTX_set_timeout(ssl->ctx, (long) timeout); + + if (SSL_CTX_set_tlsext_ticket_key_cb(ssl->ctx, + ngx_ssl_session_ticket_key_callback) + == 0) + { + ngx_log_error(NGX_LOG_WARN, cf->log, 0, + "nginx was built with Session Tickets support, however, " + "now it is linked dynamically to an OpenSSL library " + "which has no tlsext support, therefore Session Tickets " + "are not available"); + } + + return NGX_OK; +} + + +static int +ngx_ssl_session_ticket_key_callback(ngx_ssl_conn_t *ssl_conn, + unsigned char *name, unsigned char *iv, EVP_CIPHER_CTX *ectx, + HMAC_CTX *hctx, int enc) +{ + int rc; + SSL_CTX *ssl_ctx; + ngx_uint_t i; + ngx_ssl_session_ticket_key_t *key; + ngx_ssl_session_ticket_keys_t *keys; +#if (NGX_DEBUG) + ngx_connection_t *c; +#endif + + ssl_ctx = SSL_get_SSL_CTX(ssl_conn); + + keys = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_session_ticket_keys_index); + if (keys == NULL) { + return -1; + } + +#if (NGX_DEBUG) + c = ngx_ssl_get_connection(ssl_conn); +#endif + + if (enc == 1) { + /* encrypt session ticket */ + + key = keys->default_key; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "ssl session ticket encrypt, key: \"%*s\" (%s session)", + key->name_len, key->name, + SSL_session_reused(ssl_conn) ? "reused" : "new"); + + RAND_pseudo_bytes(iv, 16); + EVP_EncryptInit_ex(ectx, EVP_aes_128_cbc(), NULL, key->aes_key, iv); + HMAC_Init_ex(hctx, key->hmac_key, 16, ngx_ssl_session_ticket_md(), + NULL); + memcpy(name, key->name, 16); + + return 0; + + } else { + /* decrypt session ticket */ + + if (ngx_strncmp(name, keys->default_key->name, 16)) { + + key = keys->keys.elts; + for (i = 0; i < keys->keys.nelts; i++) { + if (ngx_strncmp(name, key[i].name, 16) == 0) { + break; + } + } + + if (i == keys->keys.nelts) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "ssl session ticket decrypt, key: \"%*s\" " + "not found", 16, name); + return 0; + } + + key = &key[i]; + rc = 2; /* success, renew */ + + } else { + key = keys->default_key; + rc = 1; /* success */ + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "ssl session ticket decrypt, key: \"%*s\"", + key->name_len, key->name); + + HMAC_Init_ex(hctx, key->hmac_key, 16, ngx_ssl_session_ticket_md(), + NULL); + EVP_DecryptInit_ex(ectx, EVP_aes_128_cbc(), NULL, key->aes_key, iv); + + return rc; + } +} + +#endif + + void ngx_ssl_cleanup_ctx(void *data) { diff -r a720f0b0e083 -r 6d3710969a18 src/event/ngx_event_openssl.h --- a/src/event/ngx_event_openssl.h Fri Sep 27 19:39:33 2013 +0400 +++ b/src/event/ngx_event_openssl.h Sat Sep 28 02:48:11 2013 -0700 @@ -83,6 +83,24 @@ typedef struct { } ngx_ssl_session_cache_t; +#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB + +typedef struct { + size_t name_len; + u_char name[16]; + + u_char aes_key[16]; + u_char hmac_key[16]; +} ngx_ssl_session_ticket_key_t; + + +typedef struct { + ngx_ssl_session_ticket_key_t *default_key; + ngx_array_t keys; +} ngx_ssl_session_ticket_keys_t; + +#endif + #define NGX_SSL_SSLv2 0x0002 #define NGX_SSL_SSLv3 0x0004 @@ -136,6 +154,16 @@ ngx_int_t ngx_ssl_set_session(ngx_connec || n == X509_V_ERR_CERT_UNTRUSTED \ || n == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE) +#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB +ngx_int_t ngx_ssl_session_ticket_keys(ngx_conf_t *cf, ngx_ssl_t *ssl, + ngx_ssl_session_ticket_keys_t *keys, time_t timeout); + +#ifdef OPENSSL_NO_SHA256 +#define ngx_ssl_session_ticket_md EVP_sha1 +#else +#define ngx_ssl_session_ticket_md EVP_sha256 +#endif +#endif ngx_int_t ngx_ssl_get_protocol(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s); @@ -175,6 +203,9 @@ extern int ngx_ssl_server_conf_index; extern int ngx_ssl_session_cache_index; extern int ngx_ssl_certificate_index; extern int ngx_ssl_stapling_index; +#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB +extern int ngx_ssl_session_ticket_keys_index; +#endif #endif /* _NGX_EVENT_OPENSSL_H_INCLUDED_ */ diff -r a720f0b0e083 -r 6d3710969a18 src/http/modules/ngx_http_ssl_module.c --- a/src/http/modules/ngx_http_ssl_module.c Fri Sep 27 19:39:33 2013 +0400 +++ b/src/http/modules/ngx_http_ssl_module.c Sat Sep 28 02:48:11 2013 -0700 @@ -38,6 +38,11 @@ static char *ngx_http_ssl_enable(ngx_con static char *ngx_http_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB +static char *ngx_http_ssl_session_ticket_key(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +#endif + static ngx_int_t ngx_http_ssl_init(ngx_conf_t *cf); @@ -153,6 +158,17 @@ static ngx_command_t ngx_http_ssl_comma 0, NULL }, +#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB + + { ngx_string("ssl_session_ticket_key"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE23, + ngx_http_ssl_session_ticket_key, + NGX_HTTP_SRV_CONF_OFFSET, + 0, + NULL }, + +#endif + { ngx_string("ssl_session_timeout"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_sec_slot, @@ -413,6 +429,7 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t * sscf->shm_zone = NULL; * sscf->stapling_file = { 0, NULL }; * sscf->stapling_responder = { 0, NULL }; + * sscf->session_ticket_keys = NULL; */ sscf->enable = NGX_CONF_UNSET; @@ -634,6 +651,30 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t * } +#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB + + if (conf->session_ticket_keys == NULL) { + conf->session_ticket_keys = prev->session_ticket_keys; + } + + if (conf->session_ticket_keys) { + if (conf->session_ticket_keys->default_key == NULL) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "default \"ssl_session_ticket_key\" is not defined"); + return NGX_CONF_ERROR; + } + + if (ngx_ssl_session_ticket_keys(cf, &conf->ssl, + conf->session_ticket_keys, + conf->session_timeout) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + } + +#endif + return NGX_CONF_OK; } @@ -769,6 +810,146 @@ invalid: return NGX_CONF_ERROR; } +#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB + +static char * +ngx_http_ssl_session_ticket_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_ssl_srv_conf_t *sscf = conf; + + char *rc; + u_char buf[32]; + ssize_t n; + ngx_str_t *value; + ngx_file_t file; + ngx_uint_t i; + ngx_file_info_t fi; + ngx_ssl_session_ticket_key_t *key, *k; + + if (sscf->session_ticket_keys == NULL) { + sscf->session_ticket_keys = ngx_pcalloc(cf->pool, + sizeof(ngx_ssl_session_ticket_keys_t)); + if (sscf->session_ticket_keys == NULL) { + return NGX_CONF_ERROR; + } + + if (ngx_array_init(&sscf->session_ticket_keys->keys, cf->pool, 4, + sizeof(ngx_ssl_session_ticket_key_t)) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + } + + key = ngx_array_push(&sscf->session_ticket_keys->keys); + if (key == NULL) { + return NGX_CONF_ERROR; + } + + value = cf->args->elts; + + if (value[1].len > 16) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"ssl_session_ticket_key\" name \"%V\" too long, " + "it cannot exceed 16 characters", &value[1]); + return NGX_CONF_ERROR; + } + + if (cf->args->nelts == 4) { + + if (ngx_strcmp(value[3].data, "default")) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter \"%V\"", &value[3]); + return NGX_CONF_ERROR; + } + + if (sscf->session_ticket_keys->default_key) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "default \"ssl_session_ticket_key\" is already " + "defined"); + return NGX_CONF_ERROR; + } + + sscf->session_ticket_keys->default_key = key; + } + + ngx_memzero(key->name, 16); + key->name_len = ngx_cpymem(key->name, value[1].data, value[1].len) + - key->name; + + k = sscf->session_ticket_keys->keys.elts; + for (i = 0; i < sscf->session_ticket_keys->keys.nelts - 1; i++) { + if (ngx_strncmp(key->name, k[i].name, 16) == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"ssl_session_ticket_key\" named \"%V\" " + "is already defined", &value[1]); + return NGX_CONF_ERROR; + } + } + + if (ngx_conf_full_name(cf->cycle, &value[2], 1) != NGX_OK) { + return NGX_CONF_ERROR; + } + + ngx_memzero(&file, sizeof(ngx_file_t)); + file.name = value[2]; + file.log = cf->log; + + file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY, 0, 0); + if (file.fd == NGX_INVALID_FILE) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno, + ngx_open_file_n " \"%V\" failed", &file.name); + + return NGX_CONF_ERROR; + } + + rc = NGX_CONF_ERROR; + + if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, + ngx_fd_info_n " \"%V\" failed", &file.name); + goto failed; + } + + if (ngx_file_size(&fi) != 32) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"%V\" must be 32 bytes", &file.name); + goto failed; + } + + n = ngx_read_file(&file, buf, 32, 0); + + if (n == NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, + ngx_read_file_n " \"%V\" failed", &file.name); + goto failed; + } + + if (n != 32) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, 0, + ngx_read_file_n " \"%V\" returned only %z bytes " + "instead of 32", &file.name, n); + goto failed; + } + + ngx_memcpy(key->aes_key, buf, 16); + ngx_memcpy(key->hmac_key, buf + 16, 16); + + rc = NGX_CONF_OK; + +failed: + + if (file.fd != NGX_INVALID_FILE) { + if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, + ngx_close_file_n " \"%V\" failed", &file.name); + } + } + + return rc; +} + +#endif static ngx_int_t ngx_http_ssl_init(ngx_conf_t *cf) diff -r a720f0b0e083 -r 6d3710969a18 src/http/modules/ngx_http_ssl_module.h --- a/src/http/modules/ngx_http_ssl_module.h Fri Sep 27 19:39:33 2013 +0400 +++ b/src/http/modules/ngx_http_ssl_module.h Sat Sep 28 02:48:11 2013 -0700 @@ -49,6 +49,10 @@ typedef struct { u_char *file; ngx_uint_t line; + +#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB + ngx_ssl_session_ticket_keys_t *session_ticket_keys; +#endif } ngx_http_ssl_srv_conf_t; _______________________________________________ nginx-devel mailing list nginx-devel@nginx.org http://mailman.nginx.org/mailman/listinfo/nginx-devel