* @struct validation_token
* @brief Represents a validation token for secure connection handling.
*
- * This struct is used to store information related to a validation token,
- * including the token buffer, original connection ID, and an integrity tag
- * for secure validation of QUIC connections.
+ * This struct is used to store information related to a validation token.
*
- * @var validation_token::token_buf
- * A character array holding the token data. The size of this array is
- * based on the length of the string "openssltoken" minus one for the null
- * terminator.
+ * @var validation_token::is_retry
+ * True iff this validation token is for a token sent in a RETRY packet.
+ * Otherwise, this token is from a NEW_TOKEN_packet. Iff this value is true,
+ * then ODCID and RSCID are set.
*
- * @var validation_token::token_odcid
+ * @var validation_token::timestamp
+ * Time that the validation token was minted.
+ *
+ * @var validation_token::odcid
* An original connection ID (`QUIC_CONN_ID`) used to identify the QUIC
* connection. This ID helps associate the token with a specific connection.
+ * This will only be valid for validation tokens from RETRY packets.
+ *
+ * @var validation_token::rscid
+ * DCID that the client will use as the DCID of the subsequent initial packet
+ * i.e the "new" DCID.
+ * This will only be valid for validation tokens from RETRY packets.
+ *
+ * @var validation_token::remote_addr_len
+ * Length of the following character array.
*
- * @var validation_token::integrity_tag
- * A character array for the integrity tag, with a length defined by
- * `QUIC_RETRY_INTEGRITY_TAG_LEN`. This tag is used to verify the integrity
- * of the token during the connection process.
+ * @var validation_token::remote_addr
+ * A character array holding the raw address of the client requesting the
+ * connection.
*/
-struct validation_token {
- char token_buf[sizeof("openssltoken") - 1];
- QUIC_CONN_ID token_odcid;
- char integrity_tag[QUIC_RETRY_INTEGRITY_TAG_LEN];
-};
+typedef struct validation_token {
+ OSSL_TIME timestamp;
+ QUIC_CONN_ID odcid;
+ QUIC_CONN_ID rscid;
+ size_t remote_addr_len;
+ unsigned char *remote_addr;
+ unsigned char is_retry;
+} QUIC_VALIDATION_TOKEN;
DEFINE_LIST_OF_IMPL(ch, QUIC_CHANNEL);
DEFINE_LIST_OF_IMPL(incoming_ch, QUIC_CHANNEL);
return i > 0;
}
-#define TOKEN_LEN (sizeof("openssltoken") + \
- QUIC_RETRY_INTEGRITY_TAG_LEN - 1 + \
- sizeof(unsigned char))
+static void cleanup_validation_token(QUIC_VALIDATION_TOKEN *token)
+{
+ OPENSSL_free(token->remote_addr);
+}
+
+/**
+ * @brief Generates a validation token for a RETRY packet.
+ *
+ * @param peer Address of the client peer receiving the packet.
+ * @param odcid DCID of the connection attempt.
+ * @param rscid Retry source connection ID of the connection attempt.
+ * @param token Address of token to fill data.
+ *
+ * @return 1 if validation token is filled successfully, 0 otherwise.
+ */
+static int generate_retry_token(BIO_ADDR *peer, QUIC_CONN_ID odcid,
+ QUIC_CONN_ID rscid, QUIC_VALIDATION_TOKEN *token)
+{
+ token->is_retry = 1;
+ token->timestamp = ossl_time_now();
+ token->remote_addr = NULL;
+ token->odcid = odcid;
+ token->rscid = rscid;
+
+ if (!BIO_ADDR_rawaddress(peer, NULL, &token->remote_addr_len)
+ || token->remote_addr_len == 0
+ || (token->remote_addr = OPENSSL_malloc(token->remote_addr_len)) == NULL
+ || !BIO_ADDR_rawaddress(peer, token->remote_addr,
+ &token->remote_addr_len)) {
+ cleanup_validation_token(token);
+ return 0;
+ }
+
+ return 1;
+}
+
+/**
+ * @brief Marshals a validation token into a new buffer.
+ *
+ * Dynamically allocates |buffer| and stores the size of |buffer| in BUFFER_LEN.
+ * Note that it will also allocate an extra QUIC_RETRY_INTEGRITY_TAG_LEN bytes.
+ * The caller is responsible for freeing |buffer|.
+ *
+ * @param token Validation token.
+ * @param buffer Callee will allocate a buffer and store the address here.
+ * @param buffer_len Size of |buffer|.
+ */
+static int marshal_validation_token(QUIC_VALIDATION_TOKEN *token,
+ unsigned char **buffer, size_t *buffer_len)
+{
+ WPACKET wpkt = {0};
+ BUF_MEM *buf_mem = BUF_MEM_new();
+
+ if (buf_mem == NULL || (token->is_retry != 0 && token->is_retry != 1))
+ return 0;
+
+ if (!WPACKET_init(&wpkt, buf_mem)
+ || !WPACKET_memset(&wpkt, token->is_retry, 1)
+ || !WPACKET_memcpy(&wpkt, &token->timestamp,
+ sizeof(token->timestamp))
+ || (token->is_retry
+ && (!WPACKET_sub_memcpy_u8(&wpkt, &token->odcid.id,
+ token->odcid.id_len)
+ || !WPACKET_sub_memcpy_u8(&wpkt, &token->rscid.id,
+ token->rscid.id_len)))
+ || !WPACKET_sub_memcpy_u8(&wpkt, token->remote_addr, token->remote_addr_len)
+ || !WPACKET_allocate_bytes(&wpkt, QUIC_RETRY_INTEGRITY_TAG_LEN, NULL)
+ || !WPACKET_get_total_written(&wpkt, buffer_len)
+ || !WPACKET_finish(&wpkt)) {
+ WPACKET_cleanup(&wpkt);
+ BUF_MEM_free(buf_mem);
+ return 0;
+ }
+
+ *buffer = (unsigned char *)buf_mem->data;
+ buf_mem->data = NULL;
+ BUF_MEM_free(buf_mem);
+ return 1;
+}
+
+/**
+ * @brief Parses contents of a buffer into a validation token.
+ *
+ * VALIDATION_TOKEN should already be initalized. Does some basic sanity checks.
+ *
+ * @param token Validation token to fill data in.
+ * @param buf Buffer of previously marshaled validation token.
+ * @param buf_len Length of |buf|.
+ */
+static int parse_validation_token(QUIC_VALIDATION_TOKEN *token,
+ const unsigned char *buf, size_t buf_len)
+{
+ PACKET pkt, subpkt;
+
+ if (buf == NULL || token == NULL)
+ return 0;
+
+ token->remote_addr = NULL;
+
+ if (!PACKET_buf_init(&pkt, buf, buf_len)
+ || !PACKET_copy_bytes(&pkt, &token->is_retry, sizeof(token->is_retry))
+ || !(token->is_retry == 0 || token->is_retry == 1)
+ || !PACKET_copy_bytes(&pkt, (unsigned char *)&token->timestamp,
+ sizeof(token->timestamp))
+ || (token->is_retry
+ && (!PACKET_get_length_prefixed_1(&pkt, &subpkt)
+ || (token->odcid.id_len = (unsigned char)PACKET_remaining(&subpkt))
+ > QUIC_MAX_CONN_ID_LEN
+ || !PACKET_copy_bytes(&subpkt,
+ (unsigned char *)&token->odcid.id,
+ token->odcid.id_len)
+ || !PACKET_get_length_prefixed_1(&pkt, &subpkt)
+ || (token->rscid.id_len = (unsigned char)PACKET_remaining(&subpkt))
+ > QUIC_MAX_CONN_ID_LEN
+ || !PACKET_copy_bytes(&subpkt, (unsigned char *)&token->rscid.id,
+ token->rscid.id_len)))
+ || !PACKET_get_length_prefixed_1(&pkt, &subpkt)
+ || (token->remote_addr_len = PACKET_remaining(&subpkt)) == 0
+ || (token->remote_addr = OPENSSL_malloc(token->remote_addr_len)) == NULL
+ || !PACKET_copy_bytes(&subpkt, token->remote_addr, token->remote_addr_len)
+ || PACKET_remaining(&pkt) != 0) {
+ cleanup_validation_token(token);
+ return 0;
+ }
+
+ return 1;
+}
/**
* @brief Sends a QUIC Retry packet to a client.
QUIC_PKT_HDR *client_hdr)
{
BIO_MSG msg[1];
- unsigned char buffer[512];
+ unsigned char buffer[512], *token_buf = NULL;
WPACKET wpkt;
- size_t written;
- QUIC_PKT_HDR hdr;
- struct validation_token token;
- size_t token_len = TOKEN_LEN;
- unsigned char *integrity_tag;
+ size_t written, token_buf_len;
+ QUIC_PKT_HDR hdr = {0};
+ QUIC_VALIDATION_TOKEN token = {0};
int ok;
- /* TODO(QUIC_SERVER): generate proper validation token */
- memcpy(token.token_buf, "openssltoken", sizeof("openssltoken") - 1);
-
- token.token_odcid = client_hdr->dst_conn_id;
- token_len += token.token_odcid.id_len;
- integrity_tag = (unsigned char *)&token.token_odcid +
- token.token_odcid.id_len + sizeof(token.token_odcid.id_len);
/*
* 17.2.5.1 Sending a Retry packet
* dst ConnId is src ConnId we got from client
*/
ok = ossl_quic_lcidm_get_unused_cid(port->lcidm, &hdr.src_conn_id);
if (ok == 0)
- return;
+ goto err;
+
+ /* Generate retry validation token */
+ if (!generate_retry_token(peer, client_hdr->dst_conn_id,
+ hdr.src_conn_id, &token)
+ || !marshal_validation_token(&token, &token_buf, &token_buf_len)
+ || !ossl_assert(token_buf_len >= QUIC_RETRY_INTEGRITY_TAG_LEN))
+ goto err;
hdr.dst_conn_id = client_hdr->src_conn_id;
hdr.type = QUIC_PKT_TYPE_RETRY;
hdr.fixed = 1;
hdr.version = 1;
- hdr.len = token_len;
- hdr.data = (unsigned char *)&token;
+ hdr.len = token_buf_len;
+ hdr.data = token_buf;
ok = ossl_quic_calculate_retry_integrity_tag(port->engine->libctx,
port->engine->propq, &hdr,
&client_hdr->dst_conn_id,
- integrity_tag);
+ token_buf + token_buf_len
+ - QUIC_RETRY_INTEGRITY_TAG_LEN);
if (ok == 0)
- return;
+ goto err;
- hdr.token = (unsigned char *)&token;
- hdr.token_len = token_len;
+ hdr.token = hdr.data;
+ hdr.token_len = hdr.len;
msg[0].data = buffer;
msg[0].peer = peer;
ok = WPACKET_init_static_len(&wpkt, buffer, sizeof(buffer), 0);
if (ok == 0)
- return;
+ goto err;
ok = ossl_quic_wire_encode_pkt_hdr(&wpkt, client_hdr->dst_conn_id.id_len,
&hdr, NULL);
if (ok == 0)
- return;
+ goto err;
ok = WPACKET_get_total_written(&wpkt, &msg[0].data_len);
if (ok == 0)
- return;
+ goto err;
ok = WPACKET_finish(&wpkt);
if (ok == 0)
- return;
+ goto err;
/*
* TODO(QUIC SERVER) need to retry this in the event it return EAGAIN
ERR_raise_data(ERR_LIB_SSL, SSL_R_QUIC_NETWORK_ERROR,
"port retry send failed due to network BIO I/O error");
+err:
+ cleanup_validation_token(&token);
+ OPENSSL_free(token_buf);
}
/**
* This function checks the validity of a token contained in the provided
* QUIC packet header (`QUIC_PKT_HDR *hdr`). The validation process involves
* verifying that the token matches an expected format and value. If the
- * token is valid, the function extracts the original connection ID (ODCID)
- * and stores it in the provided `QUIC_CONN_ID *odcid`.
+ * token is from a RETRY packet, the function extracts the original connection
+ * ID (ODCID)/original source connection ID (SCID) and stores it in the provided
+ * parameters. If the token is from a NEW_TOKEN packet, the values will be
+ * derived instead.
*
* @param hdr Pointer to the QUIC packet header containing the token.
- * @param odcid Pointer to the connection ID structure to store the ODCID if
- * the token is valid.
- * @return 1 if the token is valid and ODCID is extracted successfully,
+ * @param port Pointer to the QUIC port from which to send the packet.
+ * @param peer Address of the client peer receiving the packet.
+ * @param odcid Pointer to the connection ID structure to store the ODCID if the
+ * token is valid.
+ * @param scid Pointer to the connection ID structure to store the SCID if the
+ * token is valid.
+ *
+ * @return 1 if the token is valid and ODCID/SCID are successfully set.
* 0 otherwise.
*
* The function performs the following checks:
- * - Verifies that the token length meets the required minimum.
- * - Confirms the token buffer matches the expected "openssltoken" string.
- * -
+ * - Token length meets the required minimum.
+ * - Buffer matches expected format.
+ * - Peer address matches previous connection address.
+ * - Token has not expired. Currently set to 10 seconds for tokens from RETRY
+ * packets and 60 minutes for tokens from NEW_TOKEN packets. This may be
+ * configurable in the future.
*/
-static int port_validate_token(QUIC_PKT_HDR *hdr, QUIC_CONN_ID *odcid)
+static int port_validate_token(QUIC_PKT_HDR *hdr, QUIC_PORT *port,
+ BIO_ADDR *peer, QUIC_CONN_ID *odcid,
+ QUIC_CONN_ID *scid)
{
- int valid;
- struct validation_token *token;
-
- memset(odcid, 0, sizeof(QUIC_CONN_ID));
+ int ret = 0;
+ QUIC_VALIDATION_TOKEN token = { 0 };
+ unsigned long long time_diff;
+ size_t remote_addr_len;
+ unsigned char *remote_addr = NULL;
+ OSSL_TIME now = ossl_time_now();
+
+ if (!parse_validation_token(&token, hdr->token, hdr->token_len))
+ goto err;
- token = (struct validation_token *)hdr->token;
- if (token == NULL || hdr->token_len <= (TOKEN_LEN - QUIC_RETRY_INTEGRITY_TAG_LEN))
- return 0;
+ /*
+ * Validate token timestamp. Current time should not be before the token
+ * timestamp.
+ */
+ if (ossl_time_compare(now, token.timestamp) < 0)
+ goto err;
+ time_diff = ossl_time2seconds(ossl_time_abs_difference(token.timestamp,
+ now));
+ if ((token.is_retry && time_diff > 10)
+ || (!token.is_retry && time_diff > 3600))
+ goto err;
- valid = memcmp(token->token_buf, "openssltoken", sizeof("openssltoken") - 1);
- if (valid != 0)
- return 0;
+ /* Validate remote address */
+ if (!BIO_ADDR_rawaddress(peer, NULL, &remote_addr_len)
+ || remote_addr_len != token.remote_addr_len
+ || (remote_addr = OPENSSL_malloc(remote_addr_len)) == NULL
+ || !BIO_ADDR_rawaddress(peer, remote_addr, &remote_addr_len)
+ || memcmp(remote_addr, token.remote_addr, remote_addr_len) != 0)
+ goto err;
- odcid->id_len = token->token_odcid.id_len;
- memcpy(odcid->id, token->token_odcid.id, token->token_odcid.id_len);
+ /*
+ * Set ODCID and SCID. If the token is from a RETRY packet, retrieve both
+ * from the token. Otherwise, generate a new ODCID and use the header's
+ * source connection ID for SCID.
+ */
+ if (token.is_retry) {
+ /*
+ * We're parsing a packet header before its gone through AEAD validation
+ * here, so there is a chance we are dealing with corrupted data. Make
+ * Sure the dcid encoded in the token matches the headers dcid to
+ * mitigate that.
+ * TODO(QUIC SERVER): Consider handling AEAD validation at the port
+ * level rather than the QRX/channel level to eliminate the need for
+ * this.
+ */
+ if (token.rscid.id_len != hdr->dst_conn_id.id_len
+ || memcmp(&token.rscid.id, &hdr->dst_conn_id.id,
+ token.rscid.id_len) != 0)
+ goto err;
+ *odcid = token.odcid;
+ *scid = token.rscid;
+ } else {
+ if (!ossl_quic_lcidm_get_unused_cid(port->lcidm, odcid))
+ goto err;
+ *scid = hdr->src_conn_id;
+ }
- return 1;
+ ret = 1;
+err:
+ cleanup_validation_token(&token);
+ OPENSSL_free(remote_addr);
+ return ret;
}
/*
PACKET pkt;
QUIC_PKT_HDR hdr;
QUIC_CHANNEL *ch = NULL, *new_ch = NULL;
- QUIC_CONN_ID odcid;
+ QUIC_CONN_ID odcid, scid;
uint64_t cause_flags = 0;
/* Don't handle anything if we are no longer running. */
if (hdr.token == NULL) {
port_send_retry(port, &e->peer, &hdr);
goto undesirable;
- } else if (port_validate_token(&hdr, &odcid) == 0) {
+ } else if (port_validate_token(&hdr, port, &e->peer, &odcid, &scid) != 1) {
goto undesirable;
}
- port_bind_channel(port, &e->peer, &hdr.src_conn_id, &hdr.dst_conn_id,
+ port_bind_channel(port, &e->peer, &scid, &hdr.dst_conn_id,
&odcid, &new_ch);
/*