Added TLS session tickets support.

This commit is contained in:
Andrey Suvorov
2021-08-17 16:52:32 -07:00
parent 3bd60e317c
commit e0aa132172
6 changed files with 442 additions and 0 deletions

View File

@@ -66,6 +66,23 @@ if [ $NXT_OPENSSL = YES ]; then
return 0; return 0;
}" }"
. auto/feature . auto/feature
nxt_feature="OpenSSL tlsext support"
nxt_feature_name=NXT_HAVE_OPENSSL_TLSEXT
nxt_feature_run=
nxt_feature_incs=
nxt_feature_libs="$NXT_OPENSSL_LIBS"
nxt_feature_test="#include <openssl/ssl.h>
int main() {
#if (OPENSSL_NO_TLSEXT)
#error OpenSSL: no tlsext support.
#else
return 0;
#endif
}"
. auto/feature
fi fi

View File

@@ -31,6 +31,12 @@ NGINX Unit updated to 1.25.0.
date="" time="" date="" time=""
packager="Andrei Belov &lt;defan@nginx.com&gt;"> packager="Andrei Belov &lt;defan@nginx.com&gt;">
<change type="feature">
<para>
TLS session tickets.
</para>
</change>
<change type="feature"> <change type="feature">
<para> <para>
TLS sessions cache. TLS sessions cache.

View File

@@ -99,6 +99,12 @@ static nxt_int_t nxt_conf_vldt_tls_cache_size(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value, void *data); nxt_conf_value_t *value, void *data);
static nxt_int_t nxt_conf_vldt_tls_timeout(nxt_conf_validation_t *vldt, static nxt_int_t nxt_conf_vldt_tls_timeout(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value, void *data); nxt_conf_value_t *value, void *data);
#if (NXT_HAVE_OPENSSL_TLSEXT)
static nxt_int_t nxt_conf_vldt_ticket_key(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value, void *data);
static nxt_int_t nxt_conf_vldt_ticket_key_element(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value);
#endif
#endif #endif
static nxt_int_t nxt_conf_vldt_action(nxt_conf_validation_t *vldt, static nxt_int_t nxt_conf_vldt_action(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value, void *data); nxt_conf_value_t *value, void *data);
@@ -428,6 +434,17 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_session_members[] = {
.name = nxt_string("timeout"), .name = nxt_string("timeout"),
.type = NXT_CONF_VLDT_INTEGER, .type = NXT_CONF_VLDT_INTEGER,
.validator = nxt_conf_vldt_tls_timeout, .validator = nxt_conf_vldt_tls_timeout,
}, {
.name = nxt_string("tickets"),
.type = NXT_CONF_VLDT_STRING
| NXT_CONF_VLDT_ARRAY
| NXT_CONF_VLDT_BOOLEAN,
#if (NXT_HAVE_OPENSSL_TLSEXT)
.validator = nxt_conf_vldt_ticket_key,
#else
.validator = nxt_conf_vldt_unsupported,
.u.string = "tickets",
#endif
}, },
NXT_CONF_VLDT_END NXT_CONF_VLDT_END
@@ -469,6 +486,62 @@ nxt_conf_vldt_tls_timeout(nxt_conf_validation_t *vldt, nxt_conf_value_t *value,
#endif #endif
#if (NXT_HAVE_OPENSSL_TLSEXT)
static nxt_int_t
nxt_conf_vldt_ticket_key(nxt_conf_validation_t *vldt, nxt_conf_value_t *value,
void *data)
{
if (nxt_conf_type(value) == NXT_CONF_BOOLEAN) {
return NXT_OK;
}
if (nxt_conf_type(value) == NXT_CONF_ARRAY) {
return nxt_conf_vldt_array_iterator(vldt, value,
&nxt_conf_vldt_ticket_key_element);
}
/* NXT_CONF_STRING */
return nxt_conf_vldt_ticket_key_element(vldt, value);
}
static nxt_int_t
nxt_conf_vldt_ticket_key_element(nxt_conf_validation_t *vldt,
nxt_conf_value_t *value)
{
nxt_str_t key;
nxt_int_t ret;
if (nxt_conf_type(value) != NXT_CONF_STRING) {
return nxt_conf_vldt_error(vldt, "The \"key\" array must "
"contain only string values.");
}
nxt_conf_get_string(value, &key);
ret = nxt_openssl_base64_decode(NULL, 0, key.start, key.length);
if (nxt_slow_path(ret == NXT_ERROR)) {
return NXT_ERROR;
}
if (ret == NXT_DECLINED) {
return nxt_conf_vldt_error(vldt, "Invalid Base64 format for the ticket "
"key \"%V\".", &key);
}
if (ret != 48 && ret != 80) {
return nxt_conf_vldt_error(vldt, "Invalid length %d of the ticket "
"key \"%V\". Must be 48 or 80 bytes.",
ret, &key);
}
return NXT_OK;
}
#endif
static nxt_conf_vldt_object_t nxt_conf_vldt_route_members[] = { static nxt_conf_vldt_object_t nxt_conf_vldt_route_members[] = {
{ {

View File

@@ -11,6 +11,8 @@
#include <openssl/err.h> #include <openssl/err.h>
#include <openssl/rand.h> #include <openssl/rand.h>
#include <openssl/x509v3.h> #include <openssl/x509v3.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
typedef struct { typedef struct {
@@ -50,6 +52,12 @@ static nxt_int_t nxt_openssl_chain_file(nxt_task_t *task, SSL_CTX *ctx,
static nxt_int_t nxt_ssl_conf_commands(nxt_task_t *task, SSL_CTX *ctx, static nxt_int_t nxt_ssl_conf_commands(nxt_task_t *task, SSL_CTX *ctx,
nxt_conf_value_t *value, nxt_mp_t *mp); nxt_conf_value_t *value, nxt_mp_t *mp);
#endif #endif
#if (NXT_HAVE_OPENSSL_TLSEXT)
static nxt_int_t nxt_tls_ticket_keys(nxt_task_t *task, SSL_CTX *ctx,
nxt_tls_init_t *tls_init, nxt_mp_t *mp);
static int nxt_tls_ticket_key_callback(SSL *s, unsigned char *name,
unsigned char *iv, EVP_CIPHER_CTX *ectx,HMAC_CTX *hctx, int enc);
#endif
static void nxt_ssl_session_cache(SSL_CTX *ctx, size_t cache_size, static void nxt_ssl_session_cache(SSL_CTX *ctx, size_t cache_size,
time_t timeout); time_t timeout);
static nxt_uint_t nxt_openssl_cert_get_names(nxt_task_t *task, X509 *cert, static nxt_uint_t nxt_openssl_cert_get_names(nxt_task_t *task, X509 *cert,
@@ -350,6 +358,12 @@ nxt_openssl_server_init(nxt_task_t *task, nxt_mp_t *mp,
nxt_ssl_session_cache(ctx, tls_init->cache_size, tls_init->timeout); nxt_ssl_session_cache(ctx, tls_init->cache_size, tls_init->timeout);
#if (NXT_HAVE_OPENSSL_TLSEXT)
if (nxt_tls_ticket_keys(task, ctx, tls_init, mp) != NXT_OK) {
goto fail;
}
#endif
SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
if (conf->ca_certificate != NULL) { if (conf->ca_certificate != NULL) {
@@ -587,6 +601,241 @@ fail:
#endif #endif
#if (NXT_HAVE_OPENSSL_TLSEXT)
static nxt_int_t
nxt_tls_ticket_keys(nxt_task_t *task, SSL_CTX *ctx, nxt_tls_init_t *tls_init,
nxt_mp_t *mp)
{
uint32_t i;
nxt_int_t ret;
nxt_str_t value;
nxt_uint_t count;
nxt_conf_value_t *member, *tickets_conf;
nxt_tls_ticket_t *ticket;
nxt_tls_tickets_t *tickets;
u_char buf[80];
tickets_conf = tls_init->tickets_conf;
if (tickets_conf == NULL) {
goto no_ticket;
}
if (nxt_conf_type(tickets_conf) == NXT_CONF_BOOLEAN) {
if (nxt_conf_get_boolean(tickets_conf) == 0) {
goto no_ticket;
}
return NXT_OK;
}
if (nxt_conf_type(tickets_conf) == NXT_CONF_ARRAY) {
count = nxt_conf_array_elements_count(tickets_conf);
if (count == 0) {
goto no_ticket;
}
} else {
/* nxt_conf_type(tickets_conf) == NXT_CONF_STRING */
count = 1;
}
#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB
tickets = nxt_mp_get(mp, sizeof(nxt_tls_tickets_t)
+ count * sizeof(nxt_tls_ticket_t));
if (nxt_slow_path(tickets == NULL)) {
return NXT_ERROR;
}
tickets->count = count;
tls_init->conf->tickets = tickets;
i = 0;
do {
ticket = &tickets->tickets[i];
i++;
if (nxt_conf_type(tickets_conf) == NXT_CONF_ARRAY) {
member = nxt_conf_get_array_element(tickets_conf, count - i);
if (member == NULL) {
break;
}
} else {
/* nxt_conf_type(tickets_conf) == NXT_CONF_STRING */
member = tickets_conf;
}
nxt_conf_get_string(member, &value);
ret = nxt_openssl_base64_decode(buf, 80, value.start, value.length);
if (nxt_slow_path(ret == NXT_ERROR)) {
return NXT_ERROR;
}
if (ret == 48) {
ticket->aes128 = 1;
nxt_memcpy(ticket->aes_key, buf + 16, 16);
nxt_memcpy(ticket->hmac_key, buf + 32, 16);
} else {
ticket->aes128 = 0;
nxt_memcpy(ticket->hmac_key, buf + 16, 32);
nxt_memcpy(ticket->aes_key, buf + 48, 32);
}
nxt_memcpy(ticket->name, buf, 16);
} while (i < count);
if (SSL_CTX_set_tlsext_ticket_key_cb(ctx, nxt_tls_ticket_key_callback)
== 0)
{
nxt_openssl_log_error(task, NXT_LOG_ALERT,
"Unit 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 NXT_ERROR;
}
return NXT_OK;
#else
nxt_alert(task, "Setting custom session ticket keys is not supported with "
"this version of OpenSSL library");
return NXT_ERROR;
#endif
no_ticket:
SSL_CTX_set_options(ctx, SSL_OP_NO_TICKET);
return NXT_OK;
}
#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB
static int
nxt_tls_ticket_key_callback(SSL *s, unsigned char *name, unsigned char *iv,
EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx, int enc)
{
size_t size;
nxt_uint_t i;
nxt_conn_t *c;
const EVP_MD *digest;
const EVP_CIPHER *cipher;
nxt_tls_ticket_t *ticket;
nxt_openssl_conn_t *tls;
c = SSL_get_ex_data(s, nxt_openssl_connection_index);
if (nxt_slow_path(c == NULL)) {
nxt_thread_log_alert("SSL_get_ex_data() failed");
return -1;
}
tls = c->u.tls;
ticket = tls->conf->tickets->tickets;
#ifdef OPENSSL_NO_SHA256
digest = EVP_sha1();
#else
digest = EVP_sha256();
#endif
if (enc == 1) {
/* encrypt session ticket */
nxt_debug(c->socket.task, "TLS session ticket encrypt");
if (ticket[0].aes128 == 1) {
cipher = EVP_aes_128_cbc();
size = 16;
} else {
cipher = EVP_aes_256_cbc();
size = 32;
}
if (RAND_bytes(iv, EVP_CIPHER_iv_length(cipher)) != 1) {
nxt_openssl_log_error(c->socket.task, NXT_LOG_ALERT,
"RAND_bytes() failed");
return -1;
}
if (EVP_EncryptInit_ex(ectx, cipher, NULL, ticket[0].aes_key, iv)
!= 1)
{
nxt_openssl_log_error(c->socket.task, NXT_LOG_ALERT,
"EVP_EncryptInit_ex() failed");
return -1;
}
if (HMAC_Init_ex(hctx, ticket[0].hmac_key, size, digest, NULL) != 1) {
nxt_openssl_log_error(c->socket.task, NXT_LOG_ALERT,
"HMAC_Init_ex() failed");
return -1;
}
nxt_memcpy(name, ticket[0].name, 16);
return 1;
} else {
/* decrypt session ticket */
for (i = 0; i < tls->conf->tickets->count; i++) {
if (nxt_memcmp(name, ticket[i].name, 16) == 0) {
goto found;
}
}
nxt_debug(c->socket.task, "TLS session ticket decrypt, key not found");
return 0;
found:
nxt_debug(c->socket.task,
"TLS session ticket decrypt, key number: \"%d\"", i);
if (ticket[i].aes128 == 1) {
cipher = EVP_aes_128_cbc();
size = 16;
} else {
cipher = EVP_aes_256_cbc();
size = 32;
}
if (EVP_DecryptInit_ex(ectx, cipher, NULL, ticket[i].aes_key, iv) != 1) {
nxt_openssl_log_error(c->socket.task, NXT_LOG_ALERT,
"EVP_DecryptInit_ex() failed");
return -1;
}
if (HMAC_Init_ex(hctx, ticket[i].hmac_key, size, digest, NULL) != 1) {
nxt_openssl_log_error(c->socket.task, NXT_LOG_ALERT,
"HMAC_Init_ex() failed");
return -1;
}
return (i == 0) ? 1 : 2 /* renew */;
}
}
#endif /* SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB */
#endif /* NXT_HAVE_OPENSSL_TLSEXT */
static void static void
nxt_ssl_session_cache(SSL_CTX *ctx, size_t cache_size, time_t timeout) nxt_ssl_session_cache(SSL_CTX *ctx, size_t cache_size, time_t timeout)
@@ -904,6 +1153,11 @@ nxt_openssl_server_free(nxt_task_t *task, nxt_tls_conf_t *conf)
bundle = bundle->next; bundle = bundle->next;
} while (bundle != NULL); } while (bundle != NULL);
if (conf->tickets) {
nxt_memzero(conf->tickets->tickets,
conf->tickets->count * sizeof(nxt_tls_ticket_t));
}
#if (OPENSSL_VERSION_NUMBER >= 0x1010100fL \ #if (OPENSSL_VERSION_NUMBER >= 0x1010100fL \
&& OPENSSL_VERSION_NUMBER < 0x1010101fL) && OPENSSL_VERSION_NUMBER < 0x1010101fL)
RAND_keep_random_devices_open(0); RAND_keep_random_devices_open(0);
@@ -1565,3 +1819,70 @@ nxt_openssl_copy_error(u_char *p, u_char *end)
return p; return p;
} }
nxt_int_t
nxt_openssl_base64_decode(u_char *d, size_t dlen, const u_char *s, size_t slen)
{
BIO *bio, *b64;
nxt_int_t count, ret;
u_char buf[128];
b64 = BIO_new(BIO_f_base64());
if (nxt_slow_path(b64 == NULL)) {
goto error;
}
bio = BIO_new_mem_buf(s, slen);
if (nxt_slow_path(bio == NULL)) {
goto error;
}
bio = BIO_push(b64, bio);
BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL);
count = 0;
if (d == NULL) {
for ( ;; ) {
ret = BIO_read(bio, buf, 128);
if (ret < 0) {
goto invalid;
}
count += ret;
if (ret != 128) {
break;
}
}
} else {
count = BIO_read(bio, d, dlen);
if (count < 0) {
goto invalid;
}
}
BIO_free_all(bio);
return count;
error:
BIO_vfree(b64);
ERR_clear_error();
return NXT_ERROR;
invalid:
BIO_free_all(bio);
ERR_clear_error();
return NXT_DECLINED;
}

View File

@@ -1470,6 +1470,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
static nxt_str_t conf_commands_path = nxt_string("/tls/conf_commands"); static nxt_str_t conf_commands_path = nxt_string("/tls/conf_commands");
static nxt_str_t conf_cache_path = nxt_string("/tls/session/cache_size"); static nxt_str_t conf_cache_path = nxt_string("/tls/session/cache_size");
static nxt_str_t conf_timeout_path = nxt_string("/tls/session/timeout"); static nxt_str_t conf_timeout_path = nxt_string("/tls/session/timeout");
static nxt_str_t conf_tickets = nxt_string("/tls/session/tickets");
#endif #endif
static nxt_str_t static_path = nxt_string("/settings/http/static"); static nxt_str_t static_path = nxt_string("/settings/http/static");
static nxt_str_t websocket_path = nxt_string("/settings/http/websocket"); static nxt_str_t websocket_path = nxt_string("/settings/http/websocket");
@@ -1877,6 +1878,9 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf,
tls_init->conf_cmds = nxt_conf_get_path(listener, tls_init->conf_cmds = nxt_conf_get_path(listener,
&conf_commands_path); &conf_commands_path);
tls_init->tickets_conf = nxt_conf_get_path(listener,
&conf_tickets);
if (nxt_conf_type(certificate) == NXT_CONF_ARRAY) { if (nxt_conf_type(certificate) == NXT_CONF_ARRAY) {
n = nxt_conf_array_elements_count(certificate); n = nxt_conf_array_elements_count(certificate);

View File

@@ -29,6 +29,8 @@
typedef struct nxt_tls_conf_s nxt_tls_conf_t; typedef struct nxt_tls_conf_s nxt_tls_conf_t;
typedef struct nxt_tls_bundle_conf_s nxt_tls_bundle_conf_t; typedef struct nxt_tls_bundle_conf_s nxt_tls_bundle_conf_t;
typedef struct nxt_tls_init_s nxt_tls_init_t; typedef struct nxt_tls_init_s nxt_tls_init_t;
typedef struct nxt_tls_ticket_s nxt_tls_ticket_t;
typedef struct nxt_tls_tickets_s nxt_tls_tickets_t;
typedef struct { typedef struct {
nxt_int_t (*library_init)(nxt_task_t *task); nxt_int_t (*library_init)(nxt_task_t *task);
@@ -63,6 +65,8 @@ struct nxt_tls_conf_s {
nxt_tls_bundle_conf_t *bundle; nxt_tls_bundle_conf_t *bundle;
nxt_lvlhsh_t bundle_hash; nxt_lvlhsh_t bundle_hash;
nxt_tls_tickets_t *tickets;
void (*conn_init)(nxt_task_t *task, void (*conn_init)(nxt_task_t *task,
nxt_tls_conf_t *conf, nxt_conn_t *c); nxt_tls_conf_t *conf, nxt_conn_t *c);
@@ -82,17 +86,34 @@ struct nxt_tls_init_s {
size_t cache_size; size_t cache_size;
nxt_time_t timeout; nxt_time_t timeout;
nxt_conf_value_t *conf_cmds; nxt_conf_value_t *conf_cmds;
nxt_conf_value_t *tickets_conf;
nxt_tls_conf_t *conf; nxt_tls_conf_t *conf;
}; };
struct nxt_tls_ticket_s {
uint8_t aes128;
u_char name[16];
u_char hmac_key[32];
u_char aes_key[32];
};
struct nxt_tls_tickets_s {
nxt_uint_t count;
nxt_tls_ticket_t tickets[];
};
#if (NXT_HAVE_OPENSSL) #if (NXT_HAVE_OPENSSL)
extern const nxt_tls_lib_t nxt_openssl_lib; extern const nxt_tls_lib_t nxt_openssl_lib;
void nxt_cdecl nxt_openssl_log_error(nxt_task_t *task, nxt_uint_t level, void nxt_cdecl nxt_openssl_log_error(nxt_task_t *task, nxt_uint_t level,
const char *fmt, ...); const char *fmt, ...);
u_char *nxt_openssl_copy_error(u_char *p, u_char *end); u_char *nxt_openssl_copy_error(u_char *p, u_char *end);
nxt_int_t nxt_openssl_base64_decode(u_char *d, size_t dlen, const u_char *s,
size_t slen);
#endif #endif
#if (NXT_HAVE_GNUTLS) #if (NXT_HAVE_GNUTLS)