From f5b09dc452f582eb876527fd28103bc29c51afad Mon Sep 17 00:00:00 2001 From: Frederic Lecaille Date: Fri, 30 Aug 2024 09:13:30 +0200 Subject: [PATCH] MINOR: quic: Token for future connections implementation. There exist two sorts of token used by QUIC. They are both used to validate the peer address (path validation). Retry are used for the current connection the client want to open. This patch implement the other sort of tokens which after having been received from a connection, may be provided for the next connection from the same IP address to validate it (or validate the network path between the client and the server). The token generation is implemented by quic_generate_token(), and the token validation by quic_token_chek(). The same method is used as for Retry tokens to build such tokens to be reused for future connections. The format is very simple: one byte for the format identifier to distinguish these new tokens for the Retry token, followed by a 32bits timestamps. As this part is ciphered with AEAD as cryptographic algorithm, 16 bytes are needed for the AEAD tag. 16 more random bytes are added to this token and a salt to derive the AEAD secret used to cipher the token. In addition to this salt, this is the client IP address which is used also as AAD to derive the AEAD secret. So, the length of the token is fixed: 37 bytes. --- Makefile | 3 +- include/haproxy/quic_token.h | 49 ++++++++++ src/quic_token.c | 170 +++++++++++++++++++++++++++++++++++ 3 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 include/haproxy/quic_token.h create mode 100644 src/quic_token.c diff --git a/Makefile b/Makefile index ff1810a87..238a50377 100644 --- a/Makefile +++ b/Makefile @@ -651,7 +651,8 @@ OPTIONS_OBJS += src/quic_rx.o src/mux_quic.o src/h3.o src/quic_tx.o \ src/quic_cc_nocc.o src/qpack-dec.o src/quic_cc.o \ src/cfgparse-quic.o src/qmux_trace.o src/qpack-enc.o \ src/qpack-tbl.o src/h3_stats.o src/quic_stats.o \ - src/quic_fctl.o src/cbuf.o src/quic_rules.o + src/quic_fctl.o src/cbuf.o src/quic_rules.o \ + src/quic_token.o endif ifneq ($(USE_QUIC_OPENSSL_COMPAT:0=),) diff --git a/include/haproxy/quic_token.h b/include/haproxy/quic_token.h new file mode 100644 index 000000000..1cf82a2b5 --- /dev/null +++ b/include/haproxy/quic_token.h @@ -0,0 +1,49 @@ +/* + * include/haproxy/quic_token.h + * This file contains definition for QUIC tokens (provided by NEW_TOKEN). + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, version 2.1 + * exclusively. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _PROTO_QUIC_TOKEN_H +#define _PROTO_QUIC_TOKEN_H + +#include +#include +#include +#include + +#define QUIC_TOKEN_RAND_LEN 16 +/* The size of QUIC token as provided by NEW_TOKEN frame in bytes: + * one byte as format identifier, sizeof(uint32_t) bytes for the timestamp, + * QUIC_TLS_TAG_LEN bytes for the AEAD TAG and QUIC_TOKEN_RAND_LEN bytes + * for the random data part which are used to derive a token secret in + * addition to the cluster secret (global.cluster_secret). + */ +#define QUIC_TOKEN_LEN (1 + sizeof(uint32_t) + QUIC_TLS_TAG_LEN + QUIC_TOKEN_RAND_LEN) + +/* RFC 9001 4.6. 0-RTT + * TLS13 sets a limit of seven days on the time between the original + * connection and any attempt to use 0-RTT. + */ +#define QUIC_TOKEN_DURATION_SEC (7 * 24 * 3600) // 7 days in seconds + +int quic_generate_token(unsigned char *token, size_t len, + struct sockaddr_storage *addr); +int quic_token_check(struct quic_rx_packet *pkt, struct quic_dgram *dgram, + struct quic_conn *qc); + +#endif /* _PROTO_QUIC_TOKEN_H */ + diff --git a/src/quic_token.c b/src/quic_token.c new file mode 100644 index 000000000..4f33447dc --- /dev/null +++ b/src/quic_token.c @@ -0,0 +1,170 @@ +#include +#include +#include +#include + +#define TRACE_SOURCE &trace_quic + +#define QUIC_TOKEN_RAND_DLEN 16 + +/* Build a token into buffer with as length and cipher + * it with AEAD as cryptographic algorithm. are use as AAD. + * Return 1 if succeeded, 0 if not. + */ +int quic_generate_token(unsigned char *token, size_t len, + struct sockaddr_storage *addr) +{ +#ifdef QUIC_AEAD_API + const QUIC_AEAD *aead = EVP_aead_aes_128_gcm(); +#else + const QUIC_AEAD *aead = EVP_aes_128_gcm(); +#endif + int ret = 0; + unsigned char *p; + unsigned char aad[sizeof(struct in6_addr)]; + size_t aadlen; + uint32_t ts = (uint32_t)date.tv_sec; + uint64_t rand_u64; + unsigned char rand[QUIC_TOKEN_RAND_DLEN]; + unsigned char key[16]; + unsigned char iv[QUIC_TLS_IV_LEN]; + const unsigned char *sec = global.cluster_secret; + size_t seclen = sizeof(global.cluster_secret); + QUIC_AEAD_CTX *ctx = NULL; + + TRACE_ENTER(QUIC_EV_CONN_TXPKT); + + /* Generate random data to be used as salt to derive the token secret. */ + rand_u64 = ha_random64(); + write_u64(rand, rand_u64); + rand_u64 = ha_random64(); + write_u64(rand + sizeof(rand_u64), rand_u64); + + if (len < QUIC_TOKEN_LEN) { + TRACE_ERROR("too small buffer", QUIC_EV_CONN_TXPKT); + goto err; + } + + /* Generate the AAD. */ + aadlen = ipaddrcpy(aad, addr); + if (!quic_tls_derive_token_secret(EVP_sha256(), key, sizeof key, + iv, sizeof iv, rand, sizeof(rand), + sec, seclen)) { + TRACE_ERROR("quic_tls_derive_token_secret() failed", QUIC_EV_CONN_TXPKT); + goto err; + } + + if (!quic_tls_tx_ctx_init(&ctx, aead, key)) { + TRACE_ERROR("quic_tls_tx_ctx_init() failed", QUIC_EV_CONN_TXPKT); + goto err; + } + + /* Clear token build */ + p = token; + *p++ = QUIC_TOKEN_FMT_NEW; + write_u32(p, htonl(ts)); + p += sizeof(ts); + + if (!quic_tls_encrypt(token + 1, p - token - 1, aad, aadlen, ctx, aead, iv)) { + TRACE_ERROR("quic_tls_encrypt() failed", QUIC_EV_CONN_TXPKT); + goto err; + } + + p += QUIC_TLS_TAG_LEN; + memcpy(p, rand, sizeof(rand)); + p += sizeof(rand); + + ret = p - token; + leave: + if (ctx) + QUIC_AEAD_CTX_free(ctx); + TRACE_LEAVE(QUIC_EV_CONN_TXPKT); + return ret; + + err: + TRACE_DEVEL("leaving on error", QUIC_EV_CONN_TXPKT); + goto leave; +} + +/* QUIC server only function. + * + * Check the validity of the token from Initial packet . is + * the UDP datagram containing and is the listener instance on which + * it was received. is used only for debugging purposes (traces). + * + * Return 1 if succeeded, 0 if not. + */ +int quic_token_check(struct quic_rx_packet *pkt, + struct quic_dgram *dgram, + struct quic_conn *qc) +{ + int ret = 0; + unsigned char *token = pkt->token; + size_t tokenlen = pkt->token_len; + const unsigned char *rand; + unsigned char buf[128]; + unsigned char aad[sizeof(struct in6_addr)]; + size_t aadlen; + unsigned char key[16]; + unsigned char iv[QUIC_TLS_IV_LEN]; + const unsigned char *sec = global.cluster_secret; + size_t seclen = sizeof(global.cluster_secret); + uint32_t ts; + uint32_t now_sec = (uint32_t)date.tv_sec; + + QUIC_AEAD_CTX *ctx = NULL; + +#ifdef QUIC_AEAD_API + const QUIC_AEAD *aead = EVP_aead_aes_128_gcm(); +#else + const QUIC_AEAD *aead = EVP_aes_128_gcm(); +#endif + + TRACE_ENTER(QUIC_EV_CONN_LPKT, qc); + + BUG_ON(!tokenlen || *token != QUIC_TOKEN_FMT_NEW); + + if (sizeof(buf) < tokenlen) { + TRACE_ERROR("too short buffer", QUIC_EV_CONN_LPKT, qc); + goto err; + } + + /* Generate the AAD. */ + aadlen = ipaddrcpy(aad, &dgram->saddr); + rand = token + tokenlen - QUIC_TOKEN_RAND_DLEN; + if (!quic_tls_derive_token_secret(EVP_sha256(), key, sizeof key, iv, sizeof iv, + rand, QUIC_TOKEN_RAND_DLEN, sec, seclen)) { + TRACE_ERROR("Could not derive token secret", QUIC_EV_CONN_LPKT, qc); + goto err; + } + + if (!quic_tls_rx_ctx_init(&ctx, aead, key)) { + TRACE_ERROR("quic_tls_rx_ctx_init() failed", QUIC_EV_CONN_LPKT, qc); + goto err; + } + + /* The token is prefixed by a one-byte length format which is not ciphered. */ + if (!quic_tls_decrypt2(buf, token + 1, tokenlen - QUIC_TOKEN_RAND_DLEN - 1, aad, aadlen, + ctx, aead, key, iv)) { + TRACE_ERROR("Could not decrypt token", QUIC_EV_CONN_LPKT, qc); + goto err; + } + + ts = ntohl(read_u32(buf)); + if (now_sec - ts > QUIC_TOKEN_DURATION_SEC) { + TRACE_ERROR("expired token", QUIC_EV_CONN_LPKT, qc); + goto err; + } + + ret = 1; + leave: + if (ctx) + QUIC_AEAD_CTX_free(ctx); + TRACE_LEAVE(QUIC_EV_CONN_LPKT); + return ret; + + err: + TRACE_DEVEL("leaving on error", QUIC_EV_CONN_LPKT); + goto leave; +} +