diff --git a/include/haproxy/quic_conn-t.h b/include/haproxy/quic_conn-t.h index a832038db..0b7fac7ec 100644 --- a/include/haproxy/quic_conn-t.h +++ b/include/haproxy/quic_conn-t.h @@ -356,6 +356,10 @@ struct quic_conn { */ uint64_t hash64; + /* QUIC client only retry token received from servers RETRY packet */ + unsigned char *retry_token; + size_t retry_token_len; + /* Initial encryption level */ struct quic_enc_level *iel; /* 0-RTT encryption level */ diff --git a/include/haproxy/quic_retry.h b/include/haproxy/quic_retry.h index d31be02ee..f3ae371d3 100644 --- a/include/haproxy/quic_retry.h +++ b/include/haproxy/quic_retry.h @@ -13,6 +13,8 @@ #include #include +extern struct pool_head *pool_head_quic_retry_token; + struct listener; int quic_generate_retry_token(unsigned char *token, size_t len, @@ -28,6 +30,9 @@ int quic_retry_token_check(struct quic_rx_packet *pkt, struct listener *l, struct quic_conn *qc, struct quic_cid *odcid); +int quic_retry_packet_check(struct quic_conn *qc, struct quic_rx_packet *pkt, + const unsigned char *beg, const unsigned char *end, + const unsigned char *pos, size_t *retry_token_len); #endif /* USE_QUIC */ #endif /* _HAPROXY_QUIC_RETRY_H */ diff --git a/include/haproxy/quic_tls.h b/include/haproxy/quic_tls.h index d2e56a89a..0e053a71e 100644 --- a/include/haproxy/quic_tls.h +++ b/include/haproxy/quic_tls.h @@ -74,7 +74,8 @@ int quic_tls_decrypt(unsigned char *buf, size_t len, const unsigned char *key, const unsigned char *iv); int quic_tls_generate_retry_integrity_tag(unsigned char *odcid, unsigned char odcid_len, - unsigned char *buf, size_t len, + const unsigned char *buf, size_t len, + unsigned char *tag, const struct quic_version *qv); int quic_tls_derive_keys(const QUIC_AEAD *aead, const EVP_CIPHER *hp, @@ -536,7 +537,8 @@ static inline int quic_pktns_init(struct quic_conn *qc, struct quic_pktns **p) return 1; } -static inline void quic_pktns_tx_pkts_release(struct quic_pktns *pktns, struct quic_conn *qc) +static inline void quic_pktns_tx_pkts_release(struct quic_pktns *pktns, + struct quic_conn *qc, int resend) { struct eb64_node *node; @@ -557,7 +559,11 @@ static inline void quic_pktns_tx_pkts_release(struct quic_pktns *pktns, struct q qc_frm_unref(frm, qc); LIST_DEL_INIT(&frm->list); quic_tx_packet_refdec(frm->pkt); - qc_frm_free(qc, &frm); + if (!resend) + qc_frm_free(qc, &frm); + else + LIST_APPEND(&pktns->tx.frms, &frm->list); + } eb64_delete(&pkt->pn_node); quic_tx_packet_refdec(pkt); @@ -572,9 +578,12 @@ static inline void quic_pktns_tx_pkts_release(struct quic_pktns *pktns, struct q * connection. * Note that all the non acknowledged TX packets and their frames are freed. * Always succeeds. + * boolean must be 1 to resend the frames which are in flight. + * This is only used to resend the Initial packet frames upon a RETRY + * packet receipt (backend only option). */ static inline void quic_pktns_discard(struct quic_pktns *pktns, - struct quic_conn *qc) + struct quic_conn *qc, int resend) { TRACE_ENTER(QUIC_EV_CONN_PHPKTS, qc); @@ -590,7 +599,7 @@ static inline void quic_pktns_discard(struct quic_pktns *pktns, pktns->tx.loss_time = TICK_ETERNITY; pktns->tx.pto_probe = 0; pktns->tx.in_flight = 0; - quic_pktns_tx_pkts_release(pktns, qc); + quic_pktns_tx_pkts_release(pktns, qc, resend); TRACE_LEAVE(QUIC_EV_CONN_PHPKTS, qc); } diff --git a/src/quic_conn.c b/src/quic_conn.c index 600d2893c..904ca37f3 100644 --- a/src/quic_conn.c +++ b/src/quic_conn.c @@ -53,6 +53,7 @@ #include #include #include +#include #include #include #include @@ -841,7 +842,7 @@ struct task *quic_conn_io_cb(struct task *t, void *context, unsigned int state) if (discard_hpktns) { /* Discard the Handshake packet number space. */ TRACE_PROTO("discarding Handshake pktns", QUIC_EV_CONN_PHPKTS, qc); - quic_pktns_discard(qc->hel->pktns, qc); + quic_pktns_discard(qc->hel->pktns, qc, 0); qc_set_timer(qc); qc_el_rx_pkts_del(qc->hel); qc_release_pktns_frms(qc, qc->hel->pktns); @@ -921,7 +922,7 @@ struct task *quic_conn_io_cb(struct task *t, void *context, unsigned int state) qc->hpktns && qc->hpktns->tx.in_flight > 0) { /* Discard the Initial packet number space. */ TRACE_PROTO("discarding Initial pktns", QUIC_EV_CONN_PRSHPKT, qc); - quic_pktns_discard(qc->ipktns, qc); + quic_pktns_discard(qc->ipktns, qc, 0); qc_set_timer(qc); qc_el_rx_pkts_del(qc->iel); qc_release_pktns_frms(qc, qc->ipktns); @@ -1167,6 +1168,8 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4, #ifdef HAVE_OPENSSL_QUIC qc->prot_level = OSSL_RECORD_PROTECTION_LEVEL_NONE; #endif + qc->retry_token = NULL; + qc->retry_token_len = 0; /* Encryption levels */ qc->iel = qc->eel = qc->hel = qc->ael = NULL; LIST_INIT(&qc->qel_list); @@ -1198,8 +1201,6 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4, qc->flags = QUIC_FL_CONN_PEER_VALIDATED_ADDR; qc->state = QUIC_HS_ST_CLIENT_INITIAL; - memset(&qc->odcid, 0, sizeof qc->odcid); - qc->odcid.len = 0; /* This is the original connection ID from the peer server * point of view. */ @@ -1208,6 +1209,9 @@ struct quic_conn *qc_new_conn(const struct quic_version *qv, int ipv4, qc->dcid.len = sizeof(qc->dcid.data); + memcpy(&qc->odcid, qc->dcid.data, sizeof(qc->dcid.data)); + qc->odcid.len = qc->dcid.len; + conn_cid = new_quic_cid(qc->cids, qc, NULL, NULL); if (!conn_cid) goto err; @@ -1576,6 +1580,9 @@ int quic_conn_release(struct quic_conn *qc) pool_free(pool_head_quic_tls_secret, actx->tx.secret); } + pool_free(pool_head_quic_retry_token, qc->retry_token); + qc->retry_token = NULL; + qc->retry_token_len = 0; qc_enc_level_free(qc, &qc->iel); qc_enc_level_free(qc, &qc->eel); qc_enc_level_free(qc, &qc->hel); diff --git a/src/quic_retry.c b/src/quic_retry.c index 78ef88a76..9cdf69af5 100644 --- a/src/quic_retry.c +++ b/src/quic_retry.c @@ -1,6 +1,7 @@ #include #include +#include #include #include #include @@ -11,6 +12,9 @@ /* Salt length used to derive retry token secret */ #define QUIC_RETRY_TOKEN_SALTLEN 16 /* bytes */ +#define QUIC_RETRY_TOKEN_MAXLEN 256 /* bytes */ + +struct pool_head *pool_head_quic_retry_token; /* Copy socket address data into buffer. * This is the responsibility of the caller to check the output buffer is big @@ -319,4 +323,71 @@ int quic_retry_token_check(struct quic_rx_packet *pkt, goto leave; } +/* QUIC client only function. + * Check the integrity tag of retry packet received on connection + * with and as packet payload delimiters. is the retry token position. + * Return 1 if succeeded, 0 if not. + */ +int quic_retry_packet_check(struct quic_conn *qc, struct quic_rx_packet *pkt, + const unsigned char *beg, const unsigned char *end, + const unsigned char *pos, size_t *retry_token_len) +{ + int ret = 0; + unsigned char tag[QUIC_TLS_TAG_LEN]; + ssize_t toklen; + TRACE_ENTER(QUIC_EV_CONN_SPKT, qc); + + if (!pkt->version) { + TRACE_PROTO("retry packet without version", QUIC_EV_CONN_SPKT); + goto err; + } + + if (end - beg <= QUIC_LONG_PACKET_MINLEN + + pkt->scid.len + pkt->dcid.len + QUIC_TLS_TAG_LEN || + end - pos <= QUIC_TLS_TAG_LEN) { + TRACE_PROTO("Too short retry packet", QUIC_EV_CONN_SPKT); + goto err; + } + + if (!quic_tls_generate_retry_integrity_tag(qc->odcid.data, qc->odcid.len, + beg, end - beg - QUIC_TLS_TAG_LEN, + tag, pkt->version)) { + TRACE_PROTO("retry integrity tag faild", QUIC_EV_CONN_SPKT, qc); + goto err; + } + + if (memcmp(tag, end - QUIC_TLS_TAG_LEN, QUIC_TLS_TAG_LEN) != 0) { + TRACE_PROTO("retry integrity tag mismatch", QUIC_EV_CONN_SPKT, qc); + goto err; + } + + toklen = end - pos - QUIC_TLS_TAG_LEN; + if (toklen <= 0 || toklen > QUIC_RETRY_TOKEN_MAXLEN) { + TRACE_PROTO("wrong retry token size", QUIC_EV_CONN_SPKT, qc); + goto err; + } + + *retry_token_len = (size_t)toklen; + ret = 1; + leave: + TRACE_LEAVE(QUIC_EV_CONN_SPKT, qc); + return ret; + err: + TRACE_DEVEL("leaving on error", QUIC_EV_CONN_SPKT, qc); + goto leave; +} + +static int create_quic_retry_token_pool(void) +{ + pool_head_quic_retry_token = + create_pool("quic_retry_token", QUIC_RETRY_TOKEN_MAXLEN, MEM_F_SHARED|MEM_F_EXACT); + if (!pool_head_quic_retry_token) { + ha_warning("error on QUIC retry token buffer pool allocation.\n"); + return ERR_FATAL|ERR_ABORT; + } + + return ERR_NONE; +} + +REGISTER_POST_CHECK(create_quic_retry_token_pool); diff --git a/src/quic_rx.c b/src/quic_rx.c index f6903291e..9a61c8e9a 100644 --- a/src/quic_rx.c +++ b/src/quic_rx.c @@ -1191,7 +1191,7 @@ static int qc_parse_pkt_frms(struct quic_conn *qc, struct quic_rx_packet *pkt, if (qc->ipktns && !quic_tls_pktns_is_dcd(qc, qc->ipktns)) { /* Discard the handshake packet number space. */ TRACE_PROTO("discarding Initial pktns", QUIC_EV_CONN_PRSHPKT, qc); - quic_pktns_discard(qc->ipktns, qc); + quic_pktns_discard(qc->ipktns, qc, 0); qc_set_timer(qc); qc_el_rx_pkts_del(qc->iel); qc_release_pktns_frms(qc, qc->ipktns); @@ -1967,8 +1967,7 @@ static int quic_rx_pkt_parse(struct quic_conn *qc, struct quic_rx_packet *pkt, } /* Retry of Version Negotiation packets are only sent by servers */ - if (pkt->type == QUIC_PACKET_TYPE_RETRY || - (pkt->version && !pkt->version->num)) { + if (l && (pkt->type == QUIC_PACKET_TYPE_RETRY || (pkt->version && !pkt->version->num))) { TRACE_PROTO("Packet dropped", QUIC_EV_CONN_LPKT); goto drop; } @@ -1987,10 +1986,10 @@ static int quic_rx_pkt_parse(struct quic_conn *qc, struct quic_rx_packet *pkt, goto drop_silent; } - /* For Initial packets, and for servers (QUIC clients connections), - * there is no Initial connection IDs storage. - */ if (pkt->type == QUIC_PACKET_TYPE_INITIAL) { + /* For Initial packets, and for servers (QUIC clients connections), + * there is no Initial connection IDs storage. + */ uint64_t token_len; if (!quic_dec_int(&token_len, (const unsigned char **)&pos, end) || @@ -2011,6 +2010,47 @@ static int quic_rx_pkt_parse(struct quic_conn *qc, struct quic_rx_packet *pkt, pkt->token_len = token_len; pos += pkt->token_len; } + else if (pkt->type == QUIC_PACKET_TYPE_RETRY) { + if (!quic_retry_packet_check(qc, pkt, beg, end, pos, &qc->retry_token_len)) + /* TODO: should close the connection? */ + goto drop; + + qc->retry_token = pool_alloc(pool_head_quic_retry_token); + if (!qc->retry_token) { + TRACE_ERROR("retry token allocation failed", QUIC_EV_CONN_LPKT); + } + else { + memcpy(qc->retry_token, pos, qc->retry_token_len); + /* Save the peer Retry source connection ID into the connection ODCID. + * This is also this connection DCID (or even the first ODCID value). + * It can be erased because used only to check the retry integrity + * tag. Then, it will be matched against the retry_source_connection_id + * transport parameter which will be sent by the server. + */ + memcpy(qc->odcid.data, pkt->scid.data, pkt->scid.len); + qc->odcid.len = pkt->scid.len; + /* Copy the peer scid to be the destination of the next Initial packet */ + memcpy(qc->dcid.data, pkt->scid.data, pkt->scid.len); + qc->dcid.len = pkt->scid.len; + /* Initial packet number space discarding without releasing + * the existing frames (not already sent). + */ + quic_pktns_discard(qc->ipktns, qc, 1); + qc_set_timer(qc); + qc_el_rx_pkts_del(qc->iel); + /* Reset the DISCARDED flag for Initial packet number space */ + qc->flags &= ~QUIC_FL_CONN_IPKTNS_DCD; + /* Change the Initial TLS cryptographic context */ + quic_tls_ctx_secs_free(&qc->iel->tls_ctx); + if (!qc_new_isecs(qc, &qc->iel->tls_ctx, qc->original_version, + qc->dcid.data, qc->dcid.len, !!l)) + goto drop_silent; + + tasklet_wakeup(qc->wait_event.tasklet); + } + + goto drop_silent; + } else if (pkt->type != QUIC_PACKET_TYPE_0RTT) { if (pkt->dcid.len != QUIC_HAP_CID_LEN) { TRACE_PROTO("Packet dropped", diff --git a/src/quic_tls.c b/src/quic_tls.c index 7dd348a47..30a323d2e 100644 --- a/src/quic_tls.c +++ b/src/quic_tls.c @@ -95,7 +95,7 @@ void quic_pktns_release(struct quic_conn *qc, struct quic_pktns **pktns) if (!*pktns) return; - quic_pktns_tx_pkts_release(*pktns, qc); + quic_pktns_tx_pkts_release(*pktns, qc, 0); qc_release_pktns_frms(qc, *pktns); quic_free_arngs(qc, &(*pktns)->rx.arngs); LIST_DEL_INIT(&(*pktns)->list); @@ -1005,14 +1005,15 @@ int quic_tls_derive_token_secret(const EVP_MD *md, } /* Generate the AEAD tag for the Retry packet of bytes and - * write it to . The tag is written just after the area. It should + * write it to . The tag is written at address. It should * be at least 16 bytes longs. is the CID of the Initial packet * received which triggers the Retry. * * Returns non-zero on success else zero. */ int quic_tls_generate_retry_integrity_tag(unsigned char *odcid, unsigned char odcid_len, - unsigned char *pkt, size_t pkt_len, + const unsigned char *pkt, size_t pkt_len, + unsigned char *tag, const struct quic_version *qv) { const EVP_CIPHER *evp = EVP_aes_128_gcm(); @@ -1020,8 +1021,6 @@ int quic_tls_generate_retry_integrity_tag(unsigned char *odcid, unsigned char od /* encryption buffer - not used as only AEAD tag generation is proceed */ unsigned char *out = NULL; - /* address to store the AEAD tag */ - unsigned char *tag = pkt + pkt_len; int outlen, ret = 0; ctx = EVP_CIPHER_CTX_new(); diff --git a/src/quic_tp.c b/src/quic_tp.c index 4b081e2c4..fb47aa790 100644 --- a/src/quic_tp.c +++ b/src/quic_tp.c @@ -400,7 +400,13 @@ quic_transport_param_decode(struct quic_transport_params *p, int server, if (!server) return QUIC_TP_DEC_ERR_INVAL; - /* TODO implement parsing for client side */ + if (len > sizeof p->retry_source_connection_id.data) + return QUIC_TP_DEC_ERR_TRUNC; + + if (len) + memcpy(p->retry_source_connection_id.data, *buf, len); + p->retry_source_connection_id.len = len; + *buf += len; break; default: *buf += len; @@ -753,6 +759,12 @@ int quic_transport_params_store(struct quic_conn *qc, int server, return 0; } + if (server && (qc->odcid.len != tx_params->retry_source_connection_id.len || + memcmp(qc->odcid.data, tx_params->retry_source_connection_id.data, qc->odcid.len) != 0)) { + TRACE_ERROR("retry_source_connection_id mismatch", QUIC_EV_TRANSP_PARAMS, qc); + return 0; + } + /* Update the connection from transport parameters received */ if (tx_params->version_information.negotiated_version && tx_params->version_information.negotiated_version != qc->original_version) diff --git a/src/quic_tx.c b/src/quic_tx.c index 8ce67bbf6..8888fa77c 100644 --- a/src/quic_tx.c +++ b/src/quic_tx.c @@ -1304,7 +1304,7 @@ int send_retry(int fd, struct sockaddr_storage *addr, /* token integrity tag */ if ((sizeof(buf) - i < QUIC_TLS_TAG_LEN) || !quic_tls_generate_retry_integrity_tag(pkt->dcid.data, - pkt->dcid.len, buf, i, qv)) { + pkt->dcid.len, buf, i, buf + i, qv)) { TRACE_ERROR("quic_tls_generate_retry_integrity_tag() failed", QUIC_EV_CONN_TXPKT); goto out; } @@ -1809,10 +1809,14 @@ static int qc_do_build_pkt(unsigned char *pos, const unsigned char *end, /* Encode the token length (0) for an Initial packet. */ if (pkt->type == QUIC_PACKET_TYPE_INITIAL) { - if (end <= pos) + if (!quic_enc_int(&pos, end, qc->retry_token_len) || + end - pos <= qc->retry_token_len) goto no_room; - *pos++ = 0; + if (qc->retry_token_len) { + memcpy(pos, qc->retry_token, qc->retry_token_len); + pos += qc->retry_token_len; + } } head_len = pos - beg;