Skip to content

Commit

Permalink
crypto/tls: add QUIC 0-RTT APIs
Browse files Browse the repository at this point in the history
Fixes #60107

Change-Id: I158b1c2d80d8ebb5ed7a8e6f313f69060754e220
Reviewed-on: https://go-review.googlesource.com/c/go/+/496995
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Roland Shoemaker <roland@golang.org>
Run-TryBot: Filippo Valsorda <filippo@golang.org>
Auto-Submit: Filippo Valsorda <filippo@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
  • Loading branch information
FiloSottile authored and gopherbot committed May 25, 2023
1 parent a17de43 commit 869da4a
Show file tree
Hide file tree
Showing 42 changed files with 2,416 additions and 2,235 deletions.
6 changes: 3 additions & 3 deletions api/next/44886.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
pkg crypto/tls, const QUICEncryptionLevelApplication = 2 #44886
pkg crypto/tls, const QUICEncryptionLevelApplication = 3 #44886
pkg crypto/tls, const QUICEncryptionLevelApplication QUICEncryptionLevel #44886
pkg crypto/tls, const QUICEncryptionLevelHandshake = 1 #44886
pkg crypto/tls, const QUICEncryptionLevelHandshake = 2 #44886
pkg crypto/tls, const QUICEncryptionLevelHandshake QUICEncryptionLevel #44886
pkg crypto/tls, const QUICEncryptionLevelInitial = 0 #44886
pkg crypto/tls, const QUICEncryptionLevelInitial QUICEncryptionLevel #44886
pkg crypto/tls, const QUICHandshakeDone = 6 #44886
pkg crypto/tls, const QUICHandshakeDone = 7 #44886
pkg crypto/tls, const QUICHandshakeDone QUICEventKind #44886
pkg crypto/tls, const QUICNoEvent = 0 #44886
pkg crypto/tls, const QUICNoEvent QUICEventKind #44886
Expand Down
6 changes: 6 additions & 0 deletions api/next/60107.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
pkg crypto/tls, const QUICEncryptionLevelEarly = 1 #60107
pkg crypto/tls, const QUICEncryptionLevelEarly QUICEncryptionLevel #60107
pkg crypto/tls, const QUICRejectedEarlyData = 6 #60107
pkg crypto/tls, const QUICRejectedEarlyData QUICEventKind #60107
pkg crypto/tls, method (*QUICConn) SendSessionTicket(bool) error #60107
pkg crypto/tls, type SessionState struct, EarlyData bool #60107
2 changes: 1 addition & 1 deletion src/crypto/tls/conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ type Conn struct {
// ekm is a closure for exporting keying material.
ekm func(label string, context []byte, length int) ([]byte, error)
// resumptionSecret is the resumption_master_secret for handling
// NewSessionTicket messages. nil if config.SessionTicketsDisabled.
// or sending NewSessionTicket messages.
resumptionSecret []byte

// ticketKeys is the set of active session ticket keys for this
Expand Down
23 changes: 23 additions & 0 deletions src/crypto/tls/handshake_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,16 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) {
return err
}

if hello.earlyData {
suite := cipherSuiteTLS13ByID(session.cipherSuite)
transcript := suite.hash.New()
if err := transcriptMsg(hello, transcript); err != nil {
return err
}
earlyTrafficSecret := suite.deriveSecret(earlySecret, clientEarlyTrafficLabel, transcript)
c.quicSetWriteSecret(QUICEncryptionLevelEarly, suite.id, earlyTrafficSecret)
}

// serverHelloMsg is not included in the transcript
msg, err := c.readHandshake(nil)
if err != nil {
Expand Down Expand Up @@ -359,6 +369,19 @@ func (c *Conn) loadSession(hello *clientHelloMsg) (
return nil, nil, nil, nil
}

if c.quic != nil && session.EarlyData {
// For 0-RTT, the cipher suite has to match exactly, and we need to be
// offering the same ALPN.
if mutualCipherSuite(hello.cipherSuites, session.cipherSuite) != nil {
for _, alpn := range hello.alpnProtocols {
if alpn == session.alpnProtocol {
hello.earlyData = true
break
}
}
}
}

// Set the pre_shared_key extension. See RFC 8446, Section 4.2.11.1.
ticketAge := c.config.time().Sub(time.Unix(int64(session.createdAt), 0))
identity := pskIdentity{
Expand Down
30 changes: 30 additions & 0 deletions src/crypto/tls/handshake_client_tls13.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,11 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error {
}
}

if hs.hello.earlyData {
hs.hello.earlyData = false
c.quicRejectedEarlyData()
}

if _, err := hs.c.writeHandshakeRecord(hs.hello, hs.transcript); err != nil {
return err
}
Expand Down Expand Up @@ -455,6 +460,24 @@ func (hs *clientHandshakeStateTLS13) readServerParameters() error {
}
}

if !hs.hello.earlyData && encryptedExtensions.earlyData {
c.sendAlert(alertUnsupportedExtension)
return errors.New("tls: server sent an unexpected early_data extension")
}
if hs.hello.earlyData && !encryptedExtensions.earlyData {
c.quicRejectedEarlyData()
}
if encryptedExtensions.earlyData {
if hs.session.cipherSuite != c.cipherSuite {
c.sendAlert(alertHandshakeFailure)
return errors.New("tls: server accepted 0-RTT with the wrong cipher suite")
}
if hs.session.alpnProtocol != c.clientProtocol {
c.sendAlert(alertHandshakeFailure)
return errors.New("tls: server accepted 0-RTT with the wrong ALPN")
}
}

return nil
}

Expand Down Expand Up @@ -715,6 +738,12 @@ func (c *Conn) handleNewSessionTicket(msg *newSessionTicketMsgTLS13) error {
return errors.New("tls: received a session ticket with invalid lifetime")
}

// RFC 9001, Section 4.6.1
if c.quic != nil && msg.maxEarlyData != 0 && msg.maxEarlyData != 0xffffffff {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: invalid early data for QUIC connection")
}

cipherSuite := cipherSuiteTLS13ByID(c.cipherSuite)
if cipherSuite == nil || c.resumptionSecret == nil {
return c.sendAlert(alertInternalError)
Expand All @@ -731,6 +760,7 @@ func (c *Conn) handleNewSessionTicket(msg *newSessionTicketMsgTLS13) error {
session.secret = psk
session.useBy = uint64(c.config.time().Add(lifetime).Unix())
session.ageAdd = msg.ageAdd
session.EarlyData = c.quic != nil && msg.maxEarlyData == 0xffffffff // RFC 9001, Section 4.6.1
cs := &ClientSessionState{ticket: msg.label, session: session}

if cacheKey := c.clientSessionCacheKey(); cacheKey != "" {
Expand Down
9 changes: 9 additions & 0 deletions src/crypto/tls/handshake_messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,7 @@ type encryptedExtensionsMsg struct {
raw []byte
alpnProtocol string
quicTransportParameters []byte
earlyData bool
}

func (m *encryptedExtensionsMsg) marshal() ([]byte, error) {
Expand Down Expand Up @@ -904,6 +905,11 @@ func (m *encryptedExtensionsMsg) marshal() ([]byte, error) {
b.AddBytes(m.quicTransportParameters)
})
}
if m.earlyData {
// RFC 8446, Section 4.2.10
b.AddUint16(extensionEarlyData)
b.AddUint16(0) // empty extension_data
}
})
})

Expand Down Expand Up @@ -947,6 +953,9 @@ func (m *encryptedExtensionsMsg) unmarshal(data []byte) bool {
if !extData.CopyBytes(m.quicTransportParameters) {
return false
}
case extensionEarlyData:
// RFC 8446, Section 4.2.10
m.earlyData = true
default:
// Ignore unknown extensions.
continue
Expand Down
9 changes: 9 additions & 0 deletions src/crypto/tls/handshake_messages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,9 @@ func (*encryptedExtensionsMsg) Generate(rand *rand.Rand, size int) reflect.Value
if rand.Intn(10) > 5 {
m.alpnProtocol = randomString(rand.Intn(32)+1, rand)
}
if rand.Intn(10) > 5 {
m.earlyData = true
}

return reflect.ValueOf(m)
}
Expand Down Expand Up @@ -347,6 +350,9 @@ func (*SessionState) Generate(rand *rand.Rand, size int) reflect.Value {
s.createdAt = uint64(rand.Int63())
s.secret = randomBytes(rand.Intn(100)+1, rand)
s.Extra = randomBytes(rand.Intn(100), rand)
if rand.Intn(10) > 5 {
s.EarlyData = true
}
if s.isClient || rand.Intn(10) > 5 {
if rand.Intn(10) > 5 {
s.peerCertificates = sessionTestCerts
Expand All @@ -362,6 +368,9 @@ func (*SessionState) Generate(rand *rand.Rand, size int) reflect.Value {
s.scts = append(s.scts, randomBytes(rand.Intn(500)+1, rand))
}
}
if rand.Intn(10) > 5 && s.EarlyData {
s.alpnProtocol = string(randomBytes(rand.Intn(10), rand))
}
if s.isClient {
for i := 0; i < rand.Intn(3); i++ {
if rand.Intn(10) > 5 {
Expand Down
71 changes: 53 additions & 18 deletions src/crypto/tls/handshake_server_tls13.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type serverHandshakeStateTLS13 struct {
hello *serverHelloMsg
sentDummyCCS bool
usingPSK bool
earlyData bool
suite *cipherSuiteTLS13
cert *Certificate
sigAlg SignatureScheme
Expand Down Expand Up @@ -139,7 +140,12 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error {
return errors.New("tls: initial handshake had non-empty renegotiation extension")
}

if hs.clientHello.earlyData {
if hs.clientHello.earlyData && c.quic != nil {
if len(hs.clientHello.pskIdentities) == 0 {
c.sendAlert(alertIllegalParameter)
return errors.New("tls: early_data without pre_shared_key")
}
} else if hs.clientHello.earlyData {
// See RFC 8446, Section 4.2.10 for the complicated behavior required
// here. The scenario is that a different server at our address offered
// to accept early data in the past, which we can't handle. For now, all
Expand Down Expand Up @@ -226,6 +232,13 @@ GroupSelection:
return errors.New("tls: invalid client key share")
}

selectedProto, err := negotiateALPN(c.config.NextProtos, hs.clientHello.alpnProtocols, c.quic != nil)
if err != nil {
c.sendAlert(alertNoApplicationProtocol)
return err
}
c.clientProtocol = selectedProto

if c.quic != nil {
if hs.clientHello.quicTransportParameters == nil {
// RFC 9001 Section 8.2.
Expand Down Expand Up @@ -306,10 +319,6 @@ func (hs *serverHandshakeStateTLS13) checkForResumption() error {
continue
}

// We don't check the obfuscated ticket age because it's affected by
// clock skew and it's only a freshness signal useful for shrinking the
// window for replay attacks, which don't affect us as we don't do 0-RTT.

pskSuite := cipherSuiteTLS13ByID(sessionState.cipherSuite)
if pskSuite == nil || pskSuite.hash != hs.suite.hash {
continue
Expand Down Expand Up @@ -347,6 +356,19 @@ func (hs *serverHandshakeStateTLS13) checkForResumption() error {
return errors.New("tls: invalid PSK binder")
}

if c.quic != nil && hs.clientHello.earlyData && i == 0 &&
sessionState.EarlyData && sessionState.cipherSuite == hs.suite.id &&
sessionState.alpnProtocol == c.clientProtocol {
hs.earlyData = true

transcript := hs.suite.hash.New()
if err := transcriptMsg(hs.clientHello, transcript); err != nil {
return err
}
earlyTrafficSecret := hs.suite.deriveSecret(hs.earlySecret, clientEarlyTrafficLabel, transcript)
c.quicSetReadSecret(QUICEncryptionLevelEarly, hs.suite.id, earlyTrafficSecret)
}

c.didResume = true
if err := c.processCertsFromClient(sessionState.certificate()); err != nil {
return err
Expand Down Expand Up @@ -605,21 +627,15 @@ func (hs *serverHandshakeStateTLS13) sendServerParameters() error {
}

encryptedExtensions := new(encryptedExtensionsMsg)

selectedProto, err := negotiateALPN(c.config.NextProtos, hs.clientHello.alpnProtocols, c.quic != nil)
if err != nil {
c.sendAlert(alertNoApplicationProtocol)
return err
}
encryptedExtensions.alpnProtocol = selectedProto
c.clientProtocol = selectedProto
encryptedExtensions.alpnProtocol = c.clientProtocol

if c.quic != nil {
p, err := c.quicGetTransportParameters()
if err != nil {
return err
}
encryptedExtensions.quicTransportParameters = p
encryptedExtensions.earlyData = hs.earlyData
}

if _, err := hs.c.writeHandshakeRecord(encryptedExtensions, hs.transcript); err != nil {
Expand Down Expand Up @@ -760,6 +776,11 @@ func (hs *serverHandshakeStateTLS13) shouldSendSessionTickets() bool {
return false
}

// QUIC tickets are sent by QUICConn.SendSessionTicket, not automatically.
if hs.c.quic != nil {
return false
}

// Don't send tickets the client wouldn't use. See RFC 8446, Section 4.2.9.
for _, pskMode := range hs.clientHello.pskModes {
if pskMode == pskModeDHE {
Expand All @@ -780,16 +801,24 @@ func (hs *serverHandshakeStateTLS13) sendSessionTickets() error {
return err
}

c.resumptionSecret = hs.suite.deriveSecret(hs.masterSecret,
resumptionLabel, hs.transcript)

if !hs.shouldSendSessionTickets() {
return nil
}
return c.sendSessionTicket(false)
}

resumptionSecret := hs.suite.deriveSecret(hs.masterSecret,
resumptionLabel, hs.transcript)
func (c *Conn) sendSessionTicket(earlyData bool) error {
suite := cipherSuiteTLS13ByID(c.cipherSuite)
if suite == nil {
return errors.New("tls: internal error: unknown cipher suite")
}
// ticket_nonce, which must be unique per connection, is always left at
// zero because we only ever send one ticket per connection.
psk := hs.suite.expandLabel(resumptionSecret, "resumption",
nil, hs.suite.hash.Size())
psk := suite.expandLabel(c.resumptionSecret, "resumption",
nil, suite.hash.Size())

m := new(newSessionTicketMsgTLS13)

Expand All @@ -798,6 +827,7 @@ func (hs *serverHandshakeStateTLS13) sendSessionTickets() error {
return err
}
state.secret = psk
state.EarlyData = earlyData
if c.config.WrapSession != nil {
m.label, err = c.config.WrapSession(c.connectionStateLocked(), state)
if err != nil {
Expand All @@ -820,12 +850,17 @@ func (hs *serverHandshakeStateTLS13) sendSessionTickets() error {
// The value is not stored anywhere; we never need to check the ticket age
// because 0-RTT is not supported.
ageAdd := make([]byte, 4)
_, err = hs.c.config.rand().Read(ageAdd)
_, err = c.config.rand().Read(ageAdd)
if err != nil {
return err
}
m.ageAdd = binary.LittleEndian.Uint32(ageAdd)

if earlyData {
// RFC 9001, Section 4.6.1
m.maxEarlyData = 0xffffffff
}

if _, err := c.writeHandshakeRecord(m, nil); err != nil {
return err
}
Expand Down
1 change: 1 addition & 0 deletions src/crypto/tls/key_schedule.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (

const (
resumptionBinderLabel = "res binder"
clientEarlyTrafficLabel = "c e traffic"
clientHandshakeTrafficLabel = "c hs traffic"
serverHandshakeTrafficLabel = "s hs traffic"
clientApplicationTrafficLabel = "c ap traffic"
Expand Down
Loading

0 comments on commit 869da4a

Please sign in to comment.