Skip to content

Commit

Permalink
WIP: O-RTT securing
Browse files Browse the repository at this point in the history
  • Loading branch information
haproxyFred committed Aug 29, 2024
1 parent e481240 commit a50775d
Show file tree
Hide file tree
Showing 15 changed files with 458 additions and 27 deletions.
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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=),)
Expand Down
2 changes: 2 additions & 0 deletions include/haproxy/quic_conn-t.h
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,8 @@ struct quic_conn_closed {
#define QUIC_FL_CONN_IPKTNS_DCD (1U << 15) /* Initial packet number space discarded */
#define QUIC_FL_CONN_HPKTNS_DCD (1U << 16) /* Handshake packet number space discarded */
#define QUIC_FL_CONN_PEER_VALIDATED_ADDR (1U << 17) /* Peer address is considered as validated for this connection. */
#define QUIC_FL_CONN_NO_TOKEN_RCVD (1U << 18) /* Client dit not send any token */
#define QUIC_FL_CONN_SEND_RETRY (1U << 19) /* A send retry packet must be sent */
/* gap here */
#define QUIC_FL_CONN_TO_KILL (1U << 24) /* Unusable connection, to be killed */
#define QUIC_FL_CONN_TX_TP_RECEIVED (1U << 25) /* Peer transport parameters have been received (used for the transmitting part) */
Expand Down
3 changes: 2 additions & 1 deletion include/haproxy/quic_frame-t.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include <haproxy/buf-t.h>
#include <haproxy/list.h>
#include <haproxy/quic_stream-t.h>
#include <haproxy/quic_token.h>

extern struct pool_head *pool_head_quic_frame;
extern struct pool_head *pool_head_qf_crypto;
Expand Down Expand Up @@ -154,7 +155,7 @@ struct qf_crypto {

struct qf_new_token {
uint64_t len;
const unsigned char *data;
unsigned char data[QUIC_TOKEN_LEN];
};

struct qf_stream {
Expand Down
2 changes: 1 addition & 1 deletion include/haproxy/quic_frame.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ static inline size_t qc_frm_len(struct quic_frame *frm)
}
case QUIC_FT_NEW_TOKEN: {
struct qf_new_token *f = &frm->new_token;
len += 1 + quic_int_getsize(f->len) + f->len;
len += sizeof(f->data);
break;
}
case QUIC_FT_STREAM_8 ... QUIC_FT_STREAM_F: {
Expand Down
9 changes: 9 additions & 0 deletions include/haproxy/quic_ssl.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,14 @@ static inline void qc_free_ssl_sock_ctx(struct ssl_sock_ctx **ctx)
*ctx = NULL;
}

static inline int qc_ssl_eary_data_accepted(const SSL *ssl)
{
#if defined(OPENSSL_IS_AWSLC)
return SSL_early_data_accepted(ssl);
#else
return SSL_get_early_data_status(ssl) == SSL_EARLY_DATA_ACCEPTED;
#endif
}

#endif /* USE_QUIC */
#endif /* _HAPROXY_QUIC_SSL_H */
32 changes: 32 additions & 0 deletions include/haproxy/quic_tls.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,12 @@ int quic_tls_derive_retry_token_secret(const EVP_MD *md,
const unsigned char *salt, size_t saltlen,
const unsigned char *secret, size_t secretlen);

int quic_tls_derive_token_secret(const EVP_MD *md,
unsigned char *key, size_t keylen,
unsigned char *iv, size_t ivlen,
const unsigned char *salt, size_t saltlen,
const unsigned char *secret, size_t secretlen);

int quic_hkdf_expand(const EVP_MD *md,
unsigned char *buf, size_t buflen,
const unsigned char *key, size_t keylen,
Expand Down Expand Up @@ -469,6 +475,32 @@ static inline char quic_packet_type_enc_level_char(int packet_type)
}
}

#if defined(OPENSSL_IS_AWSLC)
static inline const char *quic_ssl_early_data_status_str(const SSL *ssl)
{
if (SSL_early_data_accepted(ssl))
return "ACCEPTED";
else
return "UNKNOWN";
}
#else
static inline const char *quic_ssl_early_data_status_str(const SSL *ssl)
{
int early_data_status = SSL_get_early_data_status(ssl);

switch (early_data_status) {
case SSL_EARLY_DATA_ACCEPTED:
return "ACCEPTED";
case SSL_EARLY_DATA_REJECTED:
return "REJECTED";
case SSL_EARLY_DATA_NOT_SENT:
return "NOT_SENT";
default:
return "UNKNOWN";
}
}
#endif

/* Initialize a QUIC packet number space.
* Never fails.
*/
Expand Down
49 changes: 49 additions & 0 deletions include/haproxy/quic_token.h
Original file line number Diff line number Diff line change
@@ -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. O-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 */

46 changes: 43 additions & 3 deletions src/quic_conn.c
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
#include <haproxy/quic_sock.h>
#include <haproxy/quic_stats.h>
#include <haproxy/quic_stream.h>
#include <haproxy/quic_token.h>
#include <haproxy/quic_tp.h>
#include <haproxy/quic_trace.h>
#include <haproxy/quic_tx.h>
Expand Down Expand Up @@ -479,6 +480,20 @@ int quic_build_post_handshake_frames(struct quic_conn *qc)
}

LIST_APPEND(&frm_list, &frm->list);

frm = qc_frm_alloc(QUIC_FT_NEW_TOKEN);
if (!frm) {
TRACE_ERROR("frame allocation error", QUIC_EV_CONN_IO_CB, qc);
goto leave;
}

if (!quic_generate_token(frm->new_token.data, sizeof(frm->new_token.data), &qc->peer_addr)) {
TRACE_ERROR("token generation failed", QUIC_EV_CONN_IO_CB, qc);
goto leave;
}

frm->new_token.len = sizeof(frm->new_token.data);
LIST_APPEND(&frm_list, &frm->list);
}

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

if (qc->flags & QUIC_FL_CONN_TO_KILL) {
TRACE_DEVEL("connection to be killed", QUIC_EV_CONN_PHPKTS, qc);
goto out;
}

/* TASK_HEAVY is set when received CRYPTO data have to be handled. */
if (HA_ATOMIC_LOAD(&tl->state) & TASK_HEAVY) {
qc_ssl_provide_all_quic_data(qc, qc->xprt_ctx);
Expand Down Expand Up @@ -862,7 +882,24 @@ struct task *quic_conn_io_cb(struct task *t, void *context, unsigned int state)
quic_nictx_free(qc);
}

if ((qc->flags & QUIC_FL_CONN_CLOSING) && qc->mux_state != QC_MUX_READY) {
if (qc->flags & QUIC_FL_CONN_SEND_RETRY) {
struct quic_counters *prx_counters;
struct proxy *prx = qc->li->bind_conf->frontend;
struct quic_rx_packet pkt = {
.scid = qc->dcid,
.dcid = qc->odcid,
};

prx_counters = EXTRA_COUNTERS_GET(prx->extra_counters_fe, &quic_stats_module);
if (send_retry(qc->li->rx.fd, &qc->peer_addr, &pkt, qc->original_version)) {
TRACE_ERROR("Error during Retry generation",
QUIC_EV_CONN_LPKT, NULL, NULL, NULL, qc->original_version);
}
else
HA_ATOMIC_INC(&prx_counters->retry_sent);
}

if ((qc->flags & (QUIC_FL_CONN_CLOSING|QUIC_FL_CONN_TO_KILL)) && qc->mux_state != QC_MUX_READY) {
quic_conn_release(qc);
qc = NULL;
}
Expand Down Expand Up @@ -972,8 +1009,8 @@ struct task *qc_process_timer(struct task *task, void *ctx, unsigned int state)
* which is the first CID of this connection but a different internal representation used to build
* NEW_CONNECTION_ID frames. This is the responsibility of the caller to insert
* <conn_id> in the CIDs tree for this connection (qc->cids).
* <token> is the token found to be used for this connection with <token_len> as
* length. Endpoints addresses are specified via <local_addr> and <peer_addr>.
* <token> is a boolean denoting if a token was received for this connection.
* Endpoints addresses are specified via <local_addr> and <peer_addr>.
* Returns the connection if succeeded, NULL if not.
*/
struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
Expand Down Expand Up @@ -1080,6 +1117,9 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4,
qc->prx_counters = EXTRA_COUNTERS_GET(prx->extra_counters_fe,
&quic_stats_module);
qc->flags = QUIC_FL_CONN_LISTENER;
/* Mark this connection as having not received any token when 0-RTT is enabled. */
if (l->bind_conf->ssl_conf.early_data && !token)
qc->flags |= QUIC_FL_CONN_NO_TOKEN_RCVD;
qc->state = QUIC_HS_ST_SERVER_INITIAL;
/* Copy the client original DCID. */
qc->odcid = *dcid;
Expand Down
6 changes: 4 additions & 2 deletions src/quic_frame.c
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,7 @@ static int quic_build_new_token_frame(unsigned char **pos, const unsigned char *
return 0;

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

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

if (!quic_dec_int(&new_token_frm->len, pos, end) || end - *pos < new_token_frm->len)
if (!quic_dec_int(&new_token_frm->len, pos, end) || end - *pos < new_token_frm->len ||
sizeof(new_token_frm->data) < new_token_frm->len)
return 0;

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

return 1;
Expand Down
8 changes: 1 addition & 7 deletions src/quic_retry.c
Original file line number Diff line number Diff line change
Expand Up @@ -258,17 +258,11 @@ int quic_retry_token_check(struct quic_rx_packet *pkt,
TRACE_ENTER(QUIC_EV_CONN_LPKT, qc);

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

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;
Expand Down
62 changes: 60 additions & 2 deletions src/quic_rx.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <haproxy/quic_stream.h>
#include <haproxy/quic_ssl.h>
#include <haproxy/quic_tls.h>
#include <haproxy/quic_token.h>
#include <haproxy/quic_trace.h>
#include <haproxy/quic_tx.h>
#include <haproxy/ssl_sock.h>
Expand Down Expand Up @@ -1522,6 +1523,47 @@ static inline int quic_padding_check(const unsigned char *pos,
return pos == end;
}

/* Validate the token, retry or not (provided by NEW_TOKEN) parsed into
* <pkt> RX packet from <dgram> datagram.
* Return 1 if succeded, 0 if not.
*/
static inline int quic_token_validate(struct quic_rx_packet *pkt,
struct quic_dgram *dgram,
struct listener *l, struct quic_conn *qc,
struct quic_cid *odcid)
{
int ret = 0;

TRACE_ENTER(QUIC_EV_CONN_LPKT, qc);

switch (*pkt->token) {
case QUIC_TOKEN_FMT_RETRY:
ret = quic_retry_token_check(pkt, dgram, l, qc, odcid);
break;
case QUIC_TOKEN_FMT_NEW:
ret = quic_token_check(pkt, dgram, qc);
if (!ret) {
/* Fallback to a retry token in case of any error. */
dgram->flags |= QUIC_DGRAM_FL_SEND_RETRY;
}
break;
default:
TRACE_PROTO("Packet dropped", QUIC_EV_CONN_LPKT, qc, NULL, NULL, pkt->version);
break;
}

if (!ret)
goto err;

ret = 1;
leave:
TRACE_LEAVE(QUIC_EV_CONN_LPKT, qc);
return ret;
err:
TRACE_DEVEL("leaving in error", QUIC_EV_CONN_LPKT, qc);
goto leave;
}

/* Find the associated connection to the packet <pkt> or create a new one if
* this is an Initial packet. <dgram> is the datagram containing the packet and
* <l> is the listener instance on which it was received.
Expand Down Expand Up @@ -1581,9 +1623,25 @@ static struct quic_conn *quic_rx_pkt_retrieve_conn(struct quic_rx_packet *pkt,
}

if (pkt->token_len) {
/* Validate the token only when connection is unknown. */
if (!quic_retry_token_check(pkt, dgram, l, qc, &token_odcid))
TRACE_PROTO("Initial with token", QUIC_EV_CONN_LPKT, NULL, NULL, NULL, pkt->version);
/* Validate the token, retry or not only when connection is unknown. */
if (!quic_token_validate(pkt, dgram, l, qc, &token_odcid)) {
if (dgram->flags & QUIC_DGRAM_FL_SEND_RETRY) {
if (send_retry(l->rx.fd, &dgram->saddr, pkt, pkt->version)) {
TRACE_ERROR("Error during Retry generation",
QUIC_EV_CONN_LPKT, NULL, NULL, NULL, pkt->version);
}
else
HA_ATOMIC_INC(&prx_counters->retry_sent);

goto out;
}

goto err;
}
}
else {
TRACE_PROTO("Initial without token", QUIC_EV_CONN_LPKT, NULL, NULL, NULL, pkt->version);
}

if (!quic_init_exec_rules(l, dgram)) {
Expand Down
Loading

0 comments on commit a50775d

Please sign in to comment.