*/
unsigned int fixed :1;
+ /*
+ * The unused bits in the low 4 bits of a Retry packet header's first byte.
+ * This is used to ensure that Retry packets have the same bit-for-bit
+ * representation in their header when decoding and encoding them again.
+ * This is necessary to validate Retry packet headers.
+ */
+ unsigned int unused :4;
+
/* [L] Version field. Valid if (type != 1RTT). */
uint32_t version;
int ossl_quic_wire_encode_pkt_hdr_pn(QUIC_PN pn,
unsigned char *enc_pn,
size_t enc_pn_len);
+
+/*
+ * Retry Integrity Tags
+ * ====================
+ */
+
+#define QUIC_RETRY_INTEGRITY_TAG_LEN 16
+
+/*
+ * Validate a retry integrity tag. Returns 1 if the tag is valid.
+ *
+ * Must be called on a hdr with a type of QUIC_PKT_TYPE_RETRY with a valid data
+ * pointer.
+ *
+ * client_initial_dcid must be the original DCID used by the client in its first
+ * Initial packet, as this is used to calculate the Retry Integrity Tag.
+ *
+ * Returns 0 if the tag is invalid, if called on any other type of packet or if
+ * the body is too short.
+ */
+int ossl_quic_validate_retry_integrity_tag(OSSL_LIB_CTX *libctx,
+ const char *propq,
+ const QUIC_PKT_HDR *hdr,
+ const QUIC_CONN_ID *client_initial_dcid);
+
+/*
+ * Calculates a retry integrity tag. Returns 0 on error, for example if hdr does
+ * not have a type of QUIC_PKT_TYPE_RETRY.
+ *
+ * client_initial_dcid must be the original DCID used by the client in its first
+ * Initial packet, as this is used to calculate the Retry Integrity Tag.
+ *
+ * tag must point to a buffer of QUIC_RETRY_INTEGRITY_TAG_LEN bytes in size.
+ *
+ * Note that hdr->data must point to the Retry packet body, and hdr->len must
+ * include the space for the Retry Integrity Tag. (This means that you can
+ * easily fill in a tag in a Retry packet you are generating by calling this
+ * function and passing (hdr->data + hdr->len - QUIC_RETRY_INTEGRITY_TAG_LEN) as
+ * the tag argument.) This function fails if hdr->len is too short to contain a
+ * Retry Integrity Tag.
+ */
+int ossl_quic_calculate_retry_integrity_tag(OSSL_LIB_CTX *libctx,
+ const char *propq,
+ const QUIC_PKT_HDR *hdr,
+ const QUIC_CONN_ID *client_initial_dcid,
+ unsigned char *tag);
+
#endif
return 0;
hdr->partial = partial;
+ hdr->unused = 0;
if ((b0 & 0x80) == 0) {
/* Short header. */
/* Retry packets are always fully decoded. */
hdr->partial = 0;
+ /* Unused bits in Retry header. */
+ hdr->unused = b0 & 0x0f;
+
/* Fields not used in Retry packets. */
memset(hdr->pn, 0, sizeof(hdr->pn));
b0 |= 0x40; /* fixed */
if (ossl_quic_pkt_type_has_pn(hdr->type))
b0 |= hdr->pn_len - 1;
+ if (hdr->type == QUIC_PKT_TYPE_RETRY)
+ b0 |= hdr->unused;
if (!WPACKET_put_bytes_u8(pkt, b0)
|| !WPACKET_put_bytes_u32(pkt, hdr->version)
if (hdr->type == QUIC_PKT_TYPE_VERSION_NEG
|| hdr->type == QUIC_PKT_TYPE_RETRY) {
- if (!WPACKET_reserve_bytes(pkt, hdr->len, NULL))
+ if (hdr->len > 0 && !WPACKET_reserve_bytes(pkt, hdr->len, NULL))
return 0;
return 1;
return 0;
}
- if (!WPACKET_reserve_bytes(pkt, hdr->len, NULL))
+ if (hdr->len > 0 && !WPACKET_reserve_bytes(pkt, hdr->len, NULL))
return 0;
off_sample = off_pn + 4;
return 1;
}
+
+int ossl_quic_validate_retry_integrity_tag(OSSL_LIB_CTX *libctx,
+ const char *propq,
+ const QUIC_PKT_HDR *hdr,
+ const QUIC_CONN_ID *client_initial_dcid)
+{
+ unsigned char expected_tag[QUIC_RETRY_INTEGRITY_TAG_LEN];
+ const unsigned char *actual_tag;
+
+ if (hdr == NULL || hdr->len < QUIC_RETRY_INTEGRITY_TAG_LEN)
+ return 0;
+
+ if (!ossl_quic_calculate_retry_integrity_tag(libctx, propq,
+ hdr, client_initial_dcid,
+ expected_tag))
+ return 0;
+
+ actual_tag = hdr->data + hdr->len - QUIC_RETRY_INTEGRITY_TAG_LEN;
+
+ return !CRYPTO_memcmp(expected_tag, actual_tag,
+ QUIC_RETRY_INTEGRITY_TAG_LEN);
+}
+
+/* RFC 9001 s. 5.8 */
+static const unsigned char retry_integrity_key[] = {
+ 0xbe, 0x0c, 0x69, 0x0b, 0x9f, 0x66, 0x57, 0x5a,
+ 0x1d, 0x76, 0x6b, 0x54, 0xe3, 0x68, 0xc8, 0x4e
+};
+
+static const unsigned char retry_integrity_nonce[] = {
+ 0x46, 0x15, 0x99, 0xd3, 0x5d, 0x63, 0x2b, 0xf2,
+ 0x23, 0x98, 0x25, 0xbb
+};
+
+int ossl_quic_calculate_retry_integrity_tag(OSSL_LIB_CTX *libctx,
+ const char *propq,
+ const QUIC_PKT_HDR *hdr,
+ const QUIC_CONN_ID *client_initial_dcid,
+ unsigned char *tag)
+{
+ EVP_CIPHER *cipher = NULL;
+ EVP_CIPHER_CTX *cctx = NULL;
+ int ok = 0, l = 0, l2 = 0, wpkt_valid = 0;
+ WPACKET wpkt;
+ /* Worst case length of the Retry Psuedo-Packet header is 68 bytes. */
+ unsigned char buf[128];
+ QUIC_PKT_HDR hdr2;
+ size_t hdr_enc_len = 0;
+
+ if (hdr->type != QUIC_PKT_TYPE_RETRY || hdr->version == 0
+ || hdr->len < QUIC_RETRY_INTEGRITY_TAG_LEN
+ || hdr->data == NULL
+ || client_initial_dcid == NULL || tag == NULL
+ || client_initial_dcid->id_len > QUIC_MAX_CONN_ID_LEN)
+ goto err;
+
+ /*
+ * Do not reserve packet body in WPACKET. Retry packet header
+ * does not contain a Length field so this does not affect
+ * the serialized packet header.
+ */
+ hdr2 = *hdr;
+ hdr2.len = 0;
+
+ /* Assemble retry psuedo-packet. */
+ if (!WPACKET_init_static_len(&wpkt, buf, sizeof(buf), 0))
+ goto err;
+
+ wpkt_valid = 1;
+
+ /* Prepend original DCID to the packet. */
+ if (!WPACKET_put_bytes_u8(&wpkt, client_initial_dcid->id_len)
+ || !WPACKET_memcpy(&wpkt, client_initial_dcid->id,
+ client_initial_dcid->id_len))
+ goto err;
+
+ /* Encode main retry header. */
+ if (!ossl_quic_wire_encode_pkt_hdr(&wpkt, hdr2.dst_conn_id.id_len,
+ &hdr2, NULL))
+ goto err;
+
+ if (!WPACKET_get_total_written(&wpkt, &hdr_enc_len))
+ return 0;
+
+ /* Create and initialise cipher context. */
+ if ((cipher = EVP_CIPHER_fetch(libctx, "AES-128-GCM", propq)) == NULL)
+ goto err;
+
+ if ((cctx = EVP_CIPHER_CTX_new()) == NULL)
+ goto err;
+
+ if (!EVP_CipherInit_ex(cctx, cipher, NULL,
+ retry_integrity_key, retry_integrity_nonce, /*enc=*/1))
+ goto err;
+
+ /* Feed packet header as AAD data. */
+ if (EVP_CipherUpdate(cctx, NULL, &l, buf, hdr_enc_len) != 1)
+ return 0;
+
+ /* Feed packet body as AAD data. */
+ if (EVP_CipherUpdate(cctx, NULL, &l, hdr->data,
+ hdr->len - QUIC_RETRY_INTEGRITY_TAG_LEN) != 1)
+ return 0;
+
+ /* Finalise and get tag. */
+ if (EVP_CipherFinal_ex(cctx, NULL, &l2) != 1)
+ return 0;
+
+ if (EVP_CIPHER_CTX_ctrl(cctx, EVP_CTRL_AEAD_GET_TAG,
+ QUIC_RETRY_INTEGRITY_TAG_LEN,
+ tag) != 1)
+ return 0;
+
+ ok = 1;
+err:
+ EVP_CIPHER_free(cipher);
+ EVP_CIPHER_CTX_free(cctx);
+ if (wpkt_valid)
+ WPACKET_finish(&wpkt);
+
+ return ok;
+}
static const QUIC_PKT_HDR rx_script_1_expect_hdr = {
QUIC_PKT_TYPE_INITIAL,
- 0, 0, 2, 0, 1, 1, { 0, {0} },
+ 0, 0, 2, 0, 1, 0, 1, { 0, {0} },
{ 8, {0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5 } },
{ 0, 1, 0, 0 },
NULL, 0,
static const QUIC_PKT_HDR rx_script_2_expect_hdr = {
QUIC_PKT_TYPE_1RTT,
- 0, 0, 3, 0, 1, 0, {0, {0}}, {0, {0}},
+ 0, 0, 3, 0, 1, 0, 0, {0, {0}}, {0, {0}},
{0x00, 0xbf, 0xf4, 0x00},
NULL, 0,
1, NULL
0, /* PN Length */
0, /* Partial */
1, /* Fixed */
+ 0, /* Unused */
0, /* Version */
{0, {0}}, /* DCID */
{12, {0x35, 0x3c, 0x1b, 0x97, 0xca, 0xf8, /* SCID */
0, /* PN Length */
0, /* Partial */
1, /* Fixed */
+ 0, /* Unused */
1, /* Version */
{0, {0}}, /* DCID */
{4, {0xad, 0x15, 0x3f, 0xae}}, /* SCID */
2, /* PN Length */
0, /* Partial */
1, /* Fixed */
+ 0, /* Unused */
1, /* Version */
{0, {0}}, /* DCID */
{4, {0x83, 0xd0, 0x0a, 0x27}}, /* SCID */
2, /* PN Length */
0, /* Partial */
1, /* Fixed */
+ 0, /* Unused */
1, /* Version */
{0, {0}}, /* DCID */
{4, {0x83, 0xd0, 0x0a, 0x27}}, /* SCID */
2, /* PN Length */
0, /* Partial */
1, /* Fixed */
+ 0, /* Unused */
0, /* Version */
{0, {0}}, /* DCID */
{0, {0}}, /* SCID */
2, /* PN Length */
0, /* Partial */
1, /* Fixed */
+ 0, /* Unused */
1, /* Version */
{0, {0}}, /* DCID */
{4, {0x36, 0xf4, 0x75, 0x2d}}, /* SCID */
2, /* PN Length */
0, /* Partial */
1, /* Fixed */
+ 0, /* Unused */
1, /* Version */
{0, {0}}, /* DCID */
{4, {0x36, 0xf4, 0x75, 0x2d}}, /* SCID */
2, /* PN Length */
0, /* Partial */
1, /* Fixed */
+ 0, /* Unused */
0, /* Version */
{0, {0}}, /* DCID */
{0, {0}}, /* SCID */
2, /* PN Length */
0, /* Partial */
1, /* Fixed */
+ 0, /* Unused */
1, /* Version */
{0, {0}}, /* DCID */
{4, {0x03, 0x45, 0x0c, 0x7a}}, /* SCID */
2, /* PN Length */
0, /* Partial */
1, /* Fixed */
+ 0, /* Unused */
1, /* Version */
{0, {0}}, /* DCID */
{4, {0x03, 0x45, 0x0c, 0x7a}}, /* SCID */
2, /* PN Length */
0, /* Partial */
1, /* Fixed */
+ 0, /* Unused */
0, /* Version */
{0, {0}}, /* DCID */
{0, {0}}, /* SCID */
2, /* PN Length */
0, /* Partial */
1, /* Fixed */
+ 0, /* Unused */
0, /* Version */
{0, {0}}, /* DCID */
{0, {0}}, /* SCID */
2, /* PN Length */
0, /* Partial */
1, /* Fixed */
+ 0, /* Unused */
0, /* Version */
{0, {0}}, /* DCID */
{0, {0}}, /* SCID */
2, /* PN Length */
0, /* Partial */
1, /* Fixed */
+ 0, /* Unused */
0, /* Version */
{0, {0}}, /* DCID */
{0, {0}}, /* SCID */
2, /* PN Length */
0, /* Partial */
1, /* Fixed */
+ 0, /* Unused */
0, /* Version */
{0, {0}}, /* DCID */
{0, {0}}, /* SCID */
2, /* PN Length */
0, /* Partial */
1, /* Fixed */
+ 0, /* Unused */
0, /* Version */
{0, {0}}, /* DCID */
{0, {0}}, /* SCID */
2, /* PN Length */
0, /* Partial */
1, /* Fixed */
+ 0, /* Unused */
0, /* Version */
{0, {0}}, /* DCID */
{0, {0}}, /* SCID */
2, /* PN length */
0, /* partial */
1, /* fixed */
+ 0, /* unused */
1, /* version */
{ 0, {0} }, /* DCID */
{ 8, {0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5 } }, /* SCID */
2, /* PN length */
0, /* partial */
1, /* fixed */
+ 0, /* unused */
1, /* version */
{ 0, {0} }, /* DCID */
{ 8, {0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5 } }, /* SCID */
2, /* PN length */
0, /* partial */
1, /* fixed */
+ 0, /* unused */
1, /* version */
{ 3, {0x70, 0x71, 0x72} }, /* DCID */
{ 8, {0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5 } }, /* SCID */
1, /* PN length */
0, /* partial */
1, /* fixed */
+ 0, /* unused */
1, /* version */
{ 3, {0x70, 0x71, 0x72} }, /* DCID */
{ 8, {0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5 } }, /* SCID */
0, /* key phase */
1, /* PN length */
0, /* partial */
- 1, /* fixed */
+ 1, /* fixed */
+ 0, /* unused */
1, /* version */
{ 3, {0x70, 0x71, 0x72} }, /* DCID */
{ 8, {0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5 } }, /* SCID */
0, /* PN length */
0, /* partial */
1, /* fixed */
+ 0, /* unused */
1, /* version */
{ 3, {0x70, 0x71, 0x72} }, /* DCID */
{ 8, {0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5 } }, /* SCID */
3, /* PN length */
0, /* partial */
1, /* fixed */
+ 0, /* unused */
0, /* version */
{ 3, {0x70, 0x71, 0x72} }, /* DCID */
{ 0, {0} }, /* SCID */
3, /* PN length */
0, /* partial */
1, /* fixed */
+ 0, /* unused */
0, /* version */
{ 3, {0x70, 0x71, 0x72} }, /* DCID */
{ 0, {0} }, /* SCID */
3, /* PN length */
0, /* partial */
1, /* fixed */
+ 0, /* unused */
0, /* version */
{ 3, {0x70, 0x71, 0x72} }, /* DCID */
{ 0, {0} }, /* SCID */
4, /* PN length */
0, /* partial */
1, /* fixed */
+ 0, /* unused */
1, /* version */
{ 3, {0x70, 0x71, 0x72} }, /* DCID */
{ 8, {0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5 } }, /* SCID */
4, /* PN length */
0, /* partial */
1, /* fixed */
+ 0, /* unused */
0, /* version */
{ 3, {0x70, 0x71, 0x72} }, /* DCID */
{ 0, {0} }, /* SCID */
0, /* PN length */
0, /* partial */
1, /* fixed */
+ 0, /* unused */
0, /* version */
{ 3, {0x70, 0x71, 0x72} }, /* DCID */
{ 2, {0x81, 0x82} }, /* SCID */
0, /* PN length */
0, /* partial */
0, /* fixed */
+ 0, /* unused */
0, /* version */
{ 3, {0x70, 0x71, 0x72} }, /* DCID */
{ 2, {0x81, 0x82} }, /* SCID */
4, /* PN length */
0, /* partial */
0, /* fixed */
+ 0, /* unused */
1, /* version */
{8, {0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08}}, /* DCID */
{ 0, {0} }, /* SCID */
2, /* PN length */
0, /* partial */
0, /* fixed */
+ 0, /* unused */
1, /* version */
{ 0, {0} }, /* DCID */
{8, {0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5}}, /* SCID */
3, /* PN length */
0, /* partial */
0, /* fixed */
+ 0, /* unused */
0, /* version */
{ 0, {0} }, /* DCID */
{ 0, {0} }, /* SCID */
2, /* PN length */
0, /* partial */
0, /* fixed */
+ 0, /* unused */
0, /* version */
{ 4, {0x6e, 0x4e, 0xbd, 0x49} }, /* DCID */
{ 0, {0} }, /* SCID */
2, /* PN length */
0, /* partial */
0, /* fixed */
+ 0, /* unused */
0, /* version */
{ 4, {0x6e, 0x4e, 0xbd, 0x49} }, /* DCID */
{ 0, {0} }, /* SCID */
2, /* PN length */
0, /* partial */
0, /* fixed */
+ 0, /* unused */
0, /* version */
{ 4, {0x6e, 0x4e, 0xbd, 0x49} }, /* DCID */
{ 0, {0} }, /* SCID */
0, /* PN length */
0, /* partial */
0, /* fixed */
+ 0, /* unused */
1, /* version */
{ 0, {0} }, /* DCID */
{ 4, {0xa9, 0x20, 0xcc, 0xc2} }, /* SCID */
0, /* PN length */
0, /* partial */
0, /* fixed */
+ 0, /* unused */
0, /* version */
{ 0, {0} }, /* DCID */
{ 12, {0x35, 0x3c, 0x1b, 0x97, 0xca, 0xf8, 0x99,
|| !TEST_int_eq(a->pn_len, b->pn_len)
|| !TEST_int_eq(a->partial, b->partial)
|| !TEST_int_eq(a->fixed, b->fixed)
+ || !TEST_int_eq(a->unused, b->unused)
|| !TEST_uint_eq(a->version, b->version)
|| !TEST_true(ossl_quic_conn_id_eq(&a->dst_conn_id, &b->dst_conn_id))
|| !TEST_true(ossl_quic_conn_id_eq(&a->src_conn_id, &b->src_conn_id))
return testresult;
}
+/* RFC 9001 s. A.4 */
+static const QUIC_CONN_ID retry_orig_dcid = {
+ 8, { 0x83, 0x94, 0xc8, 0xf0, 0x3e, 0x51, 0x57, 0x08 }
+};
+
+static const unsigned char retry_encoded[] = {
+ 0xff, /* Long Header, Retry */
+ 0x00, 0x00, 0x00, 0x01, /* Version 1 */
+ 0x00, /* DCID */
+ 0x08, 0xf0, 0x67, 0xa5, 0x50, 0x2a, 0x42, 0x62, 0xb5, /* SCID */
+
+ /* Retry Token */
+ 0x74, 0x6f, 0x6b, 0x65, 0x6e,
+
+ /* Retry Integrity Tag */
+ 0x04, 0xa2, 0x65, 0xba, 0x2e, 0xff, 0x4d, 0x82, 0x90, 0x58, 0xfb, 0x3f, 0x0f,
+ 0x24, 0x96, 0xba
+};
+
+static int test_wire_retry_integrity_tag(void)
+{
+ int testresult = 0;
+ PACKET pkt = {0};
+ QUIC_PKT_HDR hdr = {0};
+ unsigned char got_tag[QUIC_RETRY_INTEGRITY_TAG_LEN] = {0};
+
+ if (!TEST_true(PACKET_buf_init(&pkt, retry_encoded, sizeof(retry_encoded))))
+ goto err;
+
+ if (!TEST_true(ossl_quic_wire_decode_pkt_hdr(&pkt, 0, 0, &hdr, NULL)))
+ goto err;
+
+ if (!TEST_int_eq(hdr.type, QUIC_PKT_TYPE_RETRY))
+ goto err;
+
+ if (!TEST_true(ossl_quic_calculate_retry_integrity_tag(NULL, NULL, &hdr,
+ &retry_orig_dcid,
+ got_tag)))
+ goto err;
+
+ if (!TEST_mem_eq(got_tag, sizeof(got_tag),
+ retry_encoded + sizeof(retry_encoded)
+ - QUIC_RETRY_INTEGRITY_TAG_LEN,
+ QUIC_RETRY_INTEGRITY_TAG_LEN))
+ goto err;
+
+ if (!TEST_true(ossl_quic_validate_retry_integrity_tag(NULL, NULL, &hdr,
+ &retry_orig_dcid)))
+ goto err;
+
+ testresult = 1;
+err:
+ return testresult;
+}
+
int setup_tests(void)
{
ADD_ALL_TESTS(test_wire_encode, OSSL_NELEM(encode_cases));
ADD_ALL_TESTS(test_wire_ack, OSSL_NELEM(ack_cases));
ADD_ALL_TESTS(test_wire_pkt_hdr_pn, OSSL_NELEM(pn_tests));
+ ADD_TEST(test_wire_retry_integrity_tag);
return 1;
}