// QUIC Retry packet integrity (RFC 9001 §5.8). A Retry carries a 16-byte tag that
// binds it to the original Destination Connection ID, computed with AEAD_AES_128_GCM
// under a version-fixed key and nonce over a pseudo-packet — the same GCM the payload
// AEAD uses, here with an empty plaintext so the output is the tag alone.
///|
/// The version-1 Retry integrity key and nonce (RFC 9001 §5.8).
let quic_v1_retry_key : Bytes = b"\xbe\x0c\x69\x0b\x9f\x66\x57\x5a\x1d\x76\x6b\x54\xe3\x68\xc8\x4e"
///|
let quic_v1_retry_nonce : Bytes = b"\x46\x15\x99\xd3\x5d\x63\x2b\xf2\x23\x98\x25\xbb"
///|
/// The Retry integrity tag for a Retry packet (everything up to but not including the
/// tag) given the original Destination Connection ID. The AEAD associated data is the
/// Retry Pseudo-Packet: the ODCID length byte, the ODCID, then the Retry packet.
pub fn quic_retry_integrity_tag(
retry_without_tag : Bytes,
odcid : Bytes,
) -> Bytes {
let pseudo = Buffer()
pseudo.write_byte(odcid.length().to_byte())
pseudo.write_bytes(odcid[:])
pseudo.write_bytes(retry_without_tag[:])
// Empty plaintext: seal returns ciphertext||tag with no ciphertext, i.e. the tag.
aes128_gcm_seal(
quic_v1_retry_key,
quic_v1_retry_nonce,
b"",
pseudo.to_bytes(),
)
}
///|
/// Verify a full Retry packet (ending in its 16-byte integrity tag): recompute the
/// tag over the leading bytes and compare, accumulating the difference so the check
/// does not exit early on the first mismatched byte.
pub fn quic_retry_verify(retry_packet : Bytes, odcid : Bytes) -> Bool {
if retry_packet.length() < 16 {
return false
}
let body_len = retry_packet.length() - 16
let body = retry_packet[0:body_len].to_owned()
let tag = quic_retry_integrity_tag(body, odcid)
let mut diff = 0
for i = 0; i < 16; i = i + 1 {
diff = diff | (tag[i].to_int() ^ retry_packet[body_len + i].to_int())
}
diff == 0
}