From bd2b117c2c778343106f5823e4ae99da2160d095 Mon Sep 17 00:00:00 2001 From: Damien Neil Date: Sat, 8 Nov 2025 11:22:59 -0800 Subject: [PATCH] crypto/tls: add QUICErrorEvent Add a new QUICEvent type for reporting errors. This provides a way to report errors that don't occur as a result of QUICConn.Start, QUICConn.HandleData, or QUICConn.SendSessionTicket. Fixes #75108 Change-Id: I941371a21f26b940e75287a66d7e0211fc0baab1 Reviewed-on: https://go-review.googlesource.com/c/go/+/719040 Auto-Submit: Damien Neil LUCI-TryBot-Result: Go LUCI Reviewed-by: Roland Shoemaker --- api/next/75108.txt | 3 ++ .../6-stdlib/99-minor/crypto/tls/75108.md | 2 + src/crypto/tls/quic.go | 19 +++++++ src/crypto/tls/quic_test.go | 49 +++++++++++++++++++ 4 files changed, 73 insertions(+) create mode 100644 api/next/75108.txt create mode 100644 doc/next/6-stdlib/99-minor/crypto/tls/75108.md diff --git a/api/next/75108.txt b/api/next/75108.txt new file mode 100644 index 0000000000..f1784d2c5a --- /dev/null +++ b/api/next/75108.txt @@ -0,0 +1,3 @@ +pkg crypto/tls, const QUICErrorEvent = 10 #75108 +pkg crypto/tls, const QUICErrorEvent QUICEventKind #75108 +pkg crypto/tls, type QUICEvent struct, Err error #75108 diff --git a/doc/next/6-stdlib/99-minor/crypto/tls/75108.md b/doc/next/6-stdlib/99-minor/crypto/tls/75108.md new file mode 100644 index 0000000000..1913f6f567 --- /dev/null +++ b/doc/next/6-stdlib/99-minor/crypto/tls/75108.md @@ -0,0 +1,2 @@ +The [QUICConn] type used by QUIC implementations includes new event +for reporting TLS handshake errors. diff --git a/src/crypto/tls/quic.go b/src/crypto/tls/quic.go index 2ba2242b2d..b3f95dbb18 100644 --- a/src/crypto/tls/quic.go +++ b/src/crypto/tls/quic.go @@ -117,6 +117,11 @@ const ( // The application may modify the [SessionState] before storing it. // This event only occurs on client connections. QUICStoreSession + + // QUICErrorEvent indicates that a fatal error has occurred. + // The handshake cannot proceed and the connection must be closed. + // QUICEvent.Err is set. + QUICErrorEvent ) // A QUICEvent is an event occurring on a QUIC connection. @@ -138,6 +143,10 @@ type QUICEvent struct { // Set for QUICResumeSession and QUICStoreSession. SessionState *SessionState + + // Set for QUICErrorEvent. + // The error will wrap AlertError. + Err error } type quicState struct { @@ -157,6 +166,7 @@ type quicState struct { cancel context.CancelFunc waitingForDrain bool + errorReturned bool // readbuf is shared between HandleData and the handshake goroutine. // HandshakeCryptoData passes ownership to the handshake goroutine by @@ -229,6 +239,15 @@ func (q *QUICConn) NextEvent() QUICEvent { <-qs.signalc <-qs.blockedc } + if err := q.conn.handshakeErr; err != nil { + if qs.errorReturned { + return QUICEvent{Kind: QUICNoEvent} + } + qs.errorReturned = true + qs.events = nil + qs.nextEvent = 0 + return QUICEvent{Kind: QUICErrorEvent, Err: q.conn.handshakeErr} + } if qs.nextEvent >= len(qs.events) { qs.events = qs.events[:0] qs.nextEvent = 0 diff --git a/src/crypto/tls/quic_test.go b/src/crypto/tls/quic_test.go index 5f4b2b7707..bd0eaa4d47 100644 --- a/src/crypto/tls/quic_test.go +++ b/src/crypto/tls/quic_test.go @@ -8,6 +8,7 @@ import ( "bytes" "context" "errors" + "fmt" "reflect" "strings" "testing" @@ -21,6 +22,7 @@ type testQUICConn struct { ticketOpts QUICSessionTicketOptions onResumeSession func(*SessionState) gotParams []byte + gotError error earlyDataRejected bool complete bool } @@ -109,6 +111,9 @@ func runTestQUICConnection(ctx context.Context, cli, srv *testQUICConn, onEvent if onEvent != nil && onEvent(e, a, b) { continue } + if a.gotError != nil && e.Kind != QUICNoEvent { + return fmt.Errorf("unexpected event %v after QUICErrorEvent", e.Kind) + } switch e.Kind { case QUICNoEvent: idleCount++ @@ -152,6 +157,11 @@ func runTestQUICConnection(ctx context.Context, cli, srv *testQUICConn, onEvent } case QUICRejectedEarlyData: a.earlyDataRejected = true + case QUICErrorEvent: + if e.Err == nil { + return errors.New("unexpected QUICErrorEvent with no Err") + } + a.gotError = e.Err } if e.Kind != QUICNoEvent { idleCount = 0 @@ -371,6 +381,45 @@ func TestQUICHandshakeError(t *testing.T) { if _, ok := errors.AsType[*CertificateVerificationError](err); !ok { t.Errorf("connection handshake terminated with error %q, want CertificateVerificationError", err) } + + ev := cli.conn.NextEvent() + if ev.Kind != QUICErrorEvent { + t.Errorf("client.NextEvent: no QUICErrorEvent, want one") + } + if ev.Err != err { + t.Errorf("client.NextEvent: want same error returned by Start, got %v", ev.Err) + } +} + +// Test that we can report an error produced by the GetEncryptedClientHelloKeys function. +func TestQUICECHKeyError(t *testing.T) { + getECHKeysError := errors.New("error returned by GetEncryptedClientHelloKeys") + config := &QUICConfig{TLSConfig: testConfig.Clone()} + config.TLSConfig.MinVersion = VersionTLS13 + config.TLSConfig.NextProtos = []string{"h3"} + config.TLSConfig.GetEncryptedClientHelloKeys = func(*ClientHelloInfo) ([]EncryptedClientHelloKey, error) { + return nil, getECHKeysError + } + cli := newTestQUICClient(t, config) + cli.conn.SetTransportParameters(nil) + srv := newTestQUICServer(t, config) + + if err := runTestQUICConnection(context.Background(), cli, srv, nil); err != errTransportParametersRequired { + t.Fatalf("handshake with no client parameters: %v; want errTransportParametersRequired", err) + } + srv.conn.SetTransportParameters(nil) + if err := runTestQUICConnection(context.Background(), cli, srv, nil); err == nil { + t.Fatalf("handshake with GetEncryptedClientHelloKeys errors: nil, want error") + } + if srv.gotError == nil { + t.Fatalf("after GetEncryptedClientHelloKeys error, server did not see QUICErrorEvent") + } + if _, ok := errors.AsType[AlertError](srv.gotError); !ok { + t.Errorf("connection handshake terminated with error %T, want AlertError", srv.gotError) + } + if !errors.Is(srv.gotError, getECHKeysError) { + t.Errorf("connection handshake terminated with error %v, want error returned by GetEncryptedClientHelloKeys", srv.gotError) + } } // Test that QUICConn.ConnectionState can be used during the handshake, -- 2.52.0