From 47575035a057b28fcebd93ebe359e205a2fe2bf5 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Tue, 13 Apr 2021 14:41:52 +0300 Subject: [PATCH] QUIC: separate files for tokens related processing. --- auto/modules | 2 + src/event/quic/ngx_event_quic.c | 278 -------------------- src/event/quic/ngx_event_quic_connection.h | 8 +- src/event/quic/ngx_event_quic_tokens.c | 292 +++++++++++++++++++++ src/event/quic/ngx_event_quic_tokens.h | 22 ++ 5 files changed, 317 insertions(+), 285 deletions(-) create mode 100644 src/event/quic/ngx_event_quic_tokens.c create mode 100644 src/event/quic/ngx_event_quic_tokens.h diff --git a/auto/modules b/auto/modules index 925741537..fcaed67b4 100644 --- a/auto/modules +++ b/auto/modules @@ -1347,6 +1347,7 @@ if [ $USE_OPENSSL$USE_OPENSSL_QUIC = YESYES ]; then src/event/quic/ngx_event_quic_connid.h \ src/event/quic/ngx_event_quic_migration.h \ src/event/quic/ngx_event_quic_streams.h \ + src/event/quic/ngx_event_quic_tokens.h \ src/event/quic/ngx_event_quic_ack.h \ src/event/quic/ngx_event_quic_output.h" ngx_module_srcs="src/event/quic/ngx_event_quic.c \ @@ -1356,6 +1357,7 @@ if [ $USE_OPENSSL$USE_OPENSSL_QUIC = YESYES ]; then src/event/quic/ngx_event_quic_connid.c \ src/event/quic/ngx_event_quic_migration.c \ src/event/quic/ngx_event_quic_streams.c \ + src/event/quic/ngx_event_quic_tokens.c \ src/event/quic/ngx_event_quic_ack.c \ src/event/quic/ngx_event_quic_output.c" diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c index 365488701..cf9e64628 100644 --- a/src/event/quic/ngx_event_quic.c +++ b/src/event/quic/ngx_event_quic.c @@ -7,7 +7,6 @@ #include #include #include -#include #include @@ -42,10 +41,6 @@ static ngx_quic_connection_t *ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_process_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt); -static void ngx_quic_address_hash(ngx_connection_t *c, ngx_uint_t no_port, - u_char buf[20]); -static ngx_int_t ngx_quic_validate_token(ngx_connection_t *c, - u_char *key, ngx_quic_header_t *pkt); static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c); static void ngx_quic_input_handler(ngx_event_t *rev); @@ -620,32 +615,6 @@ ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, } -ngx_int_t -ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, u_char *secret, - u_char *token) -{ - ngx_str_t tmp; - - tmp.data = secret; - tmp.len = NGX_QUIC_SR_KEY_LEN; - - if (ngx_quic_derive_key(c->log, "sr_token_key", &tmp, cid, token, - NGX_QUIC_SR_TOKEN_LEN) - != NGX_OK) - { - return NGX_ERROR; - } - -#if (NGX_DEBUG) - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic stateless reset token %*xs", - (size_t) NGX_QUIC_SR_TOKEN_LEN, token); -#endif - - return NGX_OK; -} - - static ngx_int_t ngx_quic_process_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt) { @@ -690,253 +659,6 @@ ngx_quic_process_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt) } -ngx_int_t -ngx_quic_new_token(ngx_connection_t *c, u_char *key, ngx_str_t *token, - ngx_str_t *odcid, time_t exp, ngx_uint_t is_retry) -{ - int len, iv_len; - u_char *p, *iv; - EVP_CIPHER_CTX *ctx; - const EVP_CIPHER *cipher; - - u_char in[NGX_QUIC_MAX_TOKEN_SIZE]; - - ngx_quic_address_hash(c, !is_retry, in); - - p = in + 20; - - p = ngx_cpymem(p, &exp, sizeof(time_t)); - - *p++ = is_retry ? 1 : 0; - - if (odcid) { - *p++ = odcid->len; - p = ngx_cpymem(p, odcid->data, odcid->len); - - } else { - *p++ = 0; - } - - len = p - in; - - cipher = EVP_aes_256_cbc(); - iv_len = EVP_CIPHER_iv_length(cipher); - - token->len = iv_len + len + EVP_CIPHER_block_size(cipher); - token->data = ngx_pnalloc(c->pool, token->len); - if (token->data == NULL) { - return NGX_ERROR; - } - - ctx = EVP_CIPHER_CTX_new(); - if (ctx == NULL) { - return NGX_ERROR; - } - - iv = token->data; - - if (RAND_bytes(iv, iv_len) <= 0 - || !EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv)) - { - EVP_CIPHER_CTX_free(ctx); - return NGX_ERROR; - } - - token->len = iv_len; - - if (EVP_EncryptUpdate(ctx, token->data + token->len, &len, in, len) != 1) { - EVP_CIPHER_CTX_free(ctx); - return NGX_ERROR; - } - - token->len += len; - - if (EVP_EncryptFinal_ex(ctx, token->data + token->len, &len) <= 0) { - EVP_CIPHER_CTX_free(ctx); - return NGX_ERROR; - } - - token->len += len; - - EVP_CIPHER_CTX_free(ctx); - -#ifdef NGX_QUIC_DEBUG_PACKETS - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "quic new token len:%uz %xV", token->len, token); -#endif - - return NGX_OK; -} - - -static void -ngx_quic_address_hash(ngx_connection_t *c, ngx_uint_t no_port, u_char buf[20]) -{ - size_t len; - u_char *data; - ngx_sha1_t sha1; - struct sockaddr_in *sin; -#if (NGX_HAVE_INET6) - struct sockaddr_in6 *sin6; -#endif - - len = (size_t) c->socklen; - data = (u_char *) c->sockaddr; - - if (no_port) { - switch (c->sockaddr->sa_family) { - -#if (NGX_HAVE_INET6) - case AF_INET6: - sin6 = (struct sockaddr_in6 *) c->sockaddr; - - len = sizeof(struct in6_addr); - data = sin6->sin6_addr.s6_addr; - - break; -#endif - - case AF_INET: - sin = (struct sockaddr_in *) c->sockaddr; - - len = sizeof(in_addr_t); - data = (u_char *) &sin->sin_addr; - - break; - } - } - - ngx_sha1_init(&sha1); - ngx_sha1_update(&sha1, data, len); - ngx_sha1_final(buf, &sha1); -} - - -static ngx_int_t -ngx_quic_validate_token(ngx_connection_t *c, u_char *key, - ngx_quic_header_t *pkt) -{ - int len, tlen, iv_len; - u_char *iv, *p; - time_t now, exp; - size_t total; - ngx_str_t odcid; - EVP_CIPHER_CTX *ctx; - const EVP_CIPHER *cipher; - - u_char addr_hash[20]; - u_char tdec[NGX_QUIC_MAX_TOKEN_SIZE]; - - /* Retry token or NEW_TOKEN in a previous connection */ - - cipher = EVP_aes_256_cbc(); - iv = pkt->token.data; - iv_len = EVP_CIPHER_iv_length(cipher); - - /* sanity checks */ - - if (pkt->token.len < (size_t) iv_len + EVP_CIPHER_block_size(cipher)) { - goto garbage; - } - - if (pkt->token.len > (size_t) iv_len + NGX_QUIC_MAX_TOKEN_SIZE) { - goto garbage; - } - - ctx = EVP_CIPHER_CTX_new(); - if (ctx == NULL) { - return NGX_ERROR; - } - - if (!EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv)) { - EVP_CIPHER_CTX_free(ctx); - return NGX_ERROR; - } - - p = pkt->token.data + iv_len; - len = pkt->token.len - iv_len; - - if (EVP_DecryptUpdate(ctx, tdec, &len, p, len) != 1) { - EVP_CIPHER_CTX_free(ctx); - goto garbage; - } - total = len; - - if (EVP_DecryptFinal_ex(ctx, tdec + len, &tlen) <= 0) { - EVP_CIPHER_CTX_free(ctx); - goto garbage; - } - total += tlen; - - EVP_CIPHER_CTX_free(ctx); - - if (total < (20 + sizeof(time_t) + 2)) { - goto garbage; - } - - p = tdec + 20; - - ngx_memcpy(&exp, p, sizeof(time_t)); - p += sizeof(time_t); - - pkt->retried = (*p++ == 1); - - ngx_quic_address_hash(c, !pkt->retried, addr_hash); - - if (ngx_memcmp(tdec, addr_hash, 20) != 0) { - goto bad_token; - } - - odcid.len = *p++; - if (odcid.len) { - if (odcid.len > NGX_QUIC_MAX_CID_LEN) { - goto bad_token; - } - - if ((size_t)(tdec + total - p) < odcid.len) { - goto bad_token; - } - - odcid.data = p; - p += odcid.len; - } - - now = ngx_time(); - - if (now > exp) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic expired token"); - return NGX_DECLINED; - } - - if (odcid.len) { - pkt->odcid.len = odcid.len; - pkt->odcid.data = ngx_pstrdup(c->pool, &odcid); - if (pkt->odcid.data == NULL) { - return NGX_ERROR; - } - - } else { - pkt->odcid = pkt->dcid; - } - - pkt->validated = 1; - - return NGX_OK; - -garbage: - - ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic garbage token"); - - return NGX_ABORT; - -bad_token: - - ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid token"); - - return NGX_DECLINED; -} - - static ngx_int_t ngx_quic_init_connection(ngx_connection_t *c) { diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h index 6c89ab820..9e86394f7 100644 --- a/src/event/quic/ngx_event_quic_connection.h +++ b/src/event/quic/ngx_event_quic_connection.h @@ -21,13 +21,11 @@ typedef struct ngx_quic_send_ctx_s ngx_quic_send_ctx_t; #include #include #include +#include #include #include -#define NGX_QUIC_MAX_TOKEN_SIZE 64 - /* SHA-1(addr)=20 + sizeof(time_t) + retry(1) + odcid.len(1) + odcid */ - /* quic-recovery, section 6.2.2, kInitialRtt */ #define NGX_QUIC_INITIAL_RTT 333 /* ms */ @@ -206,10 +204,6 @@ struct ngx_quic_connection_s { void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc); void ngx_quic_shutdown_quic(ngx_connection_t *c); -ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, - u_char *secret, u_char *token); -ngx_int_t ngx_quic_new_token(ngx_connection_t *c, u_char *key, - ngx_str_t *token, ngx_str_t *odcid, time_t expires, ngx_uint_t is_retry); /********************************* DEBUG *************************************/ diff --git a/src/event/quic/ngx_event_quic_tokens.c b/src/event/quic/ngx_event_quic_tokens.c new file mode 100644 index 000000000..cbfc356d7 --- /dev/null +++ b/src/event/quic/ngx_event_quic_tokens.c @@ -0,0 +1,292 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include +#include +#include + + +#define NGX_QUIC_MAX_TOKEN_SIZE 64 + /* SHA-1(addr)=20 + sizeof(time_t) + retry(1) + odcid.len(1) + odcid */ + + +static void ngx_quic_address_hash(ngx_connection_t *c, ngx_uint_t no_port, + u_char buf[20]); + + +ngx_int_t +ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, u_char *secret, + u_char *token) +{ + ngx_str_t tmp; + + tmp.data = secret; + tmp.len = NGX_QUIC_SR_KEY_LEN; + + if (ngx_quic_derive_key(c->log, "sr_token_key", &tmp, cid, token, + NGX_QUIC_SR_TOKEN_LEN) + != NGX_OK) + { + return NGX_ERROR; + } + +#if (NGX_DEBUG) + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stateless reset token %*xs", + (size_t) NGX_QUIC_SR_TOKEN_LEN, token); +#endif + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_new_token(ngx_connection_t *c, u_char *key, ngx_str_t *token, + ngx_str_t *odcid, time_t exp, ngx_uint_t is_retry) +{ + int len, iv_len; + u_char *p, *iv; + EVP_CIPHER_CTX *ctx; + const EVP_CIPHER *cipher; + + u_char in[NGX_QUIC_MAX_TOKEN_SIZE]; + + ngx_quic_address_hash(c, !is_retry, in); + + p = in + 20; + + p = ngx_cpymem(p, &exp, sizeof(time_t)); + + *p++ = is_retry ? 1 : 0; + + if (odcid) { + *p++ = odcid->len; + p = ngx_cpymem(p, odcid->data, odcid->len); + + } else { + *p++ = 0; + } + + len = p - in; + + cipher = EVP_aes_256_cbc(); + iv_len = EVP_CIPHER_iv_length(cipher); + + token->len = iv_len + len + EVP_CIPHER_block_size(cipher); + token->data = ngx_pnalloc(c->pool, token->len); + if (token->data == NULL) { + return NGX_ERROR; + } + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + return NGX_ERROR; + } + + iv = token->data; + + if (RAND_bytes(iv, iv_len) <= 0 + || !EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv)) + { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + token->len = iv_len; + + if (EVP_EncryptUpdate(ctx, token->data + token->len, &len, in, len) != 1) { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + token->len += len; + + if (EVP_EncryptFinal_ex(ctx, token->data + token->len, &len) <= 0) { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + token->len += len; + + EVP_CIPHER_CTX_free(ctx); + +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic new token len:%uz %xV", token->len, token); +#endif + + return NGX_OK; +} + + +static void +ngx_quic_address_hash(ngx_connection_t *c, ngx_uint_t no_port, u_char buf[20]) +{ + size_t len; + u_char *data; + ngx_sha1_t sha1; + struct sockaddr_in *sin; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + + len = (size_t) c->socklen; + data = (u_char *) c->sockaddr; + + if (no_port) { + switch (c->sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) c->sockaddr; + + len = sizeof(struct in6_addr); + data = sin6->sin6_addr.s6_addr; + + break; +#endif + + case AF_INET: + sin = (struct sockaddr_in *) c->sockaddr; + + len = sizeof(in_addr_t); + data = (u_char *) &sin->sin_addr; + + break; + } + } + + ngx_sha1_init(&sha1); + ngx_sha1_update(&sha1, data, len); + ngx_sha1_final(buf, &sha1); +} + + +ngx_int_t +ngx_quic_validate_token(ngx_connection_t *c, u_char *key, + ngx_quic_header_t *pkt) +{ + int len, tlen, iv_len; + u_char *iv, *p; + time_t now, exp; + size_t total; + ngx_str_t odcid; + EVP_CIPHER_CTX *ctx; + const EVP_CIPHER *cipher; + + u_char addr_hash[20]; + u_char tdec[NGX_QUIC_MAX_TOKEN_SIZE]; + + /* Retry token or NEW_TOKEN in a previous connection */ + + cipher = EVP_aes_256_cbc(); + iv = pkt->token.data; + iv_len = EVP_CIPHER_iv_length(cipher); + + /* sanity checks */ + + if (pkt->token.len < (size_t) iv_len + EVP_CIPHER_block_size(cipher)) { + goto garbage; + } + + if (pkt->token.len > (size_t) iv_len + NGX_QUIC_MAX_TOKEN_SIZE) { + goto garbage; + } + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + return NGX_ERROR; + } + + if (!EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv)) { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + p = pkt->token.data + iv_len; + len = pkt->token.len - iv_len; + + if (EVP_DecryptUpdate(ctx, tdec, &len, p, len) != 1) { + EVP_CIPHER_CTX_free(ctx); + goto garbage; + } + total = len; + + if (EVP_DecryptFinal_ex(ctx, tdec + len, &tlen) <= 0) { + EVP_CIPHER_CTX_free(ctx); + goto garbage; + } + total += tlen; + + EVP_CIPHER_CTX_free(ctx); + + if (total < (20 + sizeof(time_t) + 2)) { + goto garbage; + } + + p = tdec + 20; + + ngx_memcpy(&exp, p, sizeof(time_t)); + p += sizeof(time_t); + + pkt->retried = (*p++ == 1); + + ngx_quic_address_hash(c, !pkt->retried, addr_hash); + + if (ngx_memcmp(tdec, addr_hash, 20) != 0) { + goto bad_token; + } + + odcid.len = *p++; + if (odcid.len) { + if (odcid.len > NGX_QUIC_MAX_CID_LEN) { + goto bad_token; + } + + if ((size_t)(tdec + total - p) < odcid.len) { + goto bad_token; + } + + odcid.data = p; + p += odcid.len; + } + + now = ngx_time(); + + if (now > exp) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic expired token"); + return NGX_DECLINED; + } + + if (odcid.len) { + pkt->odcid.len = odcid.len; + pkt->odcid.data = ngx_pstrdup(c->pool, &odcid); + if (pkt->odcid.data == NULL) { + return NGX_ERROR; + } + + } else { + pkt->odcid = pkt->dcid; + } + + pkt->validated = 1; + + return NGX_OK; + +garbage: + + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic garbage token"); + + return NGX_ABORT; + +bad_token: + + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid token"); + + return NGX_DECLINED; +} diff --git a/src/event/quic/ngx_event_quic_tokens.h b/src/event/quic/ngx_event_quic_tokens.h new file mode 100644 index 000000000..f3185db22 --- /dev/null +++ b/src/event/quic/ngx_event_quic_tokens.h @@ -0,0 +1,22 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_ +#define _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_ + + +#include +#include + + +ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, + u_char *secret, u_char *token); +ngx_int_t ngx_quic_new_token(ngx_connection_t *c, u_char *key, + ngx_str_t *token, ngx_str_t *odcid, time_t expires, ngx_uint_t is_retry); +ngx_int_t ngx_quic_validate_token(ngx_connection_t *c, + u_char *key, ngx_quic_header_t *pkt); + +#endif /* _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_ */