Added TLS session tickets support.
This commit is contained in:
17
auto/ssltls
17
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
|
||||
|
||||
|
||||
|
||||
@@ -31,6 +31,12 @@ NGINX Unit updated to 1.25.0.
|
||||
date="" time=""
|
||||
packager="Andrei Belov <defan@nginx.com>">
|
||||
|
||||
<change type="feature">
|
||||
<para>
|
||||
TLS session tickets.
|
||||
</para>
|
||||
</change>
|
||||
|
||||
<change type="feature">
|
||||
<para>
|
||||
TLS sessions cache.
|
||||
|
||||
@@ -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[] = {
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user