feat(quic-interop): wire SSLKEYLOGFILE + add chacha20 testcase (Phase 1a)
Adds two debugging hooks to :quic so the interop endpoint can produce
artifacts that make the runner actually useful for finding bugs:
- TlsClient.clientRandom: capture and expose the 32-byte ClientHello
random so a SSLKEYLOG line can correlate to this connection.
- QuicConnection.extraSecretsListener: optional chained secrets
listener (default null, no-op for production callers). Fires
alongside the connection's own key-installation listener at every
encryption-level transition.
- QuicConnection.cipherSuites: knob to override the offered TLS
cipher suites in ClientHello.
InteropClient now:
- Writes NSS Key Log Format lines to $SSLKEYLOGFILE when set, so
Wireshark can decrypt the sim's pcap captures.
- Implements the `chacha20` testcase by offering only
TLS_CHACHA20_POLY1305_SHA256 — exercises the ChaCha20 AEAD path
end-to-end against a peer.
Defers QLOGDIR (needs qlog observer infrastructure across packet/
frame/recovery layers — own design doc) and `versionnegotiation`
(needs the writer to accept a configurable initial QUIC version).
https://claude.ai/code/session_01HcvfQq1ttPV9PkRoJb4nyT
This commit is contained in:
@@ -73,6 +73,23 @@ class QuicConnection(
|
||||
.toEpochMilliseconds()
|
||||
},
|
||||
val alpnList: List<ByteArray> = listOf(TlsConstants.ALPN_H3),
|
||||
/**
|
||||
* Optional second listener invoked after the connection's own
|
||||
* key-installation listener. Used by the interop runner endpoint to
|
||||
* dump SSLKEYLOG lines so Wireshark can decrypt captured pcaps.
|
||||
* Default `null` keeps production callers unaffected.
|
||||
*/
|
||||
val extraSecretsListener: TlsSecretsListener? = null,
|
||||
/**
|
||||
* TLS cipher suites to offer in the ClientHello. Override to e.g.
|
||||
* `intArrayOf(TlsConstants.CIPHER_TLS_CHACHA20_POLY1305_SHA256)` for the
|
||||
* `chacha20` interop testcase. Default matches [TlsClient]'s default.
|
||||
*/
|
||||
val cipherSuites: IntArray =
|
||||
intArrayOf(
|
||||
TlsConstants.CIPHER_TLS_AES_128_GCM_SHA256,
|
||||
TlsConstants.CIPHER_TLS_CHACHA20_POLY1305_SHA256,
|
||||
),
|
||||
) {
|
||||
val sourceConnectionId: ConnectionId = ConnectionId.random(8)
|
||||
var destinationConnectionId: ConnectionId = ConnectionId.random(8)
|
||||
@@ -317,6 +334,7 @@ class QuicConnection(
|
||||
) {
|
||||
handshake.sendProtection = packetProtectionFromSecret(cipherSuite, clientSecret)
|
||||
handshake.receiveProtection = packetProtectionFromSecret(cipherSuite, serverSecret)
|
||||
extraSecretsListener?.onHandshakeKeysReady(cipherSuite, clientSecret, serverSecret)
|
||||
}
|
||||
|
||||
override fun onApplicationKeysReady(
|
||||
@@ -326,6 +344,7 @@ class QuicConnection(
|
||||
) {
|
||||
application.sendProtection = packetProtectionFromSecret(cipherSuite, clientSecret)
|
||||
application.receiveProtection = packetProtectionFromSecret(cipherSuite, serverSecret)
|
||||
extraSecretsListener?.onApplicationKeysReady(cipherSuite, clientSecret, serverSecret)
|
||||
}
|
||||
|
||||
override fun onHandshakeComplete() {
|
||||
@@ -333,6 +352,7 @@ class QuicConnection(
|
||||
if (status == Status.HANDSHAKING) status = Status.CONNECTED
|
||||
applyPeerTransportParameters()
|
||||
handshakeDoneSignal.complete(Unit)
|
||||
extraSecretsListener?.onHandshakeComplete()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,6 +378,7 @@ class QuicConnection(
|
||||
secretsListener = tlsListener,
|
||||
certificateValidator = tlsCertificateValidator,
|
||||
offeredAlpns = alpnList,
|
||||
cipherSuites = cipherSuites,
|
||||
)
|
||||
|
||||
init {
|
||||
|
||||
@@ -70,6 +70,17 @@ class TlsClient(
|
||||
val fixedKeyPair: X25519KeyPair? = null,
|
||||
/** When non-null, used as the ClientHello random (for deterministic tests). */
|
||||
val fixedRandom: ByteArray? = null,
|
||||
/**
|
||||
* Cipher suites offered in the ClientHello, in preference order. The
|
||||
* default offers AES-128-GCM first then ChaCha20-Poly1305. Override to
|
||||
* force a specific negotiation (e.g. `chacha20`-only for the matching
|
||||
* quic-interop-runner testcase).
|
||||
*/
|
||||
val cipherSuites: IntArray =
|
||||
intArrayOf(
|
||||
TlsConstants.CIPHER_TLS_AES_128_GCM_SHA256,
|
||||
TlsConstants.CIPHER_TLS_CHACHA20_POLY1305_SHA256,
|
||||
),
|
||||
) {
|
||||
enum class State {
|
||||
INITIAL,
|
||||
@@ -120,6 +131,12 @@ class TlsClient(
|
||||
private var sharedSecret: ByteArray? = null
|
||||
private var negotiatedCipherSuite: Int = -1
|
||||
|
||||
/** The 32-byte ClientHello random, available after [start]. Exposed so
|
||||
* observers (e.g. SSLKEYLOGFILE writer) can correlate secrets with
|
||||
* this connection. */
|
||||
var clientRandom: ByteArray? = null
|
||||
private set
|
||||
|
||||
/** Begin the handshake by emitting a ClientHello at Initial level. */
|
||||
fun start() {
|
||||
check(state == State.INITIAL) { "TlsClient already started" }
|
||||
@@ -127,14 +144,18 @@ class TlsClient(
|
||||
|
||||
keySchedule.deriveEarly()
|
||||
|
||||
val random =
|
||||
fixedRandom ?: com.vitorpamplona.quartz.utils.RandomInstance
|
||||
.bytes(32)
|
||||
clientRandom = random
|
||||
|
||||
val ch =
|
||||
buildQuicClientHello(
|
||||
serverName = serverName,
|
||||
x25519PublicKey = keyPair!!.publicKey,
|
||||
quicTransportParams = transportParameters,
|
||||
random =
|
||||
fixedRandom ?: com.vitorpamplona.quartz.utils.RandomInstance
|
||||
.bytes(32),
|
||||
random = random,
|
||||
cipherSuites = cipherSuites,
|
||||
)
|
||||
|
||||
val chBytes = ch.encode()
|
||||
|
||||
@@ -92,6 +92,11 @@ fun buildQuicClientHello(
|
||||
quicTransportParams: ByteArray,
|
||||
additionalAlpn: List<ByteArray> = emptyList(),
|
||||
random: ByteArray = RandomInstance.bytes(32),
|
||||
cipherSuites: IntArray =
|
||||
intArrayOf(
|
||||
TlsConstants.CIPHER_TLS_AES_128_GCM_SHA256,
|
||||
TlsConstants.CIPHER_TLS_CHACHA20_POLY1305_SHA256,
|
||||
),
|
||||
): TlsClientHello {
|
||||
val alpn = mutableListOf<ByteArray>()
|
||||
alpn += TlsConstants.ALPN_H3
|
||||
@@ -107,5 +112,5 @@ fun buildQuicClientHello(
|
||||
TlsExtension(TlsConstants.EXT_ALPN, encodeAlpn(alpn)),
|
||||
TlsExtension(TlsConstants.EXT_QUIC_TRANSPORT_PARAMETERS, quicTransportParams),
|
||||
)
|
||||
return TlsClientHello(random = random, extensions = exts)
|
||||
return TlsClientHello(random = random, cipherSuites = cipherSuites, extensions = exts)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user