mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2025-04-29 06:18:02 +00:00
REORG: quic: Add a new module for QUIC retry
Add quic_retry.c new C file for the QUIC retry feature: quic_saddr_cpy() moved from quic_tx.c, quic_generate_retry_token_aad() moved from quic_generate_retry_token() moved from parse_retry_token() moved from quic_retry_token_check() moved from quic_retry_token_check() moved from
This commit is contained in:
parent
43fbea0f38
commit
b5970967ca
2
Makefile
2
Makefile
@ -620,7 +620,7 @@ OPTIONS_OBJS += src/quic_conn.o src/mux_quic.o src/h3.o src/xprt_quic.o \
|
|||||||
src/h3_stats.o src/qmux_http.o src/cfgparse-quic.o \
|
src/h3_stats.o src/qmux_http.o src/cfgparse-quic.o \
|
||||||
src/cbuf.o src/quic_cc.o src/quic_cc_nocc.o src/quic_ack.o \
|
src/cbuf.o src/quic_cc.o src/quic_cc_nocc.o src/quic_ack.o \
|
||||||
src/quic_trace.o src/quic_cli.o src/quic_ssl.o \
|
src/quic_trace.o src/quic_cli.o src/quic_ssl.o \
|
||||||
src/quic_rx.o src/quic_tx.o src/quic_cid.o
|
src/quic_rx.o src/quic_tx.o src/quic_cid.o src/quic_retry.o
|
||||||
endif
|
endif
|
||||||
|
|
||||||
ifneq ($(USE_QUIC_OPENSSL_COMPAT),)
|
ifneq ($(USE_QUIC_OPENSSL_COMPAT),)
|
||||||
|
@ -88,8 +88,6 @@ typedef unsigned long long ull;
|
|||||||
#define QUIC_TOKEN_FMT_RETRY 0x9c
|
#define QUIC_TOKEN_FMT_RETRY 0x9c
|
||||||
/* Format for token sent for new connections after a Retry token was sent */
|
/* Format for token sent for new connections after a Retry token was sent */
|
||||||
#define QUIC_TOKEN_FMT_NEW 0xb7
|
#define QUIC_TOKEN_FMT_NEW 0xb7
|
||||||
/* Salt length used to derive retry token secret */
|
|
||||||
#define QUIC_RETRY_TOKEN_SALTLEN 16 /* bytes */
|
|
||||||
/* Retry token duration */
|
/* Retry token duration */
|
||||||
#define QUIC_RETRY_DURATION_SEC 10
|
#define QUIC_RETRY_DURATION_SEC 10
|
||||||
/* Default Retry threshold */
|
/* Default Retry threshold */
|
||||||
|
33
include/haproxy/quic_retry.h
Normal file
33
include/haproxy/quic_retry.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#ifndef _HAPROXY_QUIC_RETRY_H
|
||||||
|
#define _HAPROXY_QUIC_RETRY_H
|
||||||
|
|
||||||
|
#ifdef USE_QUIC
|
||||||
|
#ifndef USE_OPENSSL
|
||||||
|
#error "Must define USE_OPENSSL"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
|
||||||
|
#include <haproxy/quic_cid-t.h>
|
||||||
|
#include <haproxy/quic_rx-t.h>
|
||||||
|
#include <haproxy/quic_sock-t.h>
|
||||||
|
|
||||||
|
struct listener;
|
||||||
|
|
||||||
|
int quic_generate_retry_token(unsigned char *token, size_t len,
|
||||||
|
const uint32_t version,
|
||||||
|
const struct quic_cid *odcid,
|
||||||
|
const struct quic_cid *dcid,
|
||||||
|
struct sockaddr_storage *addr);
|
||||||
|
int parse_retry_token(struct quic_conn *qc,
|
||||||
|
const unsigned char *token, const unsigned char *end,
|
||||||
|
struct quic_cid *odcid);
|
||||||
|
int quic_retry_token_check(struct quic_rx_packet *pkt,
|
||||||
|
struct quic_dgram *dgram,
|
||||||
|
struct listener *l,
|
||||||
|
struct quic_conn *qc,
|
||||||
|
struct quic_cid *odcid);
|
||||||
|
|
||||||
|
#endif /* USE_QUIC */
|
||||||
|
#endif /* _HAPROXY_QUIC_RETRY_H */
|
@ -40,10 +40,6 @@ int qc_dgrams_retransmit(struct quic_conn *qc);
|
|||||||
int qc_notify_send(struct quic_conn *qc);
|
int qc_notify_send(struct quic_conn *qc);
|
||||||
void qc_prep_hdshk_fast_retrans(struct quic_conn *qc,
|
void qc_prep_hdshk_fast_retrans(struct quic_conn *qc,
|
||||||
struct list *ifrms, struct list *hfrms);
|
struct list *ifrms, struct list *hfrms);
|
||||||
int quic_generate_retry_token_aad(unsigned char *aad,
|
|
||||||
uint32_t version,
|
|
||||||
const struct quic_cid *scid,
|
|
||||||
const struct sockaddr_storage *addr);
|
|
||||||
int send_retry(int fd, struct sockaddr_storage *addr,
|
int send_retry(int fd, struct sockaddr_storage *addr,
|
||||||
struct quic_rx_packet *pkt, const struct quic_version *qv);
|
struct quic_rx_packet *pkt, const struct quic_version *qv);
|
||||||
int send_stateless_reset(struct listener *l, struct sockaddr_storage *dstaddr,
|
int send_stateless_reset(struct listener *l, struct sockaddr_storage *dstaddr,
|
||||||
|
320
src/quic_retry.c
Normal file
320
src/quic_retry.c
Normal file
@ -0,0 +1,320 @@
|
|||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <haproxy/clock.h>
|
||||||
|
#include <haproxy/global.h>
|
||||||
|
#include <haproxy/quic_retry.h>
|
||||||
|
#include <haproxy/quic_tls.h>
|
||||||
|
#include <haproxy/quic_trace-t.h>
|
||||||
|
#include <haproxy/trace.h>
|
||||||
|
|
||||||
|
#define TRACE_SOURCE &trace_quic
|
||||||
|
|
||||||
|
/* Salt length used to derive retry token secret */
|
||||||
|
#define QUIC_RETRY_TOKEN_SALTLEN 16 /* bytes */
|
||||||
|
|
||||||
|
/* Copy <saddr> socket address data into <buf> buffer.
|
||||||
|
* This is the responsibility of the caller to check the output buffer is big
|
||||||
|
* enough to contain these socket address data.
|
||||||
|
* Return the number of bytes copied.
|
||||||
|
*/
|
||||||
|
static inline size_t quic_saddr_cpy(unsigned char *buf,
|
||||||
|
const struct sockaddr_storage *saddr)
|
||||||
|
{
|
||||||
|
void *port, *addr;
|
||||||
|
unsigned char *p;
|
||||||
|
size_t port_len, addr_len;
|
||||||
|
|
||||||
|
p = buf;
|
||||||
|
if (saddr->ss_family == AF_INET6) {
|
||||||
|
port = &((struct sockaddr_in6 *)saddr)->sin6_port;
|
||||||
|
addr = &((struct sockaddr_in6 *)saddr)->sin6_addr;
|
||||||
|
port_len = sizeof ((struct sockaddr_in6 *)saddr)->sin6_port;
|
||||||
|
addr_len = sizeof ((struct sockaddr_in6 *)saddr)->sin6_addr;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
port = &((struct sockaddr_in *)saddr)->sin_port;
|
||||||
|
addr = &((struct sockaddr_in *)saddr)->sin_addr;
|
||||||
|
port_len = sizeof ((struct sockaddr_in *)saddr)->sin_port;
|
||||||
|
addr_len = sizeof ((struct sockaddr_in *)saddr)->sin_addr;
|
||||||
|
}
|
||||||
|
memcpy(p, port, port_len);
|
||||||
|
p += port_len;
|
||||||
|
memcpy(p, addr, addr_len);
|
||||||
|
p += addr_len;
|
||||||
|
|
||||||
|
return p - buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* QUIC server only function.
|
||||||
|
* Add AAD to <add> buffer from <cid> connection ID and <addr> socket address.
|
||||||
|
* This is the responsibility of the caller to check <aad> size is big enough
|
||||||
|
* to contain these data.
|
||||||
|
* Return the number of bytes copied to <aad>.
|
||||||
|
*/
|
||||||
|
static int quic_generate_retry_token_aad(unsigned char *aad,
|
||||||
|
uint32_t version,
|
||||||
|
const struct quic_cid *cid,
|
||||||
|
const struct sockaddr_storage *addr)
|
||||||
|
{
|
||||||
|
unsigned char *p;
|
||||||
|
|
||||||
|
p = aad;
|
||||||
|
*(uint32_t *)p = htonl(version);
|
||||||
|
p += sizeof version;
|
||||||
|
p += quic_saddr_cpy(p, addr);
|
||||||
|
memcpy(p, cid->data, cid->len);
|
||||||
|
p += cid->len;
|
||||||
|
|
||||||
|
return p - aad;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* QUIC server only function.
|
||||||
|
* Generate the token to be used in Retry packets. The token is written to
|
||||||
|
* <token> with <len> as length. <odcid> is the original destination connection
|
||||||
|
* ID and <dcid> is our side destination connection ID (or client source
|
||||||
|
* connection ID).
|
||||||
|
* Returns the length of the encoded token or 0 on error.
|
||||||
|
*/
|
||||||
|
int quic_generate_retry_token(unsigned char *token, size_t len,
|
||||||
|
const uint32_t version,
|
||||||
|
const struct quic_cid *odcid,
|
||||||
|
const struct quic_cid *dcid,
|
||||||
|
struct sockaddr_storage *addr)
|
||||||
|
{
|
||||||
|
int ret = 0;
|
||||||
|
unsigned char *p;
|
||||||
|
unsigned char aad[sizeof(uint32_t) + sizeof(in_port_t) +
|
||||||
|
sizeof(struct in6_addr) + QUIC_CID_MAXLEN];
|
||||||
|
size_t aadlen;
|
||||||
|
unsigned char salt[QUIC_RETRY_TOKEN_SALTLEN];
|
||||||
|
unsigned char key[QUIC_TLS_KEY_LEN];
|
||||||
|
unsigned char iv[QUIC_TLS_IV_LEN];
|
||||||
|
const unsigned char *sec = global.cluster_secret;
|
||||||
|
size_t seclen = sizeof global.cluster_secret;
|
||||||
|
EVP_CIPHER_CTX *ctx = NULL;
|
||||||
|
const EVP_CIPHER *aead = EVP_aes_128_gcm();
|
||||||
|
uint32_t timestamp = (uint32_t)date.tv_sec;
|
||||||
|
|
||||||
|
TRACE_ENTER(QUIC_EV_CONN_TXPKT);
|
||||||
|
|
||||||
|
/* The token is made of the token format byte, the ODCID prefixed by its one byte
|
||||||
|
* length, the creation timestamp, an AEAD TAG, and finally
|
||||||
|
* the random bytes used to derive the secret to encrypt the token.
|
||||||
|
*/
|
||||||
|
if (1 + odcid->len + 1 + sizeof(timestamp) + QUIC_TLS_TAG_LEN + QUIC_RETRY_TOKEN_SALTLEN > len)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
aadlen = quic_generate_retry_token_aad(aad, version, dcid, addr);
|
||||||
|
/* TODO: RAND_bytes() should be replaced */
|
||||||
|
if (RAND_bytes(salt, sizeof salt) != 1) {
|
||||||
|
TRACE_ERROR("RAND_bytes()", QUIC_EV_CONN_TXPKT);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!quic_tls_derive_retry_token_secret(EVP_sha256(), key, sizeof key, iv, sizeof iv,
|
||||||
|
salt, sizeof salt, sec, seclen)) {
|
||||||
|
TRACE_ERROR("quic_tls_derive_retry_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Token build */
|
||||||
|
p = token;
|
||||||
|
*p++ = QUIC_TOKEN_FMT_RETRY,
|
||||||
|
*p++ = odcid->len;
|
||||||
|
memcpy(p, odcid->data, odcid->len);
|
||||||
|
p += odcid->len;
|
||||||
|
write_u32(p, htonl(timestamp));
|
||||||
|
p += sizeof timestamp;
|
||||||
|
|
||||||
|
/* Do not encrypt the QUIC_TOKEN_FMT_RETRY byte */
|
||||||
|
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, salt, sizeof salt);
|
||||||
|
p += sizeof salt;
|
||||||
|
EVP_CIPHER_CTX_free(ctx);
|
||||||
|
|
||||||
|
ret = p - token;
|
||||||
|
leave:
|
||||||
|
TRACE_LEAVE(QUIC_EV_CONN_TXPKT);
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
err:
|
||||||
|
if (ctx)
|
||||||
|
EVP_CIPHER_CTX_free(ctx);
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse the Retry token from buffer <token> with <end> a pointer to
|
||||||
|
* one byte past the end of this buffer. This will extract the ODCID
|
||||||
|
* which will be stored into <odcid>
|
||||||
|
*
|
||||||
|
* Returns 0 on success else non-zero.
|
||||||
|
*/
|
||||||
|
int parse_retry_token(struct quic_conn *qc,
|
||||||
|
const unsigned char *token, const unsigned char *end,
|
||||||
|
struct quic_cid *odcid)
|
||||||
|
{
|
||||||
|
int ret = 0;
|
||||||
|
uint64_t odcid_len;
|
||||||
|
uint32_t timestamp;
|
||||||
|
uint32_t now_sec = (uint32_t)date.tv_sec;
|
||||||
|
|
||||||
|
TRACE_ENTER(QUIC_EV_CONN_LPKT, qc);
|
||||||
|
|
||||||
|
if (!quic_dec_int(&odcid_len, &token, end)) {
|
||||||
|
TRACE_ERROR("quic_dec_int() error", QUIC_EV_CONN_LPKT, qc);
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* RFC 9000 7.2. Negotiating Connection IDs:
|
||||||
|
* When an Initial packet is sent by a client that has not previously
|
||||||
|
* received an Initial or Retry packet from the server, the client
|
||||||
|
* populates the Destination Connection ID field with an unpredictable
|
||||||
|
* value. This Destination Connection ID MUST be at least 8 bytes in length.
|
||||||
|
*/
|
||||||
|
if (odcid_len < QUIC_ODCID_MINLEN || odcid_len > QUIC_CID_MAXLEN) {
|
||||||
|
TRACE_ERROR("wrong ODCID length", QUIC_EV_CONN_LPKT, qc);
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (end - token < odcid_len + sizeof timestamp) {
|
||||||
|
TRACE_ERROR("too long ODCID length", QUIC_EV_CONN_LPKT, qc);
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
|
||||||
|
timestamp = ntohl(read_u32(token + odcid_len));
|
||||||
|
/* check if elapsed time is +/- QUIC_RETRY_DURATION_SEC
|
||||||
|
* to tolerate token generator is not perfectly time synced
|
||||||
|
*/
|
||||||
|
if ((uint32_t)(now_sec - timestamp) > QUIC_RETRY_DURATION_SEC &&
|
||||||
|
(uint32_t)(timestamp - now_sec) > QUIC_RETRY_DURATION_SEC) {
|
||||||
|
TRACE_ERROR("token has expired", QUIC_EV_CONN_LPKT, qc);
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = 1;
|
||||||
|
memcpy(odcid->data, token, odcid_len);
|
||||||
|
odcid->len = odcid_len;
|
||||||
|
leave:
|
||||||
|
TRACE_LEAVE(QUIC_EV_CONN_LPKT, qc);
|
||||||
|
return !ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* QUIC server only function.
|
||||||
|
*
|
||||||
|
* Check the validity of the Retry token from Initial packet <pkt>. <dgram> is
|
||||||
|
* the UDP datagram containing <pkt> and <l> is the listener instance on which
|
||||||
|
* it was received. If the token is valid, the ODCID of <qc> QUIC connection
|
||||||
|
* will be put into <odcid>. <qc> is used to retrieve the QUIC version needed
|
||||||
|
* to validate the token but it can be NULL : in this case the version will be
|
||||||
|
* retrieved from the packet.
|
||||||
|
*
|
||||||
|
* Return 1 if succeeded, 0 if not.
|
||||||
|
*/
|
||||||
|
|
||||||
|
int quic_retry_token_check(struct quic_rx_packet *pkt,
|
||||||
|
struct quic_dgram *dgram,
|
||||||
|
struct listener *l,
|
||||||
|
struct quic_conn *qc,
|
||||||
|
struct quic_cid *odcid)
|
||||||
|
{
|
||||||
|
struct proxy *prx;
|
||||||
|
struct quic_counters *prx_counters;
|
||||||
|
int ret = 0;
|
||||||
|
unsigned char *token = pkt->token;
|
||||||
|
const uint64_t tokenlen = pkt->token_len;
|
||||||
|
unsigned char buf[128];
|
||||||
|
unsigned char aad[sizeof(uint32_t) + QUIC_CID_MAXLEN +
|
||||||
|
sizeof(in_port_t) + sizeof(struct in6_addr)];
|
||||||
|
size_t aadlen;
|
||||||
|
const unsigned char *salt;
|
||||||
|
unsigned char key[QUIC_TLS_KEY_LEN];
|
||||||
|
unsigned char iv[QUIC_TLS_IV_LEN];
|
||||||
|
const unsigned char *sec = global.cluster_secret;
|
||||||
|
size_t seclen = sizeof global.cluster_secret;
|
||||||
|
EVP_CIPHER_CTX *ctx = NULL;
|
||||||
|
const EVP_CIPHER *aead = EVP_aes_128_gcm();
|
||||||
|
const struct quic_version *qv = qc ? qc->original_version :
|
||||||
|
pkt->version;
|
||||||
|
|
||||||
|
TRACE_ENTER(QUIC_EV_CONN_LPKT, qc);
|
||||||
|
|
||||||
|
/* The caller must ensure this. */
|
||||||
|
BUG_ON(!pkt->token_len);
|
||||||
|
|
||||||
|
prx = l->bind_conf->frontend;
|
||||||
|
prx_counters = EXTRA_COUNTERS_GET(prx->extra_counters_fe, &quic_stats_module);
|
||||||
|
|
||||||
|
if (*pkt->token != QUIC_TOKEN_FMT_RETRY) {
|
||||||
|
/* TODO: New token check */
|
||||||
|
TRACE_PROTO("Packet dropped", QUIC_EV_CONN_LPKT, qc, NULL, NULL, pkt->version);
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sizeof buf < tokenlen) {
|
||||||
|
TRACE_ERROR("too short buffer", QUIC_EV_CONN_LPKT, qc);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The token is made of the token format byte, the ODCID prefixed by its one byte
|
||||||
|
* length, the creation timestamp, an AEAD TAG, and finally
|
||||||
|
* the random bytes used to derive the secret to encrypt the token.
|
||||||
|
*/
|
||||||
|
if (tokenlen < 2 + QUIC_ODCID_MINLEN + sizeof(uint32_t) + QUIC_TLS_TAG_LEN + QUIC_RETRY_TOKEN_SALTLEN ||
|
||||||
|
tokenlen > 2 + QUIC_CID_MAXLEN + sizeof(uint32_t) + QUIC_TLS_TAG_LEN + QUIC_RETRY_TOKEN_SALTLEN) {
|
||||||
|
TRACE_ERROR("invalid token length", QUIC_EV_CONN_LPKT, qc);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
aadlen = quic_generate_retry_token_aad(aad, qv->num, &pkt->scid, &dgram->saddr);
|
||||||
|
salt = token + tokenlen - QUIC_RETRY_TOKEN_SALTLEN;
|
||||||
|
if (!quic_tls_derive_retry_token_secret(EVP_sha256(), key, sizeof key, iv, sizeof iv,
|
||||||
|
salt, QUIC_RETRY_TOKEN_SALTLEN, sec, seclen)) {
|
||||||
|
TRACE_ERROR("Could not derive retry 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_RETRY_TOKEN_SALTLEN - 1, aad, aadlen,
|
||||||
|
ctx, aead, key, iv)) {
|
||||||
|
TRACE_ERROR("Could not decrypt retry token", QUIC_EV_CONN_LPKT, qc);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parse_retry_token(qc, buf, buf + tokenlen - QUIC_RETRY_TOKEN_SALTLEN - 1, odcid)) {
|
||||||
|
TRACE_ERROR("Error during Initial token parsing", QUIC_EV_CONN_LPKT, qc);
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
EVP_CIPHER_CTX_free(ctx);
|
||||||
|
|
||||||
|
ret = 1;
|
||||||
|
HA_ATOMIC_INC(&prx_counters->retry_validated);
|
||||||
|
|
||||||
|
leave:
|
||||||
|
TRACE_LEAVE(QUIC_EV_CONN_LPKT, qc);
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
err:
|
||||||
|
HA_ATOMIC_INC(&prx_counters->retry_error);
|
||||||
|
if (ctx)
|
||||||
|
EVP_CIPHER_CTX_free(ctx);
|
||||||
|
goto leave;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
164
src/quic_rx.c
164
src/quic_rx.c
@ -20,6 +20,7 @@
|
|||||||
#include <haproxy/proto_quic.h>
|
#include <haproxy/proto_quic.h>
|
||||||
#include <haproxy/quic_ack.h>
|
#include <haproxy/quic_ack.h>
|
||||||
#include <haproxy/quic_cid.h>
|
#include <haproxy/quic_cid.h>
|
||||||
|
#include <haproxy/quic_retry.h>
|
||||||
#include <haproxy/quic_sock.h>
|
#include <haproxy/quic_sock.h>
|
||||||
#include <haproxy/quic_stream.h>
|
#include <haproxy/quic_stream.h>
|
||||||
#include <haproxy/quic_ssl.h>
|
#include <haproxy/quic_ssl.h>
|
||||||
@ -1350,62 +1351,6 @@ int qc_treat_rx_pkts(struct quic_conn *qc)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Parse the Retry token from buffer <token> with <end> a pointer to
|
|
||||||
* one byte past the end of this buffer. This will extract the ODCID
|
|
||||||
* which will be stored into <odcid>
|
|
||||||
*
|
|
||||||
* Returns 0 on success else non-zero.
|
|
||||||
*/
|
|
||||||
static int parse_retry_token(struct quic_conn *qc,
|
|
||||||
const unsigned char *token, const unsigned char *end,
|
|
||||||
struct quic_cid *odcid)
|
|
||||||
{
|
|
||||||
int ret = 0;
|
|
||||||
uint64_t odcid_len;
|
|
||||||
uint32_t timestamp;
|
|
||||||
uint32_t now_sec = (uint32_t)date.tv_sec;
|
|
||||||
|
|
||||||
TRACE_ENTER(QUIC_EV_CONN_LPKT, qc);
|
|
||||||
|
|
||||||
if (!quic_dec_int(&odcid_len, &token, end)) {
|
|
||||||
TRACE_ERROR("quic_dec_int() error", QUIC_EV_CONN_LPKT, qc);
|
|
||||||
goto leave;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* RFC 9000 7.2. Negotiating Connection IDs:
|
|
||||||
* When an Initial packet is sent by a client that has not previously
|
|
||||||
* received an Initial or Retry packet from the server, the client
|
|
||||||
* populates the Destination Connection ID field with an unpredictable
|
|
||||||
* value. This Destination Connection ID MUST be at least 8 bytes in length.
|
|
||||||
*/
|
|
||||||
if (odcid_len < QUIC_ODCID_MINLEN || odcid_len > QUIC_CID_MAXLEN) {
|
|
||||||
TRACE_ERROR("wrong ODCID length", QUIC_EV_CONN_LPKT, qc);
|
|
||||||
goto leave;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (end - token < odcid_len + sizeof timestamp) {
|
|
||||||
TRACE_ERROR("too long ODCID length", QUIC_EV_CONN_LPKT, qc);
|
|
||||||
goto leave;
|
|
||||||
}
|
|
||||||
|
|
||||||
timestamp = ntohl(read_u32(token + odcid_len));
|
|
||||||
/* check if elapsed time is +/- QUIC_RETRY_DURATION_SEC
|
|
||||||
* to tolerate token generator is not perfectly time synced
|
|
||||||
*/
|
|
||||||
if ((uint32_t)(now_sec - timestamp) > QUIC_RETRY_DURATION_SEC &&
|
|
||||||
(uint32_t)(timestamp - now_sec) > QUIC_RETRY_DURATION_SEC) {
|
|
||||||
TRACE_ERROR("token has expired", QUIC_EV_CONN_LPKT, qc);
|
|
||||||
goto leave;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = 1;
|
|
||||||
memcpy(odcid->data, token, odcid_len);
|
|
||||||
odcid->len = odcid_len;
|
|
||||||
leave:
|
|
||||||
TRACE_LEAVE(QUIC_EV_CONN_LPKT, qc);
|
|
||||||
return !ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Parse into <pkt> a long header located at <*pos> position, <end> begin a pointer to the end
|
/* Parse into <pkt> a long header located at <*pos> position, <end> begin a pointer to the end
|
||||||
* past one byte of this buffer.
|
* past one byte of this buffer.
|
||||||
*/
|
*/
|
||||||
@ -1650,113 +1595,6 @@ int qc_parse_hd_form(struct quic_rx_packet *pkt,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* QUIC server only function.
|
|
||||||
*
|
|
||||||
* Check the validity of the Retry token from Initial packet <pkt>. <dgram> is
|
|
||||||
* the UDP datagram containing <pkt> and <l> is the listener instance on which
|
|
||||||
* it was received. If the token is valid, the ODCID of <qc> QUIC connection
|
|
||||||
* will be put into <odcid>. <qc> is used to retrieve the QUIC version needed
|
|
||||||
* to validate the token but it can be NULL : in this case the version will be
|
|
||||||
* retrieved from the packet.
|
|
||||||
*
|
|
||||||
* Return 1 if succeeded, 0 if not.
|
|
||||||
*/
|
|
||||||
|
|
||||||
static int quic_retry_token_check(struct quic_rx_packet *pkt,
|
|
||||||
struct quic_dgram *dgram,
|
|
||||||
struct listener *l,
|
|
||||||
struct quic_conn *qc,
|
|
||||||
struct quic_cid *odcid)
|
|
||||||
{
|
|
||||||
struct proxy *prx;
|
|
||||||
struct quic_counters *prx_counters;
|
|
||||||
int ret = 0;
|
|
||||||
unsigned char *token = pkt->token;
|
|
||||||
const uint64_t tokenlen = pkt->token_len;
|
|
||||||
unsigned char buf[128];
|
|
||||||
unsigned char aad[sizeof(uint32_t) + QUIC_CID_MAXLEN +
|
|
||||||
sizeof(in_port_t) + sizeof(struct in6_addr)];
|
|
||||||
size_t aadlen;
|
|
||||||
const unsigned char *salt;
|
|
||||||
unsigned char key[QUIC_TLS_KEY_LEN];
|
|
||||||
unsigned char iv[QUIC_TLS_IV_LEN];
|
|
||||||
const unsigned char *sec = global.cluster_secret;
|
|
||||||
size_t seclen = sizeof global.cluster_secret;
|
|
||||||
EVP_CIPHER_CTX *ctx = NULL;
|
|
||||||
const EVP_CIPHER *aead = EVP_aes_128_gcm();
|
|
||||||
const struct quic_version *qv = qc ? qc->original_version :
|
|
||||||
pkt->version;
|
|
||||||
|
|
||||||
TRACE_ENTER(QUIC_EV_CONN_LPKT, qc);
|
|
||||||
|
|
||||||
/* The caller must ensure this. */
|
|
||||||
BUG_ON(!pkt->token_len);
|
|
||||||
|
|
||||||
prx = l->bind_conf->frontend;
|
|
||||||
prx_counters = EXTRA_COUNTERS_GET(prx->extra_counters_fe, &quic_stats_module);
|
|
||||||
|
|
||||||
if (*pkt->token != QUIC_TOKEN_FMT_RETRY) {
|
|
||||||
/* TODO: New token check */
|
|
||||||
TRACE_PROTO("Packet dropped", QUIC_EV_CONN_LPKT, qc, NULL, NULL, pkt->version);
|
|
||||||
goto leave;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sizeof buf < tokenlen) {
|
|
||||||
TRACE_ERROR("too short buffer", QUIC_EV_CONN_LPKT, qc);
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The token is made of the token format byte, the ODCID prefixed by its one byte
|
|
||||||
* length, the creation timestamp, an AEAD TAG, and finally
|
|
||||||
* the random bytes used to derive the secret to encrypt the token.
|
|
||||||
*/
|
|
||||||
if (tokenlen < 2 + QUIC_ODCID_MINLEN + sizeof(uint32_t) + QUIC_TLS_TAG_LEN + QUIC_RETRY_TOKEN_SALTLEN ||
|
|
||||||
tokenlen > 2 + QUIC_CID_MAXLEN + sizeof(uint32_t) + QUIC_TLS_TAG_LEN + QUIC_RETRY_TOKEN_SALTLEN) {
|
|
||||||
TRACE_ERROR("invalid token length", QUIC_EV_CONN_LPKT, qc);
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
|
|
||||||
aadlen = quic_generate_retry_token_aad(aad, qv->num, &pkt->scid, &dgram->saddr);
|
|
||||||
salt = token + tokenlen - QUIC_RETRY_TOKEN_SALTLEN;
|
|
||||||
if (!quic_tls_derive_retry_token_secret(EVP_sha256(), key, sizeof key, iv, sizeof iv,
|
|
||||||
salt, QUIC_RETRY_TOKEN_SALTLEN, sec, seclen)) {
|
|
||||||
TRACE_ERROR("Could not derive retry 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_RETRY_TOKEN_SALTLEN - 1, aad, aadlen,
|
|
||||||
ctx, aead, key, iv)) {
|
|
||||||
TRACE_ERROR("Could not decrypt retry token", QUIC_EV_CONN_LPKT, qc);
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parse_retry_token(qc, buf, buf + tokenlen - QUIC_RETRY_TOKEN_SALTLEN - 1, odcid)) {
|
|
||||||
TRACE_ERROR("Error during Initial token parsing", QUIC_EV_CONN_LPKT, qc);
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
|
|
||||||
EVP_CIPHER_CTX_free(ctx);
|
|
||||||
|
|
||||||
ret = 1;
|
|
||||||
HA_ATOMIC_INC(&prx_counters->retry_validated);
|
|
||||||
|
|
||||||
leave:
|
|
||||||
TRACE_LEAVE(QUIC_EV_CONN_LPKT, qc);
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
err:
|
|
||||||
HA_ATOMIC_INC(&prx_counters->retry_error);
|
|
||||||
if (ctx)
|
|
||||||
EVP_CIPHER_CTX_free(ctx);
|
|
||||||
goto leave;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check that all the bytes between <pos> included and <end> address
|
/* Check that all the bytes between <pos> included and <end> address
|
||||||
* excluded are null. This is the responsibility of the caller to
|
* excluded are null. This is the responsibility of the caller to
|
||||||
* check that there is at least one byte between <pos> end <end>.
|
* check that there is at least one byte between <pos> end <end>.
|
||||||
|
143
src/quic_tx.c
143
src/quic_tx.c
@ -18,6 +18,7 @@
|
|||||||
#include <haproxy/trace.h>
|
#include <haproxy/trace.h>
|
||||||
#include <haproxy/quic_cid.h>
|
#include <haproxy/quic_cid.h>
|
||||||
#include <haproxy/quic_conn.h>
|
#include <haproxy/quic_conn.h>
|
||||||
|
#include <haproxy/quic_retry.h>
|
||||||
#include <haproxy/quic_sock.h>
|
#include <haproxy/quic_sock.h>
|
||||||
#include <haproxy/quic_tls.h>
|
#include <haproxy/quic_tls.h>
|
||||||
#include <haproxy/quic_trace.h>
|
#include <haproxy/quic_trace.h>
|
||||||
@ -1457,148 +1458,6 @@ int send_stateless_reset(struct listener *l, struct sockaddr_storage *dstaddr,
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Copy <saddr> socket address data into <buf> buffer.
|
|
||||||
* This is the responsibility of the caller to check the output buffer is big
|
|
||||||
* enough to contain these socket address data.
|
|
||||||
* Return the number of bytes copied.
|
|
||||||
*/
|
|
||||||
static inline size_t quic_saddr_cpy(unsigned char *buf,
|
|
||||||
const struct sockaddr_storage *saddr)
|
|
||||||
{
|
|
||||||
void *port, *addr;
|
|
||||||
unsigned char *p;
|
|
||||||
size_t port_len, addr_len;
|
|
||||||
|
|
||||||
p = buf;
|
|
||||||
if (saddr->ss_family == AF_INET6) {
|
|
||||||
port = &((struct sockaddr_in6 *)saddr)->sin6_port;
|
|
||||||
addr = &((struct sockaddr_in6 *)saddr)->sin6_addr;
|
|
||||||
port_len = sizeof ((struct sockaddr_in6 *)saddr)->sin6_port;
|
|
||||||
addr_len = sizeof ((struct sockaddr_in6 *)saddr)->sin6_addr;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
port = &((struct sockaddr_in *)saddr)->sin_port;
|
|
||||||
addr = &((struct sockaddr_in *)saddr)->sin_addr;
|
|
||||||
port_len = sizeof ((struct sockaddr_in *)saddr)->sin_port;
|
|
||||||
addr_len = sizeof ((struct sockaddr_in *)saddr)->sin_addr;
|
|
||||||
}
|
|
||||||
memcpy(p, port, port_len);
|
|
||||||
p += port_len;
|
|
||||||
memcpy(p, addr, addr_len);
|
|
||||||
p += addr_len;
|
|
||||||
|
|
||||||
return p - buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* QUIC server only function.
|
|
||||||
* Add AAD to <add> buffer from <cid> connection ID and <addr> socket address.
|
|
||||||
* This is the responsibility of the caller to check <aad> size is big enough
|
|
||||||
* to contain these data.
|
|
||||||
* Return the number of bytes copied to <aad>.
|
|
||||||
*/
|
|
||||||
int quic_generate_retry_token_aad(unsigned char *aad,
|
|
||||||
uint32_t version,
|
|
||||||
const struct quic_cid *cid,
|
|
||||||
const struct sockaddr_storage *addr)
|
|
||||||
{
|
|
||||||
unsigned char *p;
|
|
||||||
|
|
||||||
p = aad;
|
|
||||||
*(uint32_t *)p = htonl(version);
|
|
||||||
p += sizeof version;
|
|
||||||
p += quic_saddr_cpy(p, addr);
|
|
||||||
memcpy(p, cid->data, cid->len);
|
|
||||||
p += cid->len;
|
|
||||||
|
|
||||||
return p - aad;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* QUIC server only function.
|
|
||||||
* Generate the token to be used in Retry packets. The token is written to
|
|
||||||
* <token> with <len> as length. <odcid> is the original destination connection
|
|
||||||
* ID and <dcid> is our side destination connection ID (or client source
|
|
||||||
* connection ID).
|
|
||||||
* Returns the length of the encoded token or 0 on error.
|
|
||||||
*/
|
|
||||||
static int quic_generate_retry_token(unsigned char *token, size_t len,
|
|
||||||
const uint32_t version,
|
|
||||||
const struct quic_cid *odcid,
|
|
||||||
const struct quic_cid *dcid,
|
|
||||||
struct sockaddr_storage *addr)
|
|
||||||
{
|
|
||||||
int ret = 0;
|
|
||||||
unsigned char *p;
|
|
||||||
unsigned char aad[sizeof(uint32_t) + sizeof(in_port_t) +
|
|
||||||
sizeof(struct in6_addr) + QUIC_CID_MAXLEN];
|
|
||||||
size_t aadlen;
|
|
||||||
unsigned char salt[QUIC_RETRY_TOKEN_SALTLEN];
|
|
||||||
unsigned char key[QUIC_TLS_KEY_LEN];
|
|
||||||
unsigned char iv[QUIC_TLS_IV_LEN];
|
|
||||||
const unsigned char *sec = global.cluster_secret;
|
|
||||||
size_t seclen = sizeof global.cluster_secret;
|
|
||||||
EVP_CIPHER_CTX *ctx = NULL;
|
|
||||||
const EVP_CIPHER *aead = EVP_aes_128_gcm();
|
|
||||||
uint32_t timestamp = (uint32_t)date.tv_sec;
|
|
||||||
|
|
||||||
TRACE_ENTER(QUIC_EV_CONN_TXPKT);
|
|
||||||
|
|
||||||
/* The token is made of the token format byte, the ODCID prefixed by its one byte
|
|
||||||
* length, the creation timestamp, an AEAD TAG, and finally
|
|
||||||
* the random bytes used to derive the secret to encrypt the token.
|
|
||||||
*/
|
|
||||||
if (1 + odcid->len + 1 + sizeof(timestamp) + QUIC_TLS_TAG_LEN + QUIC_RETRY_TOKEN_SALTLEN > len)
|
|
||||||
goto err;
|
|
||||||
|
|
||||||
aadlen = quic_generate_retry_token_aad(aad, version, dcid, addr);
|
|
||||||
/* TODO: RAND_bytes() should be replaced */
|
|
||||||
if (RAND_bytes(salt, sizeof salt) != 1) {
|
|
||||||
TRACE_ERROR("RAND_bytes()", QUIC_EV_CONN_TXPKT);
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!quic_tls_derive_retry_token_secret(EVP_sha256(), key, sizeof key, iv, sizeof iv,
|
|
||||||
salt, sizeof salt, sec, seclen)) {
|
|
||||||
TRACE_ERROR("quic_tls_derive_retry_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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Token build */
|
|
||||||
p = token;
|
|
||||||
*p++ = QUIC_TOKEN_FMT_RETRY,
|
|
||||||
*p++ = odcid->len;
|
|
||||||
memcpy(p, odcid->data, odcid->len);
|
|
||||||
p += odcid->len;
|
|
||||||
write_u32(p, htonl(timestamp));
|
|
||||||
p += sizeof timestamp;
|
|
||||||
|
|
||||||
/* Do not encrypt the QUIC_TOKEN_FMT_RETRY byte */
|
|
||||||
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, salt, sizeof salt);
|
|
||||||
p += sizeof salt;
|
|
||||||
EVP_CIPHER_CTX_free(ctx);
|
|
||||||
|
|
||||||
ret = p - token;
|
|
||||||
leave:
|
|
||||||
TRACE_LEAVE(QUIC_EV_CONN_TXPKT);
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
err:
|
|
||||||
if (ctx)
|
|
||||||
EVP_CIPHER_CTX_free(ctx);
|
|
||||||
goto leave;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Return the long packet type matching with <qv> version and <type> */
|
/* Return the long packet type matching with <qv> version and <type> */
|
||||||
static inline int quic_pkt_type(int type, uint32_t version)
|
static inline int quic_pkt_type(int type, uint32_t version)
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user