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; +} +