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.
This commit is contained in:
Frederic Lecaille 2024-08-30 09:13:30 +02:00
parent 74caa0eece
commit f5b09dc452
3 changed files with 221 additions and 1 deletions

View File

@ -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=),)

View File

@ -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 <haproxy/listener-t.h>
#include <haproxy/quic_rx-t.h>
#include <haproxy/quic_sock-t.h>
#include <haproxy/quic_tls-t.h>
#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 */

170
src/quic_token.c Normal file
View File

@ -0,0 +1,170 @@
#include <haproxy/tools.h>
#include <haproxy/net_helper.h>
#include <haproxy/quic_tls.h>
#include <haproxy/quic_token.h>
#define TRACE_SOURCE &trace_quic
#define QUIC_TOKEN_RAND_DLEN 16
/* Build a token into <token> buffer with <len> as length and cipher
* it with AEAD as cryptographic algorithm. <addr> 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 <pkt>. <dgram> is
* the UDP datagram containing <pkt> and <l> is the listener instance on which
* it was received. <qc> 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;
}