// The TLS 1.3 Certificate message (RFC 8446 §4.4.2): after ServerHello and EncryptedExtensions
// the server sends its certificate chain so the peer can bind the handshake to an identity it
// trusts. The body is a certificate_request_context (empty when the server sends its
// certificate unprompted) followed by a certificate_list — each entry a DER-encoded
// certificate and its (usually empty) extensions. mooncat carries the DER opaque here; the
// CertificateVerify over this chain is a real ES256 signature (tls13_certificate_verify_*).
// This is the wire structure a QUIC/HTTP3 server's Handshake flight puts on the wire — the
// shape curl parses to authenticate the server.
///|
fn cert_write_u24(buf : Buffer, v : Int) -> Unit {
buf.write_byte(((v >> 16) & 0xff).to_byte())
buf.write_byte(((v >> 8) & 0xff).to_byte())
buf.write_byte((v & 0xff).to_byte())
}
///|
fn cert_read_u24(view : BytesView, off : Int) -> Int {
(view[off].to_int() << 16) |
(view[off + 1].to_int() << 8) |
view[off + 2].to_int()
}
///|
/// Encode a Certificate message body (RFC 8446 §4.4.2): the certificate_request_context (empty
/// for a server certificate sent without a CertificateRequest), then the certificate_list —
/// each entry a three-byte-length DER certificate and a two-byte-length extensions block
/// (empty here).
pub fn tls13_encode_certificate(
certificate_request_context : Bytes,
certs : Array[Bytes],
) -> Bytes {
let list = Buffer()
for cert in certs {
cert_write_u24(list, cert.length())
list.write_bytes(cert[:])
list.write_byte(0)
list.write_byte(0)
}
let list_bytes = list.to_bytes()
let buf = Buffer()
buf.write_byte((certificate_request_context.length() & 0xff).to_byte())
buf.write_bytes(certificate_request_context[:])
cert_write_u24(buf, list_bytes.length())
buf.write_bytes(list_bytes[:])
buf.to_bytes()
}
///|
/// Decode a Certificate message body into its certificate_request_context and the list of DER
/// certificates (RFC 8446 §4.4.2), skipping per-entry extensions. `None` on a truncated message.
pub fn tls13_decode_certificate(body : BytesView) -> (Bytes, Array[Bytes])? {
if body.length() < 1 {
return None
}
let ctx_len = body[0].to_int()
let mut off = 1
if body.length() < off + ctx_len {
return None
}
let context = body[off:off + ctx_len].to_owned()
off = off + ctx_len
if body.length() < off + 3 {
return None
}
let list_len = cert_read_u24(body, off)
off = off + 3
let end = off + list_len
if body.length() < end {
return None
}
let certs : Array[Bytes] = []
while off < end {
if end < off + 3 {
return None
}
let cert_len = cert_read_u24(body, off)
off = off + 3
if end < off + cert_len {
return None
}
certs.push(body[off:off + cert_len].to_owned())
off = off + cert_len
if end < off + 2 {
return None
}
let ext_len = (body[off].to_int() << 8) | body[off + 1].to_int()
off = off + 2
if end < off + ext_len {
return None
}
off = off + ext_len
}
Some((context, certs))
}