Skip to content

Commit 2cac7e8

Browse files
committed
crypto/tls: support QUIC as a transport
Add a QUICConn type for use by QUIC implementations. A QUICConn provides unencrypted handshake bytes and connection secrets to the QUIC layer, and receives handshake bytes. For #44886 Change-Id: I859dda4cc6d466a1df2fb863a69d3a2a069110d5 Reviewed-on: https://go-review.googlesource.com/c/go/+/493655 TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Filippo Valsorda <[email protected]> Run-TryBot: Damien Neil <[email protected]> Reviewed-by: Matthew Dempsky <[email protected]> Reviewed-by: Marten Seemann <[email protected]>
1 parent 543e601 commit 2cac7e8

12 files changed

+1118
-50
lines changed

api/next/44886.txt

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
pkg crypto/tls, const QUICEncryptionLevelApplication = 2 #44886
2+
pkg crypto/tls, const QUICEncryptionLevelApplication QUICEncryptionLevel #44886
3+
pkg crypto/tls, const QUICEncryptionLevelHandshake = 1 #44886
4+
pkg crypto/tls, const QUICEncryptionLevelHandshake QUICEncryptionLevel #44886
5+
pkg crypto/tls, const QUICEncryptionLevelInitial = 0 #44886
6+
pkg crypto/tls, const QUICEncryptionLevelInitial QUICEncryptionLevel #44886
7+
pkg crypto/tls, const QUICHandshakeDone = 6 #44886
8+
pkg crypto/tls, const QUICHandshakeDone QUICEventKind #44886
9+
pkg crypto/tls, const QUICNoEvent = 0 #44886
10+
pkg crypto/tls, const QUICNoEvent QUICEventKind #44886
11+
pkg crypto/tls, const QUICSetReadSecret = 1 #44886
12+
pkg crypto/tls, const QUICSetReadSecret QUICEventKind #44886
13+
pkg crypto/tls, const QUICSetWriteSecret = 2 #44886
14+
pkg crypto/tls, const QUICSetWriteSecret QUICEventKind #44886
15+
pkg crypto/tls, const QUICTransportParameters = 4 #44886
16+
pkg crypto/tls, const QUICTransportParameters QUICEventKind #44886
17+
pkg crypto/tls, const QUICTransportParametersRequired = 5 #44886
18+
pkg crypto/tls, const QUICTransportParametersRequired QUICEventKind #44886
19+
pkg crypto/tls, const QUICWriteData = 3 #44886
20+
pkg crypto/tls, const QUICWriteData QUICEventKind #44886
21+
pkg crypto/tls, func QUICClient(*QUICConfig) *QUICConn #44886
22+
pkg crypto/tls, func QUICServer(*QUICConfig) *QUICConn #44886
23+
pkg crypto/tls, method (*QUICConn) Close() error #44886
24+
pkg crypto/tls, method (*QUICConn) ConnectionState() ConnectionState #44886
25+
pkg crypto/tls, method (*QUICConn) HandleData(QUICEncryptionLevel, []uint8) error #44886
26+
pkg crypto/tls, method (*QUICConn) NextEvent() QUICEvent #44886
27+
pkg crypto/tls, method (*QUICConn) SetTransportParameters([]uint8) #44886
28+
pkg crypto/tls, method (*QUICConn) Start(context.Context) error #44886
29+
pkg crypto/tls, method (AlertError) Error() string #44886
30+
pkg crypto/tls, method (QUICEncryptionLevel) String() string #44886
31+
pkg crypto/tls, type AlertError uint8 #44886
32+
pkg crypto/tls, type QUICConfig struct #44886
33+
pkg crypto/tls, type QUICConfig struct, TLSConfig *Config #44886
34+
pkg crypto/tls, type QUICConn struct #44886
35+
pkg crypto/tls, type QUICEncryptionLevel int #44886
36+
pkg crypto/tls, type QUICEvent struct #44886
37+
pkg crypto/tls, type QUICEvent struct, Data []uint8 #44886
38+
pkg crypto/tls, type QUICEvent struct, Kind QUICEventKind #44886
39+
pkg crypto/tls, type QUICEvent struct, Level QUICEncryptionLevel #44886
40+
pkg crypto/tls, type QUICEvent struct, Suite uint16 #44886
41+
pkg crypto/tls, type QUICEventKind int #44886

src/crypto/tls/alert.go

+10
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ package tls
66

77
import "strconv"
88

9+
// An AlertError is a TLS alert.
10+
//
11+
// When using a QUIC transport, QUICConn methods will return an error
12+
// which wraps AlertError rather than sending a TLS alert.
13+
type AlertError uint8
14+
15+
func (e AlertError) Error() string {
16+
return alert(e).String()
17+
}
18+
919
type alert uint8
1020

1121
const (

src/crypto/tls/common.go

+1
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ const (
9999
extensionCertificateAuthorities uint16 = 47
100100
extensionSignatureAlgorithmsCert uint16 = 50
101101
extensionKeyShare uint16 = 51
102+
extensionQUICTransportParameters uint16 = 57
102103
extensionRenegotiationInfo uint16 = 0xff01
103104
)
104105

src/crypto/tls/conn.go

+96-23
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type Conn struct {
2929
conn net.Conn
3030
isClient bool
3131
handshakeFn func(context.Context) error // (*Conn).clientHandshake or serverHandshake
32+
quic *quicState // nil for non-QUIC connections
3233

3334
// isHandshakeComplete is true if the connection is currently transferring
3435
// application data (i.e. is not currently processing a handshake).
@@ -176,7 +177,8 @@ type halfConn struct {
176177
nextCipher any // next encryption state
177178
nextMac hash.Hash // next MAC algorithm
178179

179-
trafficSecret []byte // current TLS 1.3 traffic secret
180+
level QUICEncryptionLevel // current QUIC encryption level
181+
trafficSecret []byte // current TLS 1.3 traffic secret
180182
}
181183

182184
type permanentError struct {
@@ -221,8 +223,9 @@ func (hc *halfConn) changeCipherSpec() error {
221223
return nil
222224
}
223225

224-
func (hc *halfConn) setTrafficSecret(suite *cipherSuiteTLS13, secret []byte) {
226+
func (hc *halfConn) setTrafficSecret(suite *cipherSuiteTLS13, level QUICEncryptionLevel, secret []byte) {
225227
hc.trafficSecret = secret
228+
hc.level = level
226229
key, iv := suite.trafficKey(secret)
227230
hc.cipher = suite.aead(key, iv)
228231
for i := range hc.seq {
@@ -613,6 +616,10 @@ func (c *Conn) readRecordOrCCS(expectChangeCipherSpec bool) error {
613616
}
614617
c.input.Reset(nil)
615618

619+
if c.quic != nil {
620+
return c.in.setErrorLocked(errors.New("tls: internal error: attempted to read record with QUIC transport"))
621+
}
622+
616623
// Read header, payload.
617624
if err := c.readFromUntil(c.conn, recordHeaderLen); err != nil {
618625
// RFC 8446, Section 6.1 suggests that EOF without an alertCloseNotify
@@ -702,6 +709,9 @@ func (c *Conn) readRecordOrCCS(expectChangeCipherSpec bool) error {
702709
return c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage))
703710

704711
case recordTypeAlert:
712+
if c.quic != nil {
713+
return c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage))
714+
}
705715
if len(data) != 2 {
706716
return c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage))
707717
}
@@ -819,6 +829,10 @@ func (c *Conn) readFromUntil(r io.Reader, n int) error {
819829

820830
// sendAlertLocked sends a TLS alert message.
821831
func (c *Conn) sendAlertLocked(err alert) error {
832+
if c.quic != nil {
833+
return c.out.setErrorLocked(&net.OpError{Op: "local error", Err: err})
834+
}
835+
822836
switch err {
823837
case alertNoRenegotiation, alertCloseNotify:
824838
c.tmp[0] = alertLevelWarning
@@ -953,6 +967,19 @@ var outBufPool = sync.Pool{
953967
// writeRecordLocked writes a TLS record with the given type and payload to the
954968
// connection and updates the record layer state.
955969
func (c *Conn) writeRecordLocked(typ recordType, data []byte) (int, error) {
970+
if c.quic != nil {
971+
if typ != recordTypeHandshake {
972+
return 0, errors.New("tls: internal error: sending non-handshake message to QUIC transport")
973+
}
974+
c.quicWriteCryptoData(c.out.level, data)
975+
if !c.buffering {
976+
if _, err := c.flush(); err != nil {
977+
return 0, err
978+
}
979+
}
980+
return len(data), nil
981+
}
982+
956983
outBufPtr := outBufPool.Get().(*[]byte)
957984
outBuf := *outBufPtr
958985
defer func() {
@@ -1037,28 +1064,40 @@ func (c *Conn) writeChangeCipherRecord() error {
10371064
return err
10381065
}
10391066

1067+
// readHandshakeBytes reads handshake data until c.hand contains at least n bytes.
1068+
func (c *Conn) readHandshakeBytes(n int) error {
1069+
if c.quic != nil {
1070+
return c.quicReadHandshakeBytes(n)
1071+
}
1072+
for c.hand.Len() < n {
1073+
if err := c.readRecord(); err != nil {
1074+
return err
1075+
}
1076+
}
1077+
return nil
1078+
}
1079+
10401080
// readHandshake reads the next handshake message from
10411081
// the record layer. If transcript is non-nil, the message
10421082
// is written to the passed transcriptHash.
10431083
func (c *Conn) readHandshake(transcript transcriptHash) (any, error) {
1044-
for c.hand.Len() < 4 {
1045-
if err := c.readRecord(); err != nil {
1046-
return nil, err
1047-
}
1084+
if err := c.readHandshakeBytes(4); err != nil {
1085+
return nil, err
10481086
}
1049-
10501087
data := c.hand.Bytes()
10511088
n := int(data[1])<<16 | int(data[2])<<8 | int(data[3])
10521089
if n > maxHandshake {
10531090
c.sendAlertLocked(alertInternalError)
10541091
return nil, c.in.setErrorLocked(fmt.Errorf("tls: handshake message of length %d bytes exceeds maximum of %d bytes", n, maxHandshake))
10551092
}
1056-
for c.hand.Len() < 4+n {
1057-
if err := c.readRecord(); err != nil {
1058-
return nil, err
1059-
}
1093+
if err := c.readHandshakeBytes(4 + n); err != nil {
1094+
return nil, err
10601095
}
10611096
data = c.hand.Next(4 + n)
1097+
return c.unmarshalHandshakeMessage(data, transcript)
1098+
}
1099+
1100+
func (c *Conn) unmarshalHandshakeMessage(data []byte, transcript transcriptHash) (handshakeMessage, error) {
10621101
var m handshakeMessage
10631102
switch data[0] {
10641103
case typeHelloRequest:
@@ -1249,7 +1288,6 @@ func (c *Conn) handlePostHandshakeMessage() error {
12491288
if err != nil {
12501289
return err
12511290
}
1252-
12531291
c.retryCount++
12541292
if c.retryCount > maxUselessRecords {
12551293
c.sendAlert(alertUnexpectedMessage)
@@ -1261,20 +1299,28 @@ func (c *Conn) handlePostHandshakeMessage() error {
12611299
return c.handleNewSessionTicket(msg)
12621300
case *keyUpdateMsg:
12631301
return c.handleKeyUpdate(msg)
1264-
default:
1265-
c.sendAlert(alertUnexpectedMessage)
1266-
return fmt.Errorf("tls: received unexpected handshake message of type %T", msg)
12671302
}
1303+
// The QUIC layer is supposed to treat an unexpected post-handshake CertificateRequest
1304+
// as a QUIC-level PROTOCOL_VIOLATION error (RFC 9001, Section 4.4). Returning an
1305+
// unexpected_message alert here doesn't provide it with enough information to distinguish
1306+
// this condition from other unexpected messages. This is probably fine.
1307+
c.sendAlert(alertUnexpectedMessage)
1308+
return fmt.Errorf("tls: received unexpected handshake message of type %T", msg)
12681309
}
12691310

12701311
func (c *Conn) handleKeyUpdate(keyUpdate *keyUpdateMsg) error {
1312+
if c.quic != nil {
1313+
c.sendAlert(alertUnexpectedMessage)
1314+
return c.in.setErrorLocked(errors.New("tls: received unexpected key update message"))
1315+
}
1316+
12711317
cipherSuite := cipherSuiteTLS13ByID(c.cipherSuite)
12721318
if cipherSuite == nil {
12731319
return c.in.setErrorLocked(c.sendAlert(alertInternalError))
12741320
}
12751321

12761322
newSecret := cipherSuite.nextTrafficSecret(c.in.trafficSecret)
1277-
c.in.setTrafficSecret(cipherSuite, newSecret)
1323+
c.in.setTrafficSecret(cipherSuite, QUICEncryptionLevelInitial, newSecret)
12781324

12791325
if keyUpdate.updateRequested {
12801326
c.out.Lock()
@@ -1293,7 +1339,7 @@ func (c *Conn) handleKeyUpdate(keyUpdate *keyUpdateMsg) error {
12931339
}
12941340

12951341
newSecret := cipherSuite.nextTrafficSecret(c.out.trafficSecret)
1296-
c.out.setTrafficSecret(cipherSuite, newSecret)
1342+
c.out.setTrafficSecret(cipherSuite, QUICEncryptionLevelInitial, newSecret)
12971343
}
12981344

12991345
return nil
@@ -1454,12 +1500,15 @@ func (c *Conn) handshakeContext(ctx context.Context) (ret error) {
14541500
// this cancellation. In the former case, we need to close the connection.
14551501
defer cancel()
14561502

1457-
// Start the "interrupter" goroutine, if this context might be canceled.
1458-
// (The background context cannot).
1459-
//
1460-
// The interrupter goroutine waits for the input context to be done and
1461-
// closes the connection if this happens before the function returns.
1462-
if ctx.Done() != nil {
1503+
if c.quic != nil {
1504+
c.quic.cancelc = handshakeCtx.Done()
1505+
c.quic.cancel = cancel
1506+
} else if ctx.Done() != nil {
1507+
// Start the "interrupter" goroutine, if this context might be canceled.
1508+
// (The background context cannot).
1509+
//
1510+
// The interrupter goroutine waits for the input context to be done and
1511+
// closes the connection if this happens before the function returns.
14631512
done := make(chan struct{})
14641513
interruptRes := make(chan error, 1)
14651514
defer func() {
@@ -1510,6 +1559,30 @@ func (c *Conn) handshakeContext(ctx context.Context) (ret error) {
15101559
panic("tls: internal error: handshake returned an error but is marked successful")
15111560
}
15121561

1562+
if c.quic != nil {
1563+
if c.handshakeErr == nil {
1564+
c.quicHandshakeComplete()
1565+
// Provide the 1-RTT read secret now that the handshake is complete.
1566+
// The QUIC layer MUST NOT decrypt 1-RTT packets prior to completing
1567+
// the handshake (RFC 9001, Section 5.7).
1568+
c.quicSetReadSecret(QUICEncryptionLevelApplication, c.cipherSuite, c.in.trafficSecret)
1569+
} else {
1570+
var a alert
1571+
c.out.Lock()
1572+
if !errors.As(c.out.err, &a) {
1573+
a = alertInternalError
1574+
}
1575+
c.out.Unlock()
1576+
// Return an error which wraps both the handshake error and
1577+
// any alert error we may have sent, or alertInternalError
1578+
// if we didn't send an alert.
1579+
// Truncate the text of the alert to 0 characters.
1580+
c.handshakeErr = fmt.Errorf("%w%.0w", c.handshakeErr, AlertError(a))
1581+
}
1582+
close(c.quic.blockedc)
1583+
close(c.quic.signalc)
1584+
}
1585+
15131586
return c.handshakeErr
15141587
}
15151588

src/crypto/tls/handshake_client.go

+35-10
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *ecdh.PrivateKey, error) {
7171
vers: clientHelloVersion,
7272
compressionMethods: []uint8{compressionNone},
7373
random: make([]byte, 32),
74-
sessionId: make([]byte, 32),
7574
ocspStapling: true,
7675
scts: true,
7776
serverName: hostnameInSNI(config.ServerName),
@@ -114,8 +113,13 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *ecdh.PrivateKey, error) {
114113
// A random session ID is used to detect when the server accepted a ticket
115114
// and is resuming a session (see RFC 5077). In TLS 1.3, it's always set as
116115
// a compatibility measure (see RFC 8446, Section 4.1.2).
117-
if _, err := io.ReadFull(config.rand(), hello.sessionId); err != nil {
118-
return nil, nil, errors.New("tls: short read from Rand: " + err.Error())
116+
//
117+
// The session ID is not set for QUIC connections (see RFC 9001, Section 8.4).
118+
if c.quic == nil {
119+
hello.sessionId = make([]byte, 32)
120+
if _, err := io.ReadFull(config.rand(), hello.sessionId); err != nil {
121+
return nil, nil, errors.New("tls: short read from Rand: " + err.Error())
122+
}
119123
}
120124

121125
if hello.vers >= VersionTLS12 {
@@ -144,6 +148,17 @@ func (c *Conn) makeClientHello() (*clientHelloMsg, *ecdh.PrivateKey, error) {
144148
hello.keyShares = []keyShare{{group: curveID, data: key.PublicKey().Bytes()}}
145149
}
146150

151+
if c.quic != nil {
152+
p, err := c.quicGetTransportParameters()
153+
if err != nil {
154+
return nil, nil, err
155+
}
156+
if p == nil {
157+
p = []byte{}
158+
}
159+
hello.quicTransportParameters = p
160+
}
161+
147162
return hello, key, nil
148163
}
149164

@@ -271,7 +286,10 @@ func (c *Conn) loadSession(hello *clientHelloMsg) (cacheKey string,
271286
}
272287

273288
// Try to resume a previously negotiated TLS session, if available.
274-
cacheKey = clientSessionCacheKey(c.conn.RemoteAddr(), c.config)
289+
cacheKey = c.clientSessionCacheKey()
290+
if cacheKey == "" {
291+
return "", nil, nil, nil, nil
292+
}
275293
session, ok := c.config.ClientSessionCache.Get(cacheKey)
276294
if !ok || session == nil {
277295
return cacheKey, nil, nil, nil, nil
@@ -722,7 +740,7 @@ func (hs *clientHandshakeState) processServerHello() (bool, error) {
722740
}
723741
}
724742

725-
if err := checkALPN(hs.hello.alpnProtocols, hs.serverHello.alpnProtocol); err != nil {
743+
if err := checkALPN(hs.hello.alpnProtocols, hs.serverHello.alpnProtocol, false); err != nil {
726744
c.sendAlert(alertUnsupportedExtension)
727745
return false, err
728746
}
@@ -760,8 +778,12 @@ func (hs *clientHandshakeState) processServerHello() (bool, error) {
760778

761779
// checkALPN ensure that the server's choice of ALPN protocol is compatible with
762780
// the protocols that we advertised in the Client Hello.
763-
func checkALPN(clientProtos []string, serverProto string) error {
781+
func checkALPN(clientProtos []string, serverProto string, quic bool) error {
764782
if serverProto == "" {
783+
if quic && len(clientProtos) > 0 {
784+
// RFC 9001, Section 8.1
785+
return errors.New("tls: server did not select an ALPN protocol")
786+
}
765787
return nil
766788
}
767789
if len(clientProtos) == 0 {
@@ -1003,11 +1025,14 @@ func (c *Conn) getClientCertificate(cri *CertificateRequestInfo) (*Certificate,
10031025

10041026
// clientSessionCacheKey returns a key used to cache sessionTickets that could
10051027
// be used to resume previously negotiated TLS sessions with a server.
1006-
func clientSessionCacheKey(serverAddr net.Addr, config *Config) string {
1007-
if len(config.ServerName) > 0 {
1008-
return config.ServerName
1028+
func (c *Conn) clientSessionCacheKey() string {
1029+
if len(c.config.ServerName) > 0 {
1030+
return c.config.ServerName
1031+
}
1032+
if c.conn != nil {
1033+
return c.conn.RemoteAddr().String()
10091034
}
1010-
return serverAddr.String()
1035+
return ""
10111036
}
10121037

10131038
// hostnameInSNI converts name into an appropriate hostname for SNI.

0 commit comments

Comments
 (0)