mirror of
https://github.com/openssl/openssl.git
synced 2024-12-15 06:01:37 +08:00
70d45893d0
This adds support for calculating and verifying retry integrity tags. In order to support this, an 'unused' field is added to the QUIC packet header structure so we can ensure that the serialization of the header is bit-for-bit identical to what was decoded. Reviewed-by: Tomas Mraz <tomas@openssl.org> Reviewed-by: Matt Caswell <matt@openssl.org> (Merged from https://github.com/openssl/openssl/pull/19703)
876 lines
28 KiB
C
876 lines
28 KiB
C
/*
|
|
* Copyright 2022 The OpenSSL Project Authors. All Rights Reserved.
|
|
*
|
|
* Licensed under the Apache License 2.0 (the "License"). You may not use
|
|
* this file except in compliance with the License. You can obtain a copy
|
|
* in the file LICENSE in the source distribution or at
|
|
* https://www.openssl.org/source/license.html
|
|
*/
|
|
|
|
#include "internal/quic_wire_pkt.h"
|
|
|
|
int ossl_quic_hdr_protector_init(QUIC_HDR_PROTECTOR *hpr,
|
|
OSSL_LIB_CTX *libctx,
|
|
const char *propq,
|
|
uint32_t cipher_id,
|
|
const unsigned char *quic_hp_key,
|
|
size_t quic_hp_key_len)
|
|
{
|
|
const char *cipher_name = NULL;
|
|
|
|
switch (cipher_id) {
|
|
case QUIC_HDR_PROT_CIPHER_AES_128:
|
|
cipher_name = "AES-128-ECB";
|
|
break;
|
|
case QUIC_HDR_PROT_CIPHER_AES_256:
|
|
cipher_name = "AES-256-ECB";
|
|
break;
|
|
case QUIC_HDR_PROT_CIPHER_CHACHA:
|
|
cipher_name = "ChaCha20";
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
hpr->cipher_ctx = EVP_CIPHER_CTX_new();
|
|
if (hpr->cipher_ctx == NULL)
|
|
return 0;
|
|
|
|
hpr->cipher = EVP_CIPHER_fetch(libctx, cipher_name, propq);
|
|
if (hpr->cipher == NULL
|
|
|| quic_hp_key_len != (size_t)EVP_CIPHER_get_key_length(hpr->cipher))
|
|
goto err;
|
|
|
|
if (!EVP_CipherInit_ex(hpr->cipher_ctx, hpr->cipher, NULL,
|
|
quic_hp_key, NULL, 1))
|
|
goto err;
|
|
|
|
hpr->libctx = libctx;
|
|
hpr->propq = propq;
|
|
hpr->cipher_id = cipher_id;
|
|
return 1;
|
|
|
|
err:
|
|
ossl_quic_hdr_protector_cleanup(hpr);
|
|
return 0;
|
|
}
|
|
|
|
void ossl_quic_hdr_protector_cleanup(QUIC_HDR_PROTECTOR *hpr)
|
|
{
|
|
EVP_CIPHER_CTX_free(hpr->cipher_ctx);
|
|
hpr->cipher_ctx = NULL;
|
|
|
|
EVP_CIPHER_free(hpr->cipher);
|
|
hpr->cipher = NULL;
|
|
}
|
|
|
|
static int hdr_generate_mask(QUIC_HDR_PROTECTOR *hpr,
|
|
const unsigned char *sample, size_t sample_len,
|
|
unsigned char *mask)
|
|
{
|
|
int l = 0;
|
|
unsigned char dst[16];
|
|
static const unsigned char zeroes[5] = {0};
|
|
size_t i;
|
|
|
|
if (hpr->cipher_id == QUIC_HDR_PROT_CIPHER_AES_128
|
|
|| hpr->cipher_id == QUIC_HDR_PROT_CIPHER_AES_256) {
|
|
if (sample_len < 16)
|
|
return 0;
|
|
|
|
if (!EVP_CipherInit_ex(hpr->cipher_ctx, NULL, NULL, NULL, NULL, 1)
|
|
|| !EVP_CipherUpdate(hpr->cipher_ctx, dst, &l, sample, 16))
|
|
return 0;
|
|
|
|
for (i = 0; i < 5; ++i)
|
|
mask[i] = dst[i];
|
|
} else if (hpr->cipher_id == QUIC_HDR_PROT_CIPHER_CHACHA) {
|
|
if (sample_len < 16)
|
|
return 0;
|
|
|
|
if (!EVP_CipherInit_ex(hpr->cipher_ctx, NULL, NULL, NULL, sample, 1)
|
|
|| !EVP_CipherUpdate(hpr->cipher_ctx, mask, &l,
|
|
zeroes, sizeof(zeroes)))
|
|
return 0;
|
|
} else {
|
|
assert(0);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int ossl_quic_hdr_protector_decrypt(QUIC_HDR_PROTECTOR *hpr,
|
|
QUIC_PKT_HDR_PTRS *ptrs)
|
|
{
|
|
return ossl_quic_hdr_protector_decrypt_fields(hpr,
|
|
ptrs->raw_sample,
|
|
ptrs->raw_sample_len,
|
|
ptrs->raw_start,
|
|
ptrs->raw_pn);
|
|
}
|
|
|
|
int ossl_quic_hdr_protector_decrypt_fields(QUIC_HDR_PROTECTOR *hpr,
|
|
const unsigned char *sample,
|
|
size_t sample_len,
|
|
unsigned char *first_byte,
|
|
unsigned char *pn_bytes)
|
|
{
|
|
unsigned char mask[5], pn_len, i;
|
|
|
|
if (!hdr_generate_mask(hpr, sample, sample_len, mask))
|
|
return 0;
|
|
|
|
*first_byte ^= mask[0] & ((*first_byte & 0x80) != 0 ? 0xf : 0x1f);
|
|
pn_len = (*first_byte & 0x3) + 1;
|
|
|
|
for (i = 0; i < pn_len; ++i)
|
|
pn_bytes[i] ^= mask[i + 1];
|
|
|
|
return 1;
|
|
}
|
|
|
|
int ossl_quic_hdr_protector_encrypt(QUIC_HDR_PROTECTOR *hpr,
|
|
QUIC_PKT_HDR_PTRS *ptrs)
|
|
{
|
|
return ossl_quic_hdr_protector_encrypt_fields(hpr,
|
|
ptrs->raw_sample,
|
|
ptrs->raw_sample_len,
|
|
ptrs->raw_start,
|
|
ptrs->raw_pn);
|
|
}
|
|
|
|
int ossl_quic_hdr_protector_encrypt_fields(QUIC_HDR_PROTECTOR *hpr,
|
|
const unsigned char *sample,
|
|
size_t sample_len,
|
|
unsigned char *first_byte,
|
|
unsigned char *pn_bytes)
|
|
{
|
|
unsigned char mask[5], pn_len, i;
|
|
|
|
if (!hdr_generate_mask(hpr, sample, sample_len, mask))
|
|
return 0;
|
|
|
|
pn_len = (*first_byte & 0x3) + 1;
|
|
for (i = 0; i < pn_len; ++i)
|
|
pn_bytes[i] ^= mask[i + 1];
|
|
|
|
*first_byte ^= mask[0] & ((*first_byte & 0x80) != 0 ? 0xf : 0x1f);
|
|
return 1;
|
|
}
|
|
|
|
int ossl_quic_wire_decode_pkt_hdr(PACKET *pkt,
|
|
size_t short_conn_id_len,
|
|
int partial,
|
|
QUIC_PKT_HDR *hdr,
|
|
QUIC_PKT_HDR_PTRS *ptrs)
|
|
{
|
|
unsigned int b0;
|
|
unsigned char *pn = NULL;
|
|
size_t l = PACKET_remaining(pkt);
|
|
|
|
if (ptrs != NULL) {
|
|
ptrs->raw_start = (unsigned char *)PACKET_data(pkt);
|
|
ptrs->raw_sample = NULL;
|
|
ptrs->raw_sample_len = 0;
|
|
ptrs->raw_pn = NULL;
|
|
}
|
|
|
|
if (l < QUIC_MIN_VALID_PKT_LEN
|
|
|| !PACKET_get_1(pkt, &b0))
|
|
return 0;
|
|
|
|
hdr->partial = partial;
|
|
hdr->unused = 0;
|
|
|
|
if ((b0 & 0x80) == 0) {
|
|
/* Short header. */
|
|
if (short_conn_id_len > QUIC_MAX_CONN_ID_LEN)
|
|
return 0;
|
|
|
|
if ((b0 & 0x40) == 0 /* fixed bit not set? */
|
|
|| l < QUIC_MIN_VALID_PKT_LEN_CRYPTO)
|
|
return 0;
|
|
|
|
hdr->type = QUIC_PKT_TYPE_1RTT;
|
|
hdr->fixed = 1;
|
|
hdr->spin_bit = (b0 & 0x20) != 0;
|
|
if (partial) {
|
|
hdr->key_phase = 0; /* protected, zero for now */
|
|
hdr->pn_len = 0; /* protected, zero for now */
|
|
} else {
|
|
hdr->key_phase = (b0 & 0x4) != 0;
|
|
hdr->pn_len = (b0 & 0x3) + 1;
|
|
}
|
|
|
|
/* Copy destination connection ID field to header structure. */
|
|
if (!PACKET_copy_bytes(pkt, hdr->dst_conn_id.id, short_conn_id_len))
|
|
return 0;
|
|
|
|
hdr->dst_conn_id.id_len = (unsigned char)short_conn_id_len;
|
|
|
|
/*
|
|
* Skip over the PN. If this is a partial decode, the PN length field
|
|
* currently has header protection applied. Thus we do not know the
|
|
* length of the PN but we are allowed to assume it is 4 bytes long at
|
|
* this stage.
|
|
*/
|
|
memset(hdr->pn, 0, sizeof(hdr->pn));
|
|
pn = (unsigned char *)PACKET_data(pkt);
|
|
if (partial) {
|
|
if (!PACKET_forward(pkt, sizeof(hdr->pn)))
|
|
return 0;
|
|
} else {
|
|
if (!PACKET_copy_bytes(pkt, hdr->pn, hdr->pn_len))
|
|
return 0;
|
|
}
|
|
|
|
/* Fields not used in short-header packets. */
|
|
hdr->version = 0;
|
|
hdr->src_conn_id.id_len = 0;
|
|
hdr->token = NULL;
|
|
hdr->token_len = 0;
|
|
|
|
/*
|
|
* Short-header packets always come last in a datagram, the length
|
|
* is the remainder of the buffer.
|
|
*/
|
|
hdr->len = PACKET_remaining(pkt);
|
|
hdr->data = PACKET_data(pkt);
|
|
|
|
/*
|
|
* Skip over payload. Since this is a short header packet, which cannot
|
|
* be followed by any other kind of packet, this advances us to the end
|
|
* of the datagram.
|
|
*/
|
|
if (!PACKET_forward(pkt, hdr->len))
|
|
return 0;
|
|
} else {
|
|
/* Long header. */
|
|
unsigned long version;
|
|
unsigned int dst_conn_id_len, src_conn_id_len, raw_type;
|
|
|
|
if (!PACKET_get_net_4(pkt, &version))
|
|
return 0;
|
|
|
|
/*
|
|
* All QUIC packets must have the fixed bit set, except exceptionally
|
|
* for Version Negotiation packets.
|
|
*/
|
|
if (version != 0 && (b0 & 0x40) == 0)
|
|
return 0;
|
|
|
|
if (!PACKET_get_1(pkt, &dst_conn_id_len)
|
|
|| dst_conn_id_len > QUIC_MAX_CONN_ID_LEN
|
|
|| !PACKET_copy_bytes(pkt, hdr->dst_conn_id.id, dst_conn_id_len)
|
|
|| !PACKET_get_1(pkt, &src_conn_id_len)
|
|
|| src_conn_id_len > QUIC_MAX_CONN_ID_LEN
|
|
|| !PACKET_copy_bytes(pkt, hdr->src_conn_id.id, src_conn_id_len))
|
|
return 0;
|
|
|
|
hdr->version = (uint32_t)version;
|
|
hdr->dst_conn_id.id_len = (unsigned char)dst_conn_id_len;
|
|
hdr->src_conn_id.id_len = (unsigned char)src_conn_id_len;
|
|
|
|
if (version == 0) {
|
|
/*
|
|
* Version negotiation packet. Version negotiation packets are
|
|
* identified by a version field of 0 and the type bits in the first
|
|
* byte are ignored (they may take any value, and we ignore them).
|
|
*/
|
|
hdr->type = QUIC_PKT_TYPE_VERSION_NEG;
|
|
hdr->fixed = (b0 & 0x40) != 0;
|
|
|
|
hdr->data = PACKET_data(pkt);
|
|
hdr->len = PACKET_remaining(pkt);
|
|
|
|
/* Version negotiation packets are always fully decoded. */
|
|
hdr->partial = 0;
|
|
|
|
/* Fields not used in version negotiation packets. */
|
|
hdr->pn_len = 0;
|
|
hdr->spin_bit = 0;
|
|
hdr->key_phase = 0;
|
|
hdr->token = NULL;
|
|
hdr->token_len = 0;
|
|
memset(hdr->pn, 0, sizeof(hdr->pn));
|
|
|
|
if (!PACKET_forward(pkt, hdr->len))
|
|
return 0;
|
|
} else if (version != QUIC_VERSION_1) {
|
|
/* Unknown version, do not decode. */
|
|
return 0;
|
|
} else {
|
|
if (l < QUIC_MIN_VALID_PKT_LEN_CRYPTO)
|
|
return 0;
|
|
|
|
/* Get long packet type and decode to QUIC_PKT_TYPE_*. */
|
|
raw_type = ((b0 >> 4) & 0x3);
|
|
|
|
switch (raw_type) {
|
|
case 0:
|
|
hdr->type = QUIC_PKT_TYPE_INITIAL;
|
|
break;
|
|
case 1:
|
|
hdr->type = QUIC_PKT_TYPE_0RTT;
|
|
break;
|
|
case 2:
|
|
hdr->type = QUIC_PKT_TYPE_HANDSHAKE;
|
|
break;
|
|
case 3:
|
|
hdr->type = QUIC_PKT_TYPE_RETRY;
|
|
break;
|
|
}
|
|
|
|
hdr->pn_len = 0;
|
|
hdr->fixed = 1;
|
|
|
|
/* Fields not used in long-header packets. */
|
|
hdr->spin_bit = 0;
|
|
hdr->key_phase = 0;
|
|
|
|
if (hdr->type == QUIC_PKT_TYPE_INITIAL) {
|
|
/* Initial packet. */
|
|
uint64_t token_len;
|
|
|
|
if (!PACKET_get_quic_vlint(pkt, &token_len)
|
|
|| token_len > SIZE_MAX
|
|
|| !PACKET_get_bytes(pkt, &hdr->token, (size_t)token_len))
|
|
return 0;
|
|
|
|
hdr->token_len = (size_t)token_len;
|
|
if (token_len == 0)
|
|
hdr->token = NULL;
|
|
} else {
|
|
hdr->token = NULL;
|
|
hdr->token_len = 0;
|
|
}
|
|
|
|
if (hdr->type == QUIC_PKT_TYPE_RETRY) {
|
|
/* Retry packet. */
|
|
hdr->data = PACKET_data(pkt);
|
|
hdr->len = PACKET_remaining(pkt);
|
|
|
|
/* 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));
|
|
|
|
if (!PACKET_forward(pkt, hdr->len))
|
|
return 0;
|
|
} else {
|
|
/* Initial, 0-RTT or Handshake packet. */
|
|
uint64_t len;
|
|
|
|
hdr->pn_len = partial ? 0 : (b0 & 3) + 1;
|
|
|
|
if (!PACKET_get_quic_vlint(pkt, &len)
|
|
|| len < sizeof(hdr->pn)
|
|
|| len > PACKET_remaining(pkt))
|
|
return 0;
|
|
|
|
/*
|
|
* Skip over the PN. If this is a partial decode, the PN length
|
|
* field currently has header protection applied. Thus we do not
|
|
* know the length of the PN but we are allowed to assume it is
|
|
* 4 bytes long at this stage.
|
|
*/
|
|
pn = (unsigned char *)PACKET_data(pkt);
|
|
memset(hdr->pn, 0, sizeof(hdr->pn));
|
|
if (partial) {
|
|
if (!PACKET_forward(pkt, sizeof(hdr->pn)))
|
|
return 0;
|
|
|
|
hdr->len = (size_t)(len - sizeof(hdr->pn));
|
|
} else {
|
|
if (!PACKET_copy_bytes(pkt, hdr->pn, hdr->pn_len))
|
|
return 0;
|
|
|
|
hdr->len = (size_t)(len - hdr->pn_len);
|
|
}
|
|
|
|
hdr->data = PACKET_data(pkt);
|
|
|
|
/* Skip over packet body. */
|
|
if (!PACKET_forward(pkt, hdr->len))
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ptrs != NULL) {
|
|
ptrs->raw_pn = pn;
|
|
if (pn != NULL) {
|
|
ptrs->raw_sample = pn + 4;
|
|
ptrs->raw_sample_len = PACKET_end(pkt) - ptrs->raw_sample;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int ossl_quic_wire_encode_pkt_hdr(WPACKET *pkt,
|
|
size_t short_conn_id_len,
|
|
const QUIC_PKT_HDR *hdr,
|
|
QUIC_PKT_HDR_PTRS *ptrs)
|
|
{
|
|
unsigned char b0;
|
|
size_t off_start, off_sample, off_pn;
|
|
unsigned char *start = WPACKET_get_curr(pkt);
|
|
|
|
if (!WPACKET_get_total_written(pkt, &off_start))
|
|
return 0;
|
|
|
|
if (ptrs != NULL) {
|
|
ptrs->raw_start = NULL;
|
|
ptrs->raw_sample = NULL;
|
|
ptrs->raw_sample_len = 0;
|
|
ptrs->raw_pn = 0;
|
|
}
|
|
|
|
/* Cannot serialize a partial header, or one whose DCID length is wrong. */
|
|
if (hdr->partial
|
|
|| (hdr->type == QUIC_PKT_TYPE_1RTT
|
|
&& hdr->dst_conn_id.id_len != short_conn_id_len))
|
|
return 0;
|
|
|
|
if (hdr->type == QUIC_PKT_TYPE_1RTT) {
|
|
/* Short header. */
|
|
|
|
/*
|
|
* Cannot serialize a header whose DCID length is wrong, or with an
|
|
* invalid PN length.
|
|
*/
|
|
if (hdr->dst_conn_id.id_len != short_conn_id_len
|
|
|| short_conn_id_len > QUIC_MAX_CONN_ID_LEN
|
|
|| hdr->pn_len < 1 || hdr->pn_len > 4)
|
|
return 0;
|
|
|
|
b0 = (hdr->spin_bit << 5)
|
|
| (hdr->key_phase << 2)
|
|
| (hdr->pn_len - 1)
|
|
| 0x40; /* fixed bit */
|
|
|
|
if (!WPACKET_put_bytes_u8(pkt, b0)
|
|
|| !WPACKET_memcpy(pkt, hdr->dst_conn_id.id, short_conn_id_len)
|
|
|| !WPACKET_get_total_written(pkt, &off_pn)
|
|
|| !WPACKET_memcpy(pkt, hdr->pn, hdr->pn_len))
|
|
return 0;
|
|
} else {
|
|
/* Long header. */
|
|
unsigned int raw_type;
|
|
|
|
if (hdr->dst_conn_id.id_len > QUIC_MAX_CONN_ID_LEN
|
|
|| hdr->src_conn_id.id_len > QUIC_MAX_CONN_ID_LEN)
|
|
return 0;
|
|
|
|
if (ossl_quic_pkt_type_has_pn(hdr->type)
|
|
&& (hdr->pn_len < 1 || hdr->pn_len > 4))
|
|
return 0;
|
|
|
|
switch (hdr->type) {
|
|
case QUIC_PKT_TYPE_VERSION_NEG:
|
|
if (hdr->version != 0)
|
|
return 0;
|
|
|
|
/* Version negotiation packets use zero for the type bits */
|
|
raw_type = 0;
|
|
break;
|
|
|
|
case QUIC_PKT_TYPE_INITIAL: raw_type = 0; break;
|
|
case QUIC_PKT_TYPE_0RTT: raw_type = 1; break;
|
|
case QUIC_PKT_TYPE_HANDSHAKE: raw_type = 2; break;
|
|
case QUIC_PKT_TYPE_RETRY: raw_type = 3; break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
b0 = (raw_type << 4) | 0x80; /* long */
|
|
if (hdr->type != QUIC_PKT_TYPE_VERSION_NEG || hdr->fixed)
|
|
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)
|
|
|| !WPACKET_put_bytes_u8(pkt, hdr->dst_conn_id.id_len)
|
|
|| !WPACKET_memcpy(pkt, hdr->dst_conn_id.id,
|
|
hdr->dst_conn_id.id_len)
|
|
|| !WPACKET_put_bytes_u8(pkt, hdr->src_conn_id.id_len)
|
|
|| !WPACKET_memcpy(pkt, hdr->src_conn_id.id,
|
|
hdr->src_conn_id.id_len))
|
|
return 0;
|
|
|
|
if (hdr->type == QUIC_PKT_TYPE_VERSION_NEG
|
|
|| hdr->type == QUIC_PKT_TYPE_RETRY) {
|
|
if (hdr->len > 0 && !WPACKET_reserve_bytes(pkt, hdr->len, NULL))
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
if (hdr->type == QUIC_PKT_TYPE_INITIAL) {
|
|
if (!WPACKET_quic_write_vlint(pkt, hdr->token_len)
|
|
|| !WPACKET_memcpy(pkt, hdr->token, hdr->token_len))
|
|
return 0;
|
|
}
|
|
|
|
if (!WPACKET_quic_write_vlint(pkt, hdr->len + hdr->pn_len)
|
|
|| !WPACKET_get_total_written(pkt, &off_pn)
|
|
|| !WPACKET_memcpy(pkt, hdr->pn, hdr->pn_len))
|
|
return 0;
|
|
}
|
|
|
|
if (hdr->len > 0 && !WPACKET_reserve_bytes(pkt, hdr->len, NULL))
|
|
return 0;
|
|
|
|
off_sample = off_pn + 4;
|
|
|
|
if (ptrs != NULL) {
|
|
ptrs->raw_start = start;
|
|
ptrs->raw_sample = start + (off_sample - off_start);
|
|
ptrs->raw_sample_len
|
|
= WPACKET_get_curr(pkt) + hdr->len - ptrs->raw_sample;
|
|
ptrs->raw_pn = start + (off_pn - off_start);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int ossl_quic_wire_get_encoded_pkt_hdr_len(size_t short_conn_id_len,
|
|
const QUIC_PKT_HDR *hdr)
|
|
{
|
|
size_t len = 0, enclen;
|
|
|
|
/* Cannot serialize a partial header, or one whose DCID length is wrong. */
|
|
if (hdr->partial
|
|
|| (hdr->type == QUIC_PKT_TYPE_1RTT
|
|
&& hdr->dst_conn_id.id_len != short_conn_id_len))
|
|
return 0;
|
|
|
|
if (hdr->type == QUIC_PKT_TYPE_1RTT) {
|
|
/* Short header. */
|
|
|
|
/*
|
|
* Cannot serialize a header whose DCID length is wrong, or with an
|
|
* invalid PN length.
|
|
*/
|
|
if (hdr->dst_conn_id.id_len != short_conn_id_len
|
|
|| short_conn_id_len > QUIC_MAX_CONN_ID_LEN
|
|
|| hdr->pn_len < 1 || hdr->pn_len > 4)
|
|
return 0;
|
|
|
|
return 1 + short_conn_id_len + hdr->pn_len;
|
|
} else {
|
|
/* Long header. */
|
|
if (hdr->dst_conn_id.id_len > QUIC_MAX_CONN_ID_LEN
|
|
|| hdr->src_conn_id.id_len > QUIC_MAX_CONN_ID_LEN)
|
|
return 0;
|
|
|
|
len += 1 /* Initial byte */ + 4 /* Version */
|
|
+ 1 + hdr->dst_conn_id.id_len /* DCID Len, DCID */
|
|
+ 1 + hdr->src_conn_id.id_len /* SCID Len, SCID */
|
|
;
|
|
|
|
if (ossl_quic_pkt_type_has_pn(hdr->type)) {
|
|
if (hdr->pn_len < 1 || hdr->pn_len > 4)
|
|
return 0;
|
|
|
|
len += hdr->pn_len;
|
|
}
|
|
|
|
if (hdr->type == QUIC_PKT_TYPE_INITIAL) {
|
|
enclen = ossl_quic_vlint_encode_len(hdr->token_len);
|
|
if (!enclen)
|
|
return 0;
|
|
|
|
len += enclen + hdr->token_len;
|
|
}
|
|
|
|
if (!ossl_quic_pkt_type_must_be_last(hdr->type)) {
|
|
enclen = ossl_quic_vlint_encode_len(hdr->len + hdr->pn_len);
|
|
if (!enclen)
|
|
return 0;
|
|
|
|
len += enclen;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
}
|
|
|
|
int ossl_quic_wire_get_pkt_hdr_dst_conn_id(const unsigned char *buf,
|
|
size_t buf_len,
|
|
size_t short_conn_id_len,
|
|
QUIC_CONN_ID *dst_conn_id)
|
|
{
|
|
unsigned char b0;
|
|
size_t blen;
|
|
|
|
if (buf_len < QUIC_MIN_VALID_PKT_LEN
|
|
|| short_conn_id_len > QUIC_MAX_CONN_ID_LEN)
|
|
return 0;
|
|
|
|
b0 = buf[0];
|
|
if ((b0 & 0x80) != 0) {
|
|
/*
|
|
* Long header. We need 6 bytes (initial byte, 4 version bytes, DCID
|
|
* length byte to begin with). This is covered by the buf_len test
|
|
* above.
|
|
*/
|
|
|
|
/*
|
|
* If the version field is non-zero (meaning that this is not a Version
|
|
* Negotiation packet), the fixed bit must be set.
|
|
*/
|
|
if ((buf[1] || buf[2] || buf[3] || buf[4]) && (b0 & 0x40) == 0)
|
|
return 0;
|
|
|
|
blen = (size_t)buf[5]; /* DCID Length */
|
|
if (blen > QUIC_MAX_CONN_ID_LEN
|
|
|| buf_len < QUIC_MIN_VALID_PKT_LEN + blen)
|
|
return 0;
|
|
|
|
dst_conn_id->id_len = (unsigned char)blen;
|
|
memcpy(dst_conn_id->id, buf + 6, blen);
|
|
return 1;
|
|
} else {
|
|
/* Short header. */
|
|
if ((b0 & 0x40) == 0)
|
|
/* Fixed bit not set, not a valid QUIC packet header. */
|
|
return 0;
|
|
|
|
if (buf_len < QUIC_MIN_VALID_PKT_LEN_CRYPTO + short_conn_id_len)
|
|
return 0;
|
|
|
|
dst_conn_id->id_len = (unsigned char)short_conn_id_len;
|
|
memcpy(dst_conn_id->id, buf + 1, short_conn_id_len);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
int ossl_quic_wire_decode_pkt_hdr_pn(const unsigned char *enc_pn,
|
|
size_t enc_pn_len,
|
|
QUIC_PN largest_pn,
|
|
QUIC_PN *res_pn)
|
|
{
|
|
int64_t expected_pn, truncated_pn, candidate_pn, pn_win, pn_hwin, pn_mask;
|
|
|
|
switch (enc_pn_len) {
|
|
case 1:
|
|
truncated_pn = enc_pn[0];
|
|
break;
|
|
case 2:
|
|
truncated_pn = ((QUIC_PN)enc_pn[0] << 8)
|
|
| (QUIC_PN)enc_pn[1];
|
|
break;
|
|
case 3:
|
|
truncated_pn = ((QUIC_PN)enc_pn[0] << 16)
|
|
| ((QUIC_PN)enc_pn[1] << 8)
|
|
| (QUIC_PN)enc_pn[2];
|
|
break;
|
|
case 4:
|
|
truncated_pn = ((QUIC_PN)enc_pn[0] << 24)
|
|
| ((QUIC_PN)enc_pn[1] << 16)
|
|
| ((QUIC_PN)enc_pn[2] << 8)
|
|
| (QUIC_PN)enc_pn[3];
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
/* Implemented as per RFC 9000 Section A.3. */
|
|
expected_pn = largest_pn + 1;
|
|
pn_win = ((int64_t)1) << (enc_pn_len * 8);
|
|
pn_hwin = pn_win / 2;
|
|
pn_mask = pn_win - 1;
|
|
candidate_pn = (expected_pn & ~pn_mask) | truncated_pn;
|
|
if (candidate_pn <= expected_pn - pn_hwin
|
|
&& candidate_pn < (((int64_t)1) << 62) - pn_win)
|
|
*res_pn = candidate_pn + pn_win;
|
|
else if (candidate_pn > expected_pn + pn_hwin
|
|
&& candidate_pn >= pn_win)
|
|
*res_pn = candidate_pn - pn_win;
|
|
else
|
|
*res_pn = candidate_pn;
|
|
return 1;
|
|
}
|
|
|
|
/* From RFC 9000 Section A.2. Simplified implementation. */
|
|
int ossl_quic_wire_determine_pn_len(QUIC_PN pn,
|
|
QUIC_PN largest_acked)
|
|
{
|
|
uint64_t num_unacked
|
|
= (largest_acked == QUIC_PN_INVALID) ? pn + 1 : pn - largest_acked;
|
|
|
|
/*
|
|
* num_unacked \in [ 0, 2** 7] -> 1 byte
|
|
* num_unacked \in (2** 7, 2**15] -> 2 bytes
|
|
* num_unacked \in (2**15, 2**23] -> 3 bytes
|
|
* num_unacked \in (2**23, ] -> 4 bytes
|
|
*/
|
|
|
|
if (num_unacked <= (1U<<7)) return 1;
|
|
if (num_unacked <= (1U<<15)) return 2;
|
|
if (num_unacked <= (1U<<23)) return 3;
|
|
return 4;
|
|
}
|
|
|
|
int ossl_quic_wire_encode_pkt_hdr_pn(QUIC_PN pn,
|
|
unsigned char *enc_pn,
|
|
size_t enc_pn_len)
|
|
{
|
|
switch (enc_pn_len) {
|
|
case 1:
|
|
enc_pn[0] = (unsigned char)pn;
|
|
break;
|
|
case 2:
|
|
enc_pn[1] = (unsigned char)pn;
|
|
enc_pn[0] = (unsigned char)(pn >> 8);
|
|
break;
|
|
case 3:
|
|
enc_pn[2] = (unsigned char)pn;
|
|
enc_pn[1] = (unsigned char)(pn >> 8);
|
|
enc_pn[0] = (unsigned char)(pn >> 16);
|
|
break;
|
|
case 4:
|
|
enc_pn[3] = (unsigned char)pn;
|
|
enc_pn[2] = (unsigned char)(pn >> 8);
|
|
enc_pn[1] = (unsigned char)(pn >> 16);
|
|
enc_pn[0] = (unsigned char)(pn >> 24);
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
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;
|
|
}
|