mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
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>
This commit is contained in:
parent
3ad2e113fc
commit
bd2b117c2c
4 changed files with 73 additions and 0 deletions
3
api/next/75108.txt
Normal file
3
api/next/75108.txt
Normal file
|
|
@ -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
|
||||||
2
doc/next/6-stdlib/99-minor/crypto/tls/75108.md
Normal file
2
doc/next/6-stdlib/99-minor/crypto/tls/75108.md
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
The [QUICConn] type used by QUIC implementations includes new event
|
||||||
|
for reporting TLS handshake errors.
|
||||||
|
|
@ -117,6 +117,11 @@ const (
|
||||||
// The application may modify the [SessionState] before storing it.
|
// The application may modify the [SessionState] before storing it.
|
||||||
// This event only occurs on client connections.
|
// This event only occurs on client connections.
|
||||||
QUICStoreSession
|
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.
|
// A QUICEvent is an event occurring on a QUIC connection.
|
||||||
|
|
@ -138,6 +143,10 @@ type QUICEvent struct {
|
||||||
|
|
||||||
// Set for QUICResumeSession and QUICStoreSession.
|
// Set for QUICResumeSession and QUICStoreSession.
|
||||||
SessionState *SessionState
|
SessionState *SessionState
|
||||||
|
|
||||||
|
// Set for QUICErrorEvent.
|
||||||
|
// The error will wrap AlertError.
|
||||||
|
Err error
|
||||||
}
|
}
|
||||||
|
|
||||||
type quicState struct {
|
type quicState struct {
|
||||||
|
|
@ -157,6 +166,7 @@ type quicState struct {
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
|
||||||
waitingForDrain bool
|
waitingForDrain bool
|
||||||
|
errorReturned bool
|
||||||
|
|
||||||
// readbuf is shared between HandleData and the handshake goroutine.
|
// readbuf is shared between HandleData and the handshake goroutine.
|
||||||
// HandshakeCryptoData passes ownership to the handshake goroutine by
|
// HandshakeCryptoData passes ownership to the handshake goroutine by
|
||||||
|
|
@ -229,6 +239,15 @@ func (q *QUICConn) NextEvent() QUICEvent {
|
||||||
<-qs.signalc
|
<-qs.signalc
|
||||||
<-qs.blockedc
|
<-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) {
|
if qs.nextEvent >= len(qs.events) {
|
||||||
qs.events = qs.events[:0]
|
qs.events = qs.events[:0]
|
||||||
qs.nextEvent = 0
|
qs.nextEvent = 0
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -21,6 +22,7 @@ type testQUICConn struct {
|
||||||
ticketOpts QUICSessionTicketOptions
|
ticketOpts QUICSessionTicketOptions
|
||||||
onResumeSession func(*SessionState)
|
onResumeSession func(*SessionState)
|
||||||
gotParams []byte
|
gotParams []byte
|
||||||
|
gotError error
|
||||||
earlyDataRejected bool
|
earlyDataRejected bool
|
||||||
complete bool
|
complete bool
|
||||||
}
|
}
|
||||||
|
|
@ -109,6 +111,9 @@ func runTestQUICConnection(ctx context.Context, cli, srv *testQUICConn, onEvent
|
||||||
if onEvent != nil && onEvent(e, a, b) {
|
if onEvent != nil && onEvent(e, a, b) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if a.gotError != nil && e.Kind != QUICNoEvent {
|
||||||
|
return fmt.Errorf("unexpected event %v after QUICErrorEvent", e.Kind)
|
||||||
|
}
|
||||||
switch e.Kind {
|
switch e.Kind {
|
||||||
case QUICNoEvent:
|
case QUICNoEvent:
|
||||||
idleCount++
|
idleCount++
|
||||||
|
|
@ -152,6 +157,11 @@ func runTestQUICConnection(ctx context.Context, cli, srv *testQUICConn, onEvent
|
||||||
}
|
}
|
||||||
case QUICRejectedEarlyData:
|
case QUICRejectedEarlyData:
|
||||||
a.earlyDataRejected = true
|
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 {
|
if e.Kind != QUICNoEvent {
|
||||||
idleCount = 0
|
idleCount = 0
|
||||||
|
|
@ -371,6 +381,45 @@ func TestQUICHandshakeError(t *testing.T) {
|
||||||
if _, ok := errors.AsType[*CertificateVerificationError](err); !ok {
|
if _, ok := errors.AsType[*CertificateVerificationError](err); !ok {
|
||||||
t.Errorf("connection handshake terminated with error %q, want CertificateVerificationError", err)
|
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,
|
// Test that QUICConn.ConnectionState can be used during the handshake,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue