summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrey Suvorov <a.suvorov@f5.com>2021-08-17 16:52:32 -0700
committerAndrey Suvorov <a.suvorov@f5.com>2021-08-17 16:52:32 -0700
commite0aa132172f03fe7c31484ce7d301813b5dacb89 (patch)
tree86c83ac1ffc2c842d99aca6ba47c7d7ad67ca82d
parent3bd60e317c142f4596bdc0ef4747ea0f2cc03503 (diff)
downloadunit-e0aa132172f03fe7c31484ce7d301813b5dacb89.tar.gz
unit-e0aa132172f03fe7c31484ce7d301813b5dacb89.tar.bz2
Added TLS session tickets support.
-rw-r--r--auto/ssltls17
-rw-r--r--docs/changes.xml6
-rw-r--r--src/nxt_conf_validation.c73
-rw-r--r--src/nxt_openssl.c321
-rw-r--r--src/nxt_router.c4
-rw-r--r--src/nxt_tls.h21
6 files changed, 442 insertions, 0 deletions
diff --git a/auto/ssltls b/auto/ssltls
index f9363dde..d678ba74 100644
--- a/auto/ssltls
+++ b/auto/ssltls
@@ -66,6 +66,23 @@ if [ $NXT_OPENSSL = YES ]; then
return 0;
}"
. 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
diff --git a/docs/changes.xml b/docs/changes.xml
index 56dfa038..bb894e3d 100644
--- a/docs/changes.xml
+++ b/docs/changes.xml
@@ -33,6 +33,12 @@ NGINX Unit updated to 1.25.0.
<change type="feature">
<para>
+TLS session tickets.
+</para>
+</change>
+
+<change type="feature">
+<para>
TLS sessions cache.
</para>
</change>
diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c
index 0106ebc8..a53fff74 100644
--- a/src/nxt_conf_validation.c
+++ b/src/nxt_conf_validation.c
@@ -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);
static nxt_int_t nxt_conf_vldt_tls_timeout(nxt_conf_validation_t *vldt,
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
static nxt_int_t nxt_conf_vldt_action(nxt_conf_validation_t *vldt,
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"),
.type = NXT_CONF_VLDT_INTEGER,
.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
@@ -469,6 +486,62 @@ nxt_conf_vldt_tls_timeout(nxt_conf_validation_t *vldt, nxt_conf_value_t *value,
#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[] = {
{
diff --git a/src/nxt_openssl.c b/src/nxt_openssl.c
index 297e11cf..273ca7f4 100644
--- a/src/nxt_openssl.c
+++ b/src/nxt_openssl.c
@@ -11,6 +11,8 @@
#include <openssl/err.h>
#include <openssl/rand.h>
#include <openssl/x509v3.h>
+#include <openssl/bio.h>
+#include <openssl/evp.h>
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,
nxt_conf_value_t *value, nxt_mp_t *mp);
#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,
time_t timeout);
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);
+#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);
if (conf->ca_certificate != NULL) {
@@ -587,6 +601,241 @@ fail:
#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
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;
} while (bundle != NULL);
+ if (conf->tickets) {
+ nxt_memzero(conf->tickets->tickets,
+ conf->tickets->count * sizeof(nxt_tls_ticket_t));
+ }
+
#if (OPENSSL_VERSION_NUMBER >= 0x1010100fL \
&& OPENSSL_VERSION_NUMBER < 0x1010101fL)
RAND_keep_random_devices_open(0);
@@ -1565,3 +1819,70 @@ nxt_openssl_copy_error(u_char *p, u_char *end)
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;
+}
diff --git a/src/nxt_router.c b/src/nxt_router.c
index e0029d13..39d375f8 100644
--- a/src/nxt_router.c
+++ b/src/nxt_router.c
@@ -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_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_tickets = nxt_string("/tls/session/tickets");
#endif
static nxt_str_t static_path = nxt_string("/settings/http/static");
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,
&conf_commands_path);
+ tls_init->tickets_conf = nxt_conf_get_path(listener,
+ &conf_tickets);
+
if (nxt_conf_type(certificate) == NXT_CONF_ARRAY) {
n = nxt_conf_array_elements_count(certificate);
diff --git a/src/nxt_tls.h b/src/nxt_tls.h
index a92f1c21..eeb4e7ba 100644
--- a/src/nxt_tls.h
+++ b/src/nxt_tls.h
@@ -29,6 +29,8 @@
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_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 {
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_lvlhsh_t bundle_hash;
+ nxt_tls_tickets_t *tickets;
+
void (*conn_init)(nxt_task_t *task,
nxt_tls_conf_t *conf, nxt_conn_t *c);
@@ -82,17 +86,34 @@ struct nxt_tls_init_s {
size_t cache_size;
nxt_time_t timeout;
nxt_conf_value_t *conf_cmds;
+ nxt_conf_value_t *tickets_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)
extern const nxt_tls_lib_t nxt_openssl_lib;
void nxt_cdecl nxt_openssl_log_error(nxt_task_t *task, nxt_uint_t level,
const char *fmt, ...);
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
#if (NXT_HAVE_GNUTLS)