/* * 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, int nodata, 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)) return 0; if (!nodata && 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); } if (nodata) { hdr->data = NULL; } else { 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. */ /* TODO(QUIC): Cipher fetch caching. */ 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; }