buildInitialClientHello function
Build a dynamic TLS 1.3 ClientHello for QUIC / HTTP/3
IMPORTANT:
- pass the X25519 public key here, not the private key
- use
serialize()on the returned ClientHello to get the full TLS Handshake message (header + body)
Implementation
ClientHello buildInitialClientHello({
required String hostname,
required Uint8List x25519PublicKey,
required Uint8List localCid,
List<String> alpns = const ['h3'],
}) {
final rnd = math.Random.secure();
final random = Uint8List.fromList(List.generate(32, (_) => rnd.nextInt(256)));
final extensions = <TlsExtension>[];
TlsExtension makeExt(int type, QuicBuffer buf) {
final bytes = buf.toBytes();
return TlsExtension(type: type, length: bytes.length, data: bytes);
}
// ----------------------------------------------------------
// 1) SNI
// ----------------------------------------------------------
final hostBytes = Uint8List.fromList(hostname.codeUnits);
final sniBuf = QuicBuffer()
..pushUint16(hostBytes.length + 3) // server_name_list length
..pushUint8(0x00) // host_name
..pushUint16(hostBytes.length)
..pushBytes(hostBytes);
extensions.add(makeExt(0x0000, sniBuf));
// ----------------------------------------------------------
// 2) Supported groups
// ----------------------------------------------------------
final groupsBuf = QuicBuffer()
..pushUint16(6)
..pushUint16(0x001d) // x25519
..pushUint16(0x0017) // secp256r1
..pushUint16(0x0018); // secp384r1
extensions.add(makeExt(0x000a, groupsBuf));
// ----------------------------------------------------------
// 3) Signature algorithms
// ----------------------------------------------------------
final sigBuf = QuicBuffer()
..pushUint16(4)
..pushUint16(0x0403) // ecdsa_secp256r1_sha256
..pushUint16(0x0804); // rsa_pss_rsae_sha256
extensions.add(makeExt(0x000d, sigBuf));
// ----------------------------------------------------------
// 4) KeyShare (X25519)
// ----------------------------------------------------------
final keyShareEntry = QuicBuffer()
..pushUint16(0x001d) // x25519
..pushUint16(x25519PublicKey.length)
..pushBytes(x25519PublicKey);
final keyShareBuf = QuicBuffer()
..pushUint16(keyShareEntry.writeIndex)
..pushBytes(keyShareEntry.toBytes());
extensions.add(makeExt(0x0033, keyShareBuf));
// ----------------------------------------------------------
// 5) PSK key exchange modes
// ----------------------------------------------------------
final pskBuf = QuicBuffer()
..pushUint8(1)
..pushUint8(1); // psk_dhe_ke
extensions.add(makeExt(0x002d, pskBuf));
// ----------------------------------------------------------
// 6) Supported versions = TLS 1.3
// ----------------------------------------------------------
final versionsBuf = QuicBuffer()
..pushUint8(2)
..pushUint8(0x03)
..pushUint8(0x04);
extensions.add(makeExt(0x002b, versionsBuf));
// ----------------------------------------------------------
// 7) QUIC transport parameters
//
// NOTE:
// integer-valued transport params MUST be encoded as QUIC varints
// inside the parameter value bytes.
// ----------------------------------------------------------
final tpBytes = BytesBuilder()
..add(_tpInt(tpMaxIdleTimeout, 30000))
..add(_tpInt(tpMaxUdpPayloadSize, 65527))
..add(_tpInt(tpInitialMaxData, 1 << 20))
..add(_tpInt(tpInitialMaxStreamDataBidiLocal, 1 << 18))
..add(_tpInt(tpInitialMaxStreamDataBidiRemote, 1 << 18))
..add(_tpInt(tpInitialMaxStreamDataUni, 1 << 18))
..add(_tpInt(tpInitialMaxStreamsBidi, 16))
..add(_tpInt(tpInitialMaxStreamsUni, 16))
..add(_tpInt(tpActiveConnectionIdLimit, 4))
..add(_tpBytes(tpInitialSourceConnectionId, localCid));
extensions.add(
TlsExtension(
type: 0x0039,
length: tpBytes.toBytes().length,
data: tpBytes.toBytes(),
),
);
// ----------------------------------------------------------
// IMPORTANT:
// We set `alpn: alpns` here so that ClientHello.serialize()
// will upsert the ALPN extension correctly.
//
// We intentionally do NOT manually add extension 0x0010 here,
// because serialize() / upsertAlpnExtension() will handle it
// from the semantic `alpn` field.
// ----------------------------------------------------------
return ClientHello(
type: 'client_hello',
legacyVersion: 0x0303,
random: random,
sessionId: Uint8List(0),
cipherSuites: const [
0x1301, // TLS_AES_128_GCM_SHA256
0x1302, // TLS_AES_256_GCM_SHA384
0x1303, // TLS_CHACHA20_POLY1305_SHA256
],
compressionMethods: Uint8List.fromList([0x00]),
extensions: extensions,
rawData: Uint8List(0),
alpn: alpns,
);
}