]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/tls: add QUICErrorEvent
authorDamien Neil <dneil@google.com>
Sat, 8 Nov 2025 19:22:59 +0000 (11:22 -0800)
committerGopher Robot <gobot@golang.org>
Thu, 20 Nov 2025 23:39:14 +0000 (15:39 -0800)
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 <dneil@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
api/next/75108.txt [new file with mode: 0644]
doc/next/6-stdlib/99-minor/crypto/tls/75108.md [new file with mode: 0644]
src/crypto/tls/quic.go
src/crypto/tls/quic_test.go

diff --git a/api/next/75108.txt b/api/next/75108.txt
new file mode 100644 (file)
index 0000000..f1784d2
--- /dev/null
@@ -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 (file)
index 0000000..1913f6f
--- /dev/null
@@ -0,0 +1,2 @@
+The [QUICConn] type used by QUIC implementations includes new event
+for reporting TLS handshake errors.
index 2ba2242b2d93d2159f905956aa601d95b86a6413..b3f95dbb18ce01f6b2372123f5fc886e6fc0a331 100644 (file)
@@ -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
index 5f4b2b7707d01e85224c2abc6a42fc9ad36cb1b3..bd0eaa4d47efc6b56eff614693ac4430f0e89678 100644 (file)
@@ -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,