Skip to content

Commit cc69f7b

Browse files
committed
WIP: O-RTT securing
1 parent e481240 commit cc69f7b

15 files changed

+458
-27
lines changed

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -651,7 +651,8 @@ OPTIONS_OBJS += src/quic_rx.o src/mux_quic.o src/h3.o src/quic_tx.o \
651651
src/quic_cc_nocc.o src/qpack-dec.o src/quic_cc.o \
652652
src/cfgparse-quic.o src/qmux_trace.o src/qpack-enc.o \
653653
src/qpack-tbl.o src/h3_stats.o src/quic_stats.o \
654-
src/quic_fctl.o src/cbuf.o src/quic_rules.o
654+
src/quic_fctl.o src/cbuf.o src/quic_rules.o \
655+
src/quic_token.o
655656
endif
656657
657658
ifneq ($(USE_QUIC_OPENSSL_COMPAT:0=),)

include/haproxy/quic_conn-t.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,8 @@ struct quic_conn_closed {
445445
#define QUIC_FL_CONN_IPKTNS_DCD (1U << 15) /* Initial packet number space discarded */
446446
#define QUIC_FL_CONN_HPKTNS_DCD (1U << 16) /* Handshake packet number space discarded */
447447
#define QUIC_FL_CONN_PEER_VALIDATED_ADDR (1U << 17) /* Peer address is considered as validated for this connection. */
448+
#define QUIC_FL_CONN_NO_TOKEN_RCVD (1U << 18) /* Client dit not send any token */
449+
#define QUIC_FL_CONN_SEND_RETRY (1U << 19) /* A send retry packet must be sent */
448450
/* gap here */
449451
#define QUIC_FL_CONN_TO_KILL (1U << 24) /* Unusable connection, to be killed */
450452
#define QUIC_FL_CONN_TX_TP_RECEIVED (1U << 25) /* Peer transport parameters have been received (used for the transmitting part) */

include/haproxy/quic_frame-t.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include <haproxy/buf-t.h>
3434
#include <haproxy/list.h>
3535
#include <haproxy/quic_stream-t.h>
36+
#include <haproxy/quic_token.h>
3637

3738
extern struct pool_head *pool_head_quic_frame;
3839
extern struct pool_head *pool_head_qf_crypto;
@@ -154,7 +155,7 @@ struct qf_crypto {
154155

155156
struct qf_new_token {
156157
uint64_t len;
157-
const unsigned char *data;
158+
unsigned char data[QUIC_TOKEN_LEN];
158159
};
159160

160161
struct qf_stream {

include/haproxy/quic_frame.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ static inline size_t qc_frm_len(struct quic_frame *frm)
9191
}
9292
case QUIC_FT_NEW_TOKEN: {
9393
struct qf_new_token *f = &frm->new_token;
94-
len += 1 + quic_int_getsize(f->len) + f->len;
94+
len += sizeof(f->data);
9595
break;
9696
}
9797
case QUIC_FT_STREAM_8 ... QUIC_FT_STREAM_F: {

include/haproxy/quic_ssl.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,14 @@ static inline void qc_free_ssl_sock_ctx(struct ssl_sock_ctx **ctx)
4747
*ctx = NULL;
4848
}
4949

50+
static inline int qc_ssl_eary_data_accepted(const SSL *ssl)
51+
{
52+
#if defined(OPENSSL_IS_AWSLC)
53+
return SSL_early_data_accepted(ssl);
54+
#else
55+
return SSL_get_early_data_status(ssl) == SSL_EARLY_DATA_ACCEPTED;
56+
#endif
57+
}
58+
5059
#endif /* USE_QUIC */
5160
#endif /* _HAPROXY_QUIC_SSL_H */

include/haproxy/quic_tls.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ int quic_tls_derive_retry_token_secret(const EVP_MD *md,
9090
const unsigned char *salt, size_t saltlen,
9191
const unsigned char *secret, size_t secretlen);
9292

93+
int quic_tls_derive_token_secret(const EVP_MD *md,
94+
unsigned char *key, size_t keylen,
95+
unsigned char *iv, size_t ivlen,
96+
const unsigned char *salt, size_t saltlen,
97+
const unsigned char *secret, size_t secretlen);
98+
9399
int quic_hkdf_expand(const EVP_MD *md,
94100
unsigned char *buf, size_t buflen,
95101
const unsigned char *key, size_t keylen,
@@ -469,6 +475,32 @@ static inline char quic_packet_type_enc_level_char(int packet_type)
469475
}
470476
}
471477

478+
#if defined(OPENSSL_IS_AWSLC)
479+
static inline const char *quic_ssl_early_data_status_str(SSL *ssl)
480+
{
481+
if (SSL_early_data_accepted(ssl))
482+
return "ACCEPTED";
483+
else
484+
return "UNKNOWN";
485+
}
486+
#else
487+
static inline const char *quic_ssl_early_data_status_str(SSL *ssl)
488+
{
489+
int early_data_status = SSL_get_early_data_status(ssl);
490+
491+
switch (early_data_status) {
492+
case SSL_EARLY_DATA_ACCEPTED:
493+
return "ACCEPTED";
494+
case SSL_EARLY_DATA_REJECTED:
495+
return "REJECTED";
496+
case SSL_EARLY_DATA_NOT_SENT:
497+
return "NOT_SENT";
498+
default:
499+
return "UNKNOWN";
500+
}
501+
}
502+
#endif
503+
472504
/* Initialize a QUIC packet number space.
473505
* Never fails.
474506
*/

include/haproxy/quic_token.h

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* include/haproxy/quic_token.h
3+
* This file contains definition for QUIC tokens (provided by NEW_TOKEN).
4+
*
5+
* This library is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation, version 2.1
8+
* exclusively.
9+
*
10+
* This library is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this library; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18+
*/
19+
20+
#ifndef _PROTO_QUIC_TOKEN_H
21+
#define _PROTO_QUIC_TOKEN_H
22+
23+
#include <haproxy/listener-t.h>
24+
#include <haproxy/quic_rx-t.h>
25+
#include <haproxy/quic_sock-t.h>
26+
#include <haproxy/quic_tls-t.h>
27+
28+
#define QUIC_TOKEN_RAND_LEN 16
29+
/* The size of QUIC token as provided by NEW_TOKEN frame in bytes:
30+
* one byte as format identifier, sizeof(uint32_t) bytes for the timestamp,
31+
* QUIC_TLS_TAG_LEN bytes for the AEAD TAG and QUIC_TOKEN_RAND_LEN bytes
32+
* for the random data part which are used to derive a token secret in
33+
* addition to the cluster secret (global.cluster_secret).
34+
*/
35+
#define QUIC_TOKEN_LEN (1 + sizeof(uint32_t) + QUIC_TLS_TAG_LEN + QUIC_TOKEN_RAND_LEN)
36+
37+
/* RFC 9001 4.6. O-RTT
38+
* TLS13 sets a limit of seven days on the time between the original
39+
* connection and any attempt to use 0-RTT.
40+
*/
41+
#define QUIC_TOKEN_DURATION_SEC (7 * 24 * 3600) // 7 days in seconds
42+
43+
int quic_generate_token(unsigned char *token, size_t len,
44+
struct sockaddr_storage *addr);
45+
int quic_token_check(struct quic_rx_packet *pkt, struct quic_dgram *dgram,
46+
struct quic_conn *qc);
47+
48+
#endif /* _PROTO_QUIC_TOKEN_H */
49+

src/quic_conn.c

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
#include <haproxy/quic_sock.h>
5858
#include <haproxy/quic_stats.h>
5959
#include <haproxy/quic_stream.h>
60+
#include <haproxy/quic_token.h>
6061
#include <haproxy/quic_tp.h>
6162
#include <haproxy/quic_trace.h>
6263
#include <haproxy/quic_tx.h>
@@ -479,6 +480,20 @@ int quic_build_post_handshake_frames(struct quic_conn *qc)
479480
}
480481

481482
LIST_APPEND(&frm_list, &frm->list);
483+
484+
frm = qc_frm_alloc(QUIC_FT_NEW_TOKEN);
485+
if (!frm) {
486+
TRACE_ERROR("frame allocation error", QUIC_EV_CONN_IO_CB, qc);
487+
goto leave;
488+
}
489+
490+
if (!quic_generate_token(frm->new_token.data, sizeof(frm->new_token.data), &qc->peer_addr)) {
491+
TRACE_ERROR("token generation failed", QUIC_EV_CONN_IO_CB, qc);
492+
goto leave;
493+
}
494+
495+
frm->new_token.len = sizeof(frm->new_token.data);
496+
LIST_APPEND(&frm_list, &frm->list);
482497
}
483498

484499
/* Initialize <max> connection IDs minus one: there is
@@ -755,6 +770,11 @@ struct task *quic_conn_io_cb(struct task *t, void *context, unsigned int state)
755770
st = qc->state;
756771
TRACE_PROTO("connection state", QUIC_EV_CONN_IO_CB, qc, &st);
757772

773+
if (qc->flags & QUIC_FL_CONN_TO_KILL) {
774+
TRACE_DEVEL("connection to be killed", QUIC_EV_CONN_PHPKTS, qc);
775+
goto out;
776+
}
777+
758778
/* TASK_HEAVY is set when received CRYPTO data have to be handled. */
759779
if (HA_ATOMIC_LOAD(&tl->state) & TASK_HEAVY) {
760780
qc_ssl_provide_all_quic_data(qc, qc->xprt_ctx);
@@ -862,7 +882,24 @@ struct task *quic_conn_io_cb(struct task *t, void *context, unsigned int state)
862882
quic_nictx_free(qc);
863883
}
864884

865-
if ((qc->flags & QUIC_FL_CONN_CLOSING) && qc->mux_state != QC_MUX_READY) {
885+
if (qc->flags & QUIC_FL_CONN_SEND_RETRY) {
886+
struct quic_counters *prx_counters;
887+
struct proxy *prx = qc->li->bind_conf->frontend;
888+
struct quic_rx_packet pkt = {
889+
.scid = qc->dcid,
890+
.dcid = qc->odcid,
891+
};
892+
893+
prx_counters = EXTRA_COUNTERS_GET(prx->extra_counters_fe, &quic_stats_module);
894+
if (send_retry(qc->li->rx.fd, &qc->peer_addr, &pkt, qc->original_version)) {
895+
TRACE_ERROR("Error during Retry generation",
896+
QUIC_EV_CONN_LPKT, NULL, NULL, NULL, qc->original_version);
897+
}
898+
else
899+
HA_ATOMIC_INC(&prx_counters->retry_sent);
900+
}
901+
902+
if ((qc->flags & (QUIC_FL_CONN_CLOSING|QUIC_FL_CONN_TO_KILL)) && qc->mux_state != QC_MUX_READY) {
866903
quic_conn_release(qc);
867904
qc = NULL;
868905
}
@@ -972,8 +1009,8 @@ struct task *qc_process_timer(struct task *task, void *ctx, unsigned int state)
9721009
* which is the first CID of this connection but a different internal representation used to build
9731010
* NEW_CONNECTION_ID frames. This is the responsibility of the caller to insert
9741011
* <conn_id> in the CIDs tree for this connection (qc->cids).
975-
* <token> is the token found to be used for this connection with <token_len> as
976-
* length. Endpoints addresses are specified via <local_addr> and <peer_addr>.
1012+
* <token> is a boolean denoting if a token was received for this connection.
1013+
* Endpoints addresses are specified via <local_addr> and <peer_addr>.
9771014
* Returns the connection if succeeded, NULL if not.
9781015
*/
9791016
struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
@@ -1080,6 +1117,9 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
10801117
qc->prx_counters = EXTRA_COUNTERS_GET(prx->extra_counters_fe,
10811118
&quic_stats_module);
10821119
qc->flags = QUIC_FL_CONN_LISTENER;
1120+
/* Mark this connection as having not received any token when 0-RTT is enabled. */
1121+
if (l->bind_conf->ssl_conf.early_data && !token)
1122+
qc->flags |= QUIC_FL_CONN_NO_TOKEN_RCVD;
10831123
qc->state = QUIC_HS_ST_SERVER_INITIAL;
10841124
/* Copy the client original DCID. */
10851125
qc->odcid = *dcid;

src/quic_frame.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,7 @@ static int quic_build_new_token_frame(unsigned char **pos, const unsigned char *
485485
return 0;
486486

487487
memcpy(*pos, new_token_frm->data, new_token_frm->len);
488+
*pos += new_token_frm->len;
488489

489490
return 1;
490491
}
@@ -497,10 +498,11 @@ static int quic_parse_new_token_frame(struct quic_frame *frm, struct quic_conn *
497498
{
498499
struct qf_new_token *new_token_frm = &frm->new_token;
499500

500-
if (!quic_dec_int(&new_token_frm->len, pos, end) || end - *pos < new_token_frm->len)
501+
if (!quic_dec_int(&new_token_frm->len, pos, end) || end - *pos < new_token_frm->len ||
502+
sizeof(new_token_frm->data) < new_token_frm->len)
501503
return 0;
502504

503-
new_token_frm->data = *pos;
505+
memcpy(new_token_frm->data, *pos, new_token_frm->len);
504506
*pos += new_token_frm->len;
505507

506508
return 1;

src/quic_retry.c

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -258,17 +258,11 @@ int quic_retry_token_check(struct quic_rx_packet *pkt,
258258
TRACE_ENTER(QUIC_EV_CONN_LPKT, qc);
259259

260260
/* The caller must ensure this. */
261-
BUG_ON(!pkt->token_len);
261+
BUG_ON(!pkt->token_len || *pkt->token != QUIC_TOKEN_FMT_RETRY);
262262

263263
prx = l->bind_conf->frontend;
264264
prx_counters = EXTRA_COUNTERS_GET(prx->extra_counters_fe, &quic_stats_module);
265265

266-
if (*pkt->token != QUIC_TOKEN_FMT_RETRY) {
267-
/* TODO: New token check */
268-
TRACE_PROTO("Packet dropped", QUIC_EV_CONN_LPKT, qc, NULL, NULL, pkt->version);
269-
goto leave;
270-
}
271-
272266
if (sizeof buf < tokenlen) {
273267
TRACE_ERROR("too short buffer", QUIC_EV_CONN_LPKT, qc);
274268
goto err;

src/quic_rx.c

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include <haproxy/quic_stream.h>
2828
#include <haproxy/quic_ssl.h>
2929
#include <haproxy/quic_tls.h>
30+
#include <haproxy/quic_token.h>
3031
#include <haproxy/quic_trace.h>
3132
#include <haproxy/quic_tx.h>
3233
#include <haproxy/ssl_sock.h>
@@ -1522,6 +1523,47 @@ static inline int quic_padding_check(const unsigned char *pos,
15221523
return pos == end;
15231524
}
15241525

1526+
/* Validate the token, retry or not (provided by NEW_TOKEN) parsed into
1527+
* <pkt> RX packet from <dgram> datagram.
1528+
* Return 1 if succeded, 0 if not.
1529+
*/
1530+
static inline int quic_token_validate(struct quic_rx_packet *pkt,
1531+
struct quic_dgram *dgram,
1532+
struct listener *l, struct quic_conn *qc,
1533+
struct quic_cid *odcid)
1534+
{
1535+
int ret = 0;
1536+
1537+
TRACE_ENTER(QUIC_EV_CONN_LPKT, qc);
1538+
1539+
switch (*pkt->token) {
1540+
case QUIC_TOKEN_FMT_RETRY:
1541+
ret = quic_retry_token_check(pkt, dgram, l, qc, odcid);
1542+
break;
1543+
case QUIC_TOKEN_FMT_NEW:
1544+
ret = quic_token_check(pkt, dgram, qc);
1545+
if (!ret) {
1546+
/* Fallback to a retry token in case of any error. */
1547+
dgram->flags |= QUIC_DGRAM_FL_SEND_RETRY;
1548+
}
1549+
break;
1550+
default:
1551+
TRACE_PROTO("Packet dropped", QUIC_EV_CONN_LPKT, qc, NULL, NULL, pkt->version);
1552+
break;
1553+
}
1554+
1555+
if (!ret)
1556+
goto err;
1557+
1558+
ret = 1;
1559+
leave:
1560+
TRACE_LEAVE(QUIC_EV_CONN_LPKT, qc);
1561+
return ret;
1562+
err:
1563+
TRACE_DEVEL("leaving in error", QUIC_EV_CONN_LPKT, qc);
1564+
goto leave;
1565+
}
1566+
15251567
/* Find the associated connection to the packet <pkt> or create a new one if
15261568
* this is an Initial packet. <dgram> is the datagram containing the packet and
15271569
* <l> is the listener instance on which it was received.
@@ -1581,9 +1623,25 @@ static struct quic_conn *quic_rx_pkt_retrieve_conn(struct quic_rx_packet *pkt,
15811623
}
15821624

15831625
if (pkt->token_len) {
1584-
/* Validate the token only when connection is unknown. */
1585-
if (!quic_retry_token_check(pkt, dgram, l, qc, &token_odcid))
1626+
TRACE_PROTO("Initial with token", QUIC_EV_CONN_LPKT, NULL, NULL, NULL, pkt->version);
1627+
/* Validate the token, retry or not only when connection is unknown. */
1628+
if (!quic_token_validate(pkt, dgram, l, qc, &token_odcid)) {
1629+
if (dgram->flags & QUIC_DGRAM_FL_SEND_RETRY) {
1630+
if (send_retry(l->rx.fd, &dgram->saddr, pkt, pkt->version)) {
1631+
TRACE_ERROR("Error during Retry generation",
1632+
QUIC_EV_CONN_LPKT, NULL, NULL, NULL, pkt->version);
1633+
}
1634+
else
1635+
HA_ATOMIC_INC(&prx_counters->retry_sent);
1636+
1637+
goto out;
1638+
}
1639+
15861640
goto err;
1641+
}
1642+
}
1643+
else {
1644+
TRACE_PROTO("Initial without token", QUIC_EV_CONN_LPKT, NULL, NULL, NULL, pkt->version);
15871645
}
15881646

15891647
if (!quic_init_exec_rules(l, dgram)) {

0 commit comments

Comments
 (0)