aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDamien Neil <dneil@google.com>2025-11-08 11:22:59 -0800
committerGopher Robot <gobot@golang.org>2025-11-20 15:39:14 -0800
commitbd2b117c2c778343106f5823e4ae99da2160d095 (patch)
treed55e0fad8e9cfa78b5b40f20f3712e1769b785c4 /src
parent3ad2e113fc3dd202bfb2ef87d376b6ef54337f0b (diff)
downloadgo-bd2b117c2c778343106f5823e4ae99da2160d095.tar.xz
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 <dneil@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Roland Shoemaker <roland@golang.org>
Diffstat (limited to 'src')
-rw-r--r--src/crypto/tls/quic.go19
-rw-r--r--src/crypto/tls/quic_test.go49
2 files changed, 68 insertions, 0 deletions
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,