Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit c6a6a14

Browse files
committedFeb 14, 2025
Augment RETRY validation token
Adds fields to the QUIC RETRY packet validation token: timestamp, remote_addr, odcid, & rscid. Also adds functionality to validate the token once returned by the client. Note that this does not encrypt the token yet. Also check that the RSCID stored in the RETRY validation token matches the DCID in the header. Reviewed-by: Neil Horman <nhorman@openssl.org> Reviewed-by: Saša Nedvědický <sashan@openssl.org> Reviewed-by: Matt Caswell <matt@openssl.org> Reviewed-by: Tomas Mraz <tomas@openssl.org> (Merged from openssl#26048)
1 parent 4aecfb8 commit c6a6a14

File tree

4 files changed

+274
-82
lines changed

4 files changed

+274
-82
lines changed
 

‎ssl/quic/quic_port.c

+264-69
Original file line numberDiff line numberDiff line change
@@ -34,29 +34,41 @@ static void port_rx_pre(QUIC_PORT *port);
3434
* @struct validation_token
3535
* @brief Represents a validation token for secure connection handling.
3636
*
37-
* This struct is used to store information related to a validation token,
38-
* including the token buffer, original connection ID, and an integrity tag
39-
* for secure validation of QUIC connections.
37+
* This struct is used to store information related to a validation token.
4038
*
41-
* @var validation_token::token_buf
42-
* A character array holding the token data. The size of this array is
43-
* based on the length of the string "openssltoken" minus one for the null
44-
* terminator.
39+
* @var validation_token::is_retry
40+
* True iff this validation token is for a token sent in a RETRY packet.
41+
* Otherwise, this token is from a NEW_TOKEN_packet. Iff this value is true,
42+
* then ODCID and RSCID are set.
4543
*
46-
* @var validation_token::token_odcid
44+
* @var validation_token::timestamp
45+
* Time that the validation token was minted.
46+
*
47+
* @var validation_token::odcid
4748
* An original connection ID (`QUIC_CONN_ID`) used to identify the QUIC
4849
* connection. This ID helps associate the token with a specific connection.
50+
* This will only be valid for validation tokens from RETRY packets.
51+
*
52+
* @var validation_token::rscid
53+
* DCID that the client will use as the DCID of the subsequent initial packet
54+
* i.e the "new" DCID.
55+
* This will only be valid for validation tokens from RETRY packets.
56+
*
57+
* @var validation_token::remote_addr_len
58+
* Length of the following character array.
4959
*
50-
* @var validation_token::integrity_tag
51-
* A character array for the integrity tag, with a length defined by
52-
* `QUIC_RETRY_INTEGRITY_TAG_LEN`. This tag is used to verify the integrity
53-
* of the token during the connection process.
60+
* @var validation_token::remote_addr
61+
* A character array holding the raw address of the client requesting the
62+
* connection.
5463
*/
55-
struct validation_token {
56-
char token_buf[sizeof("openssltoken") - 1];
57-
QUIC_CONN_ID token_odcid;
58-
char integrity_tag[QUIC_RETRY_INTEGRITY_TAG_LEN];
59-
};
64+
typedef struct validation_token {
65+
OSSL_TIME timestamp;
66+
QUIC_CONN_ID odcid;
67+
QUIC_CONN_ID rscid;
68+
size_t remote_addr_len;
69+
unsigned char *remote_addr;
70+
unsigned char is_retry;
71+
} QUIC_VALIDATION_TOKEN;
6072

6173
DEFINE_LIST_OF_IMPL(ch, QUIC_CHANNEL);
6274
DEFINE_LIST_OF_IMPL(incoming_ch, QUIC_CHANNEL);
@@ -647,9 +659,133 @@ static int port_try_handle_stateless_reset(QUIC_PORT *port, const QUIC_URXE *e)
647659
return i > 0;
648660
}
649661

650-
#define TOKEN_LEN (sizeof("openssltoken") + \
651-
QUIC_RETRY_INTEGRITY_TAG_LEN - 1 + \
652-
sizeof(unsigned char))
662+
static void cleanup_validation_token(QUIC_VALIDATION_TOKEN *token)
663+
{
664+
OPENSSL_free(token->remote_addr);
665+
}
666+
667+
/**
668+
* @brief Generates a validation token for a RETRY packet.
669+
*
670+
* @param peer Address of the client peer receiving the packet.
671+
* @param odcid DCID of the connection attempt.
672+
* @param rscid Retry source connection ID of the connection attempt.
673+
* @param token Address of token to fill data.
674+
*
675+
* @return 1 if validation token is filled successfully, 0 otherwise.
676+
*/
677+
static int generate_retry_token(BIO_ADDR *peer, QUIC_CONN_ID odcid,
678+
QUIC_CONN_ID rscid, QUIC_VALIDATION_TOKEN *token)
679+
{
680+
token->is_retry = 1;
681+
token->timestamp = ossl_time_now();
682+
token->remote_addr = NULL;
683+
token->odcid = odcid;
684+
token->rscid = rscid;
685+
686+
if (!BIO_ADDR_rawaddress(peer, NULL, &token->remote_addr_len)
687+
|| token->remote_addr_len == 0
688+
|| (token->remote_addr = OPENSSL_malloc(token->remote_addr_len)) == NULL
689+
|| !BIO_ADDR_rawaddress(peer, token->remote_addr,
690+
&token->remote_addr_len)) {
691+
cleanup_validation_token(token);
692+
return 0;
693+
}
694+
695+
return 1;
696+
}
697+
698+
/**
699+
* @brief Marshals a validation token into a new buffer.
700+
*
701+
* Dynamically allocates |buffer| and stores the size of |buffer| in BUFFER_LEN.
702+
* Note that it will also allocate an extra QUIC_RETRY_INTEGRITY_TAG_LEN bytes.
703+
* The caller is responsible for freeing |buffer|.
704+
*
705+
* @param token Validation token.
706+
* @param buffer Callee will allocate a buffer and store the address here.
707+
* @param buffer_len Size of |buffer|.
708+
*/
709+
static int marshal_validation_token(QUIC_VALIDATION_TOKEN *token,
710+
unsigned char **buffer, size_t *buffer_len)
711+
{
712+
WPACKET wpkt = {0};
713+
BUF_MEM *buf_mem = BUF_MEM_new();
714+
715+
if (buf_mem == NULL || (token->is_retry != 0 && token->is_retry != 1))
716+
return 0;
717+
718+
if (!WPACKET_init(&wpkt, buf_mem)
719+
|| !WPACKET_memset(&wpkt, token->is_retry, 1)
720+
|| !WPACKET_memcpy(&wpkt, &token->timestamp,
721+
sizeof(token->timestamp))
722+
|| (token->is_retry
723+
&& (!WPACKET_sub_memcpy_u8(&wpkt, &token->odcid.id,
724+
token->odcid.id_len)
725+
|| !WPACKET_sub_memcpy_u8(&wpkt, &token->rscid.id,
726+
token->rscid.id_len)))
727+
|| !WPACKET_sub_memcpy_u8(&wpkt, token->remote_addr, token->remote_addr_len)
728+
|| !WPACKET_allocate_bytes(&wpkt, QUIC_RETRY_INTEGRITY_TAG_LEN, NULL)
729+
|| !WPACKET_get_total_written(&wpkt, buffer_len)
730+
|| !WPACKET_finish(&wpkt)) {
731+
WPACKET_cleanup(&wpkt);
732+
BUF_MEM_free(buf_mem);
733+
return 0;
734+
}
735+
736+
*buffer = (unsigned char *)buf_mem->data;
737+
buf_mem->data = NULL;
738+
BUF_MEM_free(buf_mem);
739+
return 1;
740+
}
741+
742+
/**
743+
* @brief Parses contents of a buffer into a validation token.
744+
*
745+
* VALIDATION_TOKEN should already be initalized. Does some basic sanity checks.
746+
*
747+
* @param token Validation token to fill data in.
748+
* @param buf Buffer of previously marshaled validation token.
749+
* @param buf_len Length of |buf|.
750+
*/
751+
static int parse_validation_token(QUIC_VALIDATION_TOKEN *token,
752+
const unsigned char *buf, size_t buf_len)
753+
{
754+
PACKET pkt, subpkt;
755+
756+
if (buf == NULL || token == NULL)
757+
return 0;
758+
759+
token->remote_addr = NULL;
760+
761+
if (!PACKET_buf_init(&pkt, buf, buf_len)
762+
|| !PACKET_copy_bytes(&pkt, &token->is_retry, sizeof(token->is_retry))
763+
|| !(token->is_retry == 0 || token->is_retry == 1)
764+
|| !PACKET_copy_bytes(&pkt, (unsigned char *)&token->timestamp,
765+
sizeof(token->timestamp))
766+
|| (token->is_retry
767+
&& (!PACKET_get_length_prefixed_1(&pkt, &subpkt)
768+
|| (token->odcid.id_len = (unsigned char)PACKET_remaining(&subpkt))
769+
> QUIC_MAX_CONN_ID_LEN
770+
|| !PACKET_copy_bytes(&subpkt,
771+
(unsigned char *)&token->odcid.id,
772+
token->odcid.id_len)
773+
|| !PACKET_get_length_prefixed_1(&pkt, &subpkt)
774+
|| (token->rscid.id_len = (unsigned char)PACKET_remaining(&subpkt))
775+
> QUIC_MAX_CONN_ID_LEN
776+
|| !PACKET_copy_bytes(&subpkt, (unsigned char *)&token->rscid.id,
777+
token->rscid.id_len)))
778+
|| !PACKET_get_length_prefixed_1(&pkt, &subpkt)
779+
|| (token->remote_addr_len = PACKET_remaining(&subpkt)) == 0
780+
|| (token->remote_addr = OPENSSL_malloc(token->remote_addr_len)) == NULL
781+
|| !PACKET_copy_bytes(&subpkt, token->remote_addr, token->remote_addr_len)
782+
|| PACKET_remaining(&pkt) != 0) {
783+
cleanup_validation_token(token);
784+
return 0;
785+
}
786+
787+
return 1;
788+
}
653789

654790
/**
655791
* @brief Sends a QUIC Retry packet to a client.
@@ -678,22 +814,13 @@ static void port_send_retry(QUIC_PORT *port,
678814
QUIC_PKT_HDR *client_hdr)
679815
{
680816
BIO_MSG msg[1];
681-
unsigned char buffer[512];
817+
unsigned char buffer[512], *token_buf = NULL;
682818
WPACKET wpkt;
683-
size_t written;
684-
QUIC_PKT_HDR hdr;
685-
struct validation_token token;
686-
size_t token_len = TOKEN_LEN;
687-
unsigned char *integrity_tag;
819+
size_t written, token_buf_len;
820+
QUIC_PKT_HDR hdr = {0};
821+
QUIC_VALIDATION_TOKEN token = {0};
688822
int ok;
689823

690-
/* TODO(QUIC_SERVER): generate proper validation token */
691-
memcpy(token.token_buf, "openssltoken", sizeof("openssltoken") - 1);
692-
693-
token.token_odcid = client_hdr->dst_conn_id;
694-
token_len += token.token_odcid.id_len;
695-
integrity_tag = (unsigned char *)&token.token_odcid +
696-
token.token_odcid.id_len + sizeof(token.token_odcid.id_len);
697824
/*
698825
* 17.2.5.1 Sending a Retry packet
699826
* dst ConnId is src ConnId we got from client
@@ -708,23 +835,31 @@ static void port_send_retry(QUIC_PORT *port,
708835
*/
709836
ok = ossl_quic_lcidm_get_unused_cid(port->lcidm, &hdr.src_conn_id);
710837
if (ok == 0)
711-
return;
838+
goto err;
839+
840+
/* Generate retry validation token */
841+
if (!generate_retry_token(peer, client_hdr->dst_conn_id,
842+
hdr.src_conn_id, &token)
843+
|| !marshal_validation_token(&token, &token_buf, &token_buf_len)
844+
|| !ossl_assert(token_buf_len >= QUIC_RETRY_INTEGRITY_TAG_LEN))
845+
goto err;
712846

713847
hdr.dst_conn_id = client_hdr->src_conn_id;
714848
hdr.type = QUIC_PKT_TYPE_RETRY;
715849
hdr.fixed = 1;
716850
hdr.version = 1;
717-
hdr.len = token_len;
718-
hdr.data = (unsigned char *)&token;
851+
hdr.len = token_buf_len;
852+
hdr.data = token_buf;
719853
ok = ossl_quic_calculate_retry_integrity_tag(port->engine->libctx,
720854
port->engine->propq, &hdr,
721855
&client_hdr->dst_conn_id,
722-
integrity_tag);
856+
token_buf + token_buf_len
857+
- QUIC_RETRY_INTEGRITY_TAG_LEN);
723858
if (ok == 0)
724-
return;
859+
goto err;
725860

726-
hdr.token = (unsigned char *)&token;
727-
hdr.token_len = token_len;
861+
hdr.token = hdr.data;
862+
hdr.token_len = hdr.len;
728863

729864
msg[0].data = buffer;
730865
msg[0].peer = peer;
@@ -733,20 +868,20 @@ static void port_send_retry(QUIC_PORT *port,
733868

734869
ok = WPACKET_init_static_len(&wpkt, buffer, sizeof(buffer), 0);
735870
if (ok == 0)
736-
return;
871+
goto err;
737872

738873
ok = ossl_quic_wire_encode_pkt_hdr(&wpkt, client_hdr->dst_conn_id.id_len,
739874
&hdr, NULL);
740875
if (ok == 0)
741-
return;
876+
goto err;
742877

743878
ok = WPACKET_get_total_written(&wpkt, &msg[0].data_len);
744879
if (ok == 0)
745-
return;
880+
goto err;
746881

747882
ok = WPACKET_finish(&wpkt);
748883
if (ok == 0)
749-
return;
884+
goto err;
750885

751886
/*
752887
* TODO(QUIC SERVER) need to retry this in the event it return EAGAIN
@@ -756,6 +891,9 @@ static void port_send_retry(QUIC_PORT *port,
756891
ERR_raise_data(ERR_LIB_SSL, SSL_R_QUIC_NETWORK_ERROR,
757892
"port retry send failed due to network BIO I/O error");
758893

894+
err:
895+
cleanup_validation_token(&token);
896+
OPENSSL_free(token_buf);
759897
}
760898

761899
/**
@@ -853,39 +991,96 @@ static void port_send_version_negotiation(QUIC_PORT *port, BIO_ADDR *peer,
853991
* This function checks the validity of a token contained in the provided
854992
* QUIC packet header (`QUIC_PKT_HDR *hdr`). The validation process involves
855993
* verifying that the token matches an expected format and value. If the
856-
* token is valid, the function extracts the original connection ID (ODCID)
857-
* and stores it in the provided `QUIC_CONN_ID *odcid`.
994+
* token is from a RETRY packet, the function extracts the original connection
995+
* ID (ODCID)/original source connection ID (SCID) and stores it in the provided
996+
* parameters. If the token is from a NEW_TOKEN packet, the values will be
997+
* derived instead.
858998
*
859999
* @param hdr Pointer to the QUIC packet header containing the token.
860-
* @param odcid Pointer to the connection ID structure to store the ODCID if
861-
* the token is valid.
862-
* @return 1 if the token is valid and ODCID is extracted successfully,
1000+
* @param port Pointer to the QUIC port from which to send the packet.
1001+
* @param peer Address of the client peer receiving the packet.
1002+
* @param odcid Pointer to the connection ID structure to store the ODCID if the
1003+
* token is valid.
1004+
* @param scid Pointer to the connection ID structure to store the SCID if the
1005+
* token is valid.
1006+
*
1007+
* @return 1 if the token is valid and ODCID/SCID are successfully set.
8631008
* 0 otherwise.
8641009
*
8651010
* The function performs the following checks:
866-
* - Verifies that the token length meets the required minimum.
867-
* - Confirms the token buffer matches the expected "openssltoken" string.
868-
* -
1011+
* - Token length meets the required minimum.
1012+
* - Buffer matches expected format.
1013+
* - Peer address matches previous connection address.
1014+
* - Token has not expired. Currently set to 10 seconds for tokens from RETRY
1015+
* packets and 60 minutes for tokens from NEW_TOKEN packets. This may be
1016+
* configurable in the future.
8691017
*/
870-
static int port_validate_token(QUIC_PKT_HDR *hdr, QUIC_CONN_ID *odcid)
1018+
static int port_validate_token(QUIC_PKT_HDR *hdr, QUIC_PORT *port,
1019+
BIO_ADDR *peer, QUIC_CONN_ID *odcid,
1020+
QUIC_CONN_ID *scid)
8711021
{
872-
int valid;
873-
struct validation_token *token;
874-
875-
memset(odcid, 0, sizeof(QUIC_CONN_ID));
1022+
int ret = 0;
1023+
QUIC_VALIDATION_TOKEN token = { 0 };
1024+
unsigned long long time_diff;
1025+
size_t remote_addr_len;
1026+
unsigned char *remote_addr = NULL;
1027+
OSSL_TIME now = ossl_time_now();
1028+
1029+
if (!parse_validation_token(&token, hdr->token, hdr->token_len))
1030+
goto err;
8761031

877-
token = (struct validation_token *)hdr->token;
878-
if (token == NULL || hdr->token_len <= (TOKEN_LEN - QUIC_RETRY_INTEGRITY_TAG_LEN))
879-
return 0;
1032+
/*
1033+
* Validate token timestamp. Current time should not be before the token
1034+
* timestamp.
1035+
*/
1036+
if (ossl_time_compare(now, token.timestamp) < 0)
1037+
goto err;
1038+
time_diff = ossl_time2seconds(ossl_time_abs_difference(token.timestamp,
1039+
now));
1040+
if ((token.is_retry && time_diff > 10)
1041+
|| (!token.is_retry && time_diff > 3600))
1042+
goto err;
8801043

881-
valid = memcmp(token->token_buf, "openssltoken", sizeof("openssltoken") - 1);
882-
if (valid != 0)
883-
return 0;
1044+
/* Validate remote address */
1045+
if (!BIO_ADDR_rawaddress(peer, NULL, &remote_addr_len)
1046+
|| remote_addr_len != token.remote_addr_len
1047+
|| (remote_addr = OPENSSL_malloc(remote_addr_len)) == NULL
1048+
|| !BIO_ADDR_rawaddress(peer, remote_addr, &remote_addr_len)
1049+
|| memcmp(remote_addr, token.remote_addr, remote_addr_len) != 0)
1050+
goto err;
8841051

885-
odcid->id_len = token->token_odcid.id_len;
886-
memcpy(odcid->id, token->token_odcid.id, token->token_odcid.id_len);
1052+
/*
1053+
* Set ODCID and SCID. If the token is from a RETRY packet, retrieve both
1054+
* from the token. Otherwise, generate a new ODCID and use the header's
1055+
* source connection ID for SCID.
1056+
*/
1057+
if (token.is_retry) {
1058+
/*
1059+
* We're parsing a packet header before its gone through AEAD validation
1060+
* here, so there is a chance we are dealing with corrupted data. Make
1061+
* Sure the dcid encoded in the token matches the headers dcid to
1062+
* mitigate that.
1063+
* TODO(QUIC SERVER): Consider handling AEAD validation at the port
1064+
* level rather than the QRX/channel level to eliminate the need for
1065+
* this.
1066+
*/
1067+
if (token.rscid.id_len != hdr->dst_conn_id.id_len
1068+
|| memcmp(&token.rscid.id, &hdr->dst_conn_id.id,
1069+
token.rscid.id_len) != 0)
1070+
goto err;
1071+
*odcid = token.odcid;
1072+
*scid = token.rscid;
1073+
} else {
1074+
if (!ossl_quic_lcidm_get_unused_cid(port->lcidm, odcid))
1075+
goto err;
1076+
*scid = hdr->src_conn_id;
1077+
}
8871078

888-
return 1;
1079+
ret = 1;
1080+
err:
1081+
cleanup_validation_token(&token);
1082+
OPENSSL_free(remote_addr);
1083+
return ret;
8891084
}
8901085

8911086
/*
@@ -899,7 +1094,7 @@ static void port_default_packet_handler(QUIC_URXE *e, void *arg,
8991094
PACKET pkt;
9001095
QUIC_PKT_HDR hdr;
9011096
QUIC_CHANNEL *ch = NULL, *new_ch = NULL;
902-
QUIC_CONN_ID odcid;
1097+
QUIC_CONN_ID odcid, scid;
9031098
uint64_t cause_flags = 0;
9041099

9051100
/* Don't handle anything if we are no longer running. */
@@ -991,11 +1186,11 @@ static void port_default_packet_handler(QUIC_URXE *e, void *arg,
9911186
if (hdr.token == NULL) {
9921187
port_send_retry(port, &e->peer, &hdr);
9931188
goto undesirable;
994-
} else if (port_validate_token(&hdr, &odcid) == 0) {
1189+
} else if (port_validate_token(&hdr, port, &e->peer, &odcid, &scid) != 1) {
9951190
goto undesirable;
9961191
}
9971192

998-
port_bind_channel(port, &e->peer, &hdr.src_conn_id, &hdr.dst_conn_id,
1193+
port_bind_channel(port, &e->peer, &scid, &hdr.dst_conn_id,
9991194
&odcid, &new_ch);
10001195

10011196
/*

‎ssl/quic/quic_tserver.c

-3
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,6 @@ struct quic_tserver_st {
4343
/* SSL for the underlying TLS connection */
4444
SSL *tls;
4545

46-
/* The current peer L4 address. AF_UNSPEC if we do not have a peer yet. */
47-
BIO_ADDR cur_peer_addr;
48-
4946
/* Are we connected to a peer? */
5047
unsigned int connected : 1;
5148
};

‎test/recipes/75-test_quicapi_data/ssltraceref-zlib.txt

+5-5
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ Sent Packet
8585
Sent Datagram
8686
Length: 1200
8787
Received Datagram
88-
Length: 52
88+
Length: 63
8989
Sent Frame: Crypto
9090
Offset: 0
9191
Len: 263
@@ -95,8 +95,8 @@ Sent Packet
9595
Version: 0x00000001
9696
Destination Conn Id: 0x????????????????
9797
Source Conn Id: <zero length id>
98-
Payload length: 1157
99-
Token: ??????????????????????????????????????????
98+
Payload length: 1146
99+
Token: ????????????????????????????????????????????????????????????????
100100
Packet Number: 0x00000001
101101
Sent Datagram
102102
Length: 1200
@@ -308,8 +308,8 @@ Sent Packet
308308
Version: 0x00000001
309309
Destination Conn Id: 0x????????????????
310310
Source Conn Id: <zero length id>
311-
Payload length: 1076
312-
Token: ??????????????????????????????????????????
311+
Payload length: 1065
312+
Token: ????????????????????????????????????????????????????????????????
313313
Packet Number: 0x00000002
314314
Sent Packet
315315
Packet Type: Handshake

‎test/recipes/75-test_quicapi_data/ssltraceref.txt

+5-5
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ Sent Packet
8383
Sent Datagram
8484
Length: 1200
8585
Received Datagram
86-
Length: 52
86+
Length: 63
8787
Sent Frame: Crypto
8888
Offset: 0
8989
Len: 256
@@ -93,8 +93,8 @@ Sent Packet
9393
Version: 0x00000001
9494
Destination Conn Id: 0x????????????????
9595
Source Conn Id: <zero length id>
96-
Payload length: 1157
97-
Token: ??????????????????????????????????????????
96+
Payload length: 1146
97+
Token: ????????????????????????????????????????????????????????????????
9898
Packet Number: 0x00000001
9999
Sent Datagram
100100
Length: 1200
@@ -306,8 +306,8 @@ Sent Packet
306306
Version: 0x00000001
307307
Destination Conn Id: 0x????????????????
308308
Source Conn Id: <zero length id>
309-
Payload length: 1076
310-
Token: ??????????????????????????????????????????
309+
Payload length: 1065
310+
Token: ????????????????????????????????????????????????????????????????
311311
Packet Number: 0x00000002
312312
Sent Packet
313313
Packet Type: Handshake

0 commit comments

Comments
 (0)
Please sign in to comment.