Skip to content

Commit cbd8f16

Browse files
committed
crypto/tls: improved 0-RTT QUIC API
Add synchronous management of stored sessions to QUICConn. This adds QUICStoreSession and QUICResumeSession events, permitting a QUIC implementation to handle session resumption as part of its regular event loop processing. Fixes #63691 Change-Id: I9fe16207cc1986eac084869675bc36e227cbf3f0 Reviewed-on: https://go-review.googlesource.com/c/go/+/536935 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Marten Seemann <[email protected]> Reviewed-by: Roland Shoemaker <[email protected]>
1 parent 8524931 commit cbd8f16

File tree

9 files changed

+265
-30
lines changed

9 files changed

+265
-30
lines changed

api/next/63691.txt

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
pkg crypto/tls, const QUICResumeSession = 8 #63691
2+
pkg crypto/tls, const QUICResumeSession QUICEventKind #63691
3+
pkg crypto/tls, const QUICStoreSession = 9 #63691
4+
pkg crypto/tls, const QUICStoreSession QUICEventKind #63691
5+
pkg crypto/tls, method (*QUICConn) StoreSession(*SessionState) error #63691
6+
pkg crypto/tls, type QUICConfig struct, EnableStoreSessionEvent bool #63691
7+
pkg crypto/tls, type QUICEvent struct, SessionState *SessionState #63691
8+
pkg crypto/tls, type QUICSessionTicketOptions struct, Extra [][]uint8 #63691
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
The [QUICConn] type used by QUIC implementations includes new events
2+
reporting on the state of session resumption, and provides a way for
3+
the QUIC layer to add data to session tickets and session cache entries.

src/crypto/tls/handshake_client.go

+8-5
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ func (c *Conn) loadSession(hello *clientHelloMsg) (
366366
return nil, nil, nil, nil
367367
}
368368

369-
hello.sessionTicket = cs.ticket
369+
hello.sessionTicket = session.ticket
370370
return
371371
}
372372

@@ -394,10 +394,12 @@ func (c *Conn) loadSession(hello *clientHelloMsg) (
394394
return nil, nil, nil, nil
395395
}
396396

397-
if c.quic != nil && session.EarlyData {
397+
if c.quic != nil {
398+
c.quicResumeSession(session)
399+
398400
// For 0-RTT, the cipher suite has to match exactly, and we need to be
399401
// offering the same ALPN.
400-
if mutualCipherSuiteTLS13(hello.cipherSuites, session.cipherSuite) != nil {
402+
if session.EarlyData && mutualCipherSuiteTLS13(hello.cipherSuites, session.cipherSuite) != nil {
401403
for _, alpn := range hello.alpnProtocols {
402404
if alpn == session.alpnProtocol {
403405
hello.earlyData = true
@@ -410,7 +412,7 @@ func (c *Conn) loadSession(hello *clientHelloMsg) (
410412
// Set the pre_shared_key extension. See RFC 8446, Section 4.2.11.1.
411413
ticketAge := c.config.time().Sub(time.Unix(int64(session.createdAt), 0))
412414
identity := pskIdentity{
413-
label: cs.ticket,
415+
label: session.ticket,
414416
obfuscatedTicketAge: uint32(ticketAge/time.Millisecond) + session.ageAdd,
415417
}
416418
hello.pskIdentities = []pskIdentity{identity}
@@ -940,8 +942,9 @@ func (hs *clientHandshakeState) saveSessionTicket() error {
940942

941943
session := c.sessionState()
942944
session.secret = hs.masterSecret
945+
session.ticket = hs.ticket
943946

944-
cs := &ClientSessionState{ticket: hs.ticket, session: session}
947+
cs := &ClientSessionState{session: session}
945948
c.config.ClientSessionCache.Put(cacheKey, cs)
946949
return nil
947950
}

src/crypto/tls/handshake_client_test.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -923,7 +923,7 @@ func testResumption(t *testing.T, version uint16) {
923923
}
924924

925925
getTicket := func() []byte {
926-
return clientConfig.ClientSessionCache.(*lruSessionCache).q.Front().Value.(*lruSessionCacheEntry).state.ticket
926+
return clientConfig.ClientSessionCache.(*lruSessionCache).q.Front().Value.(*lruSessionCacheEntry).state.session.ticket
927927
}
928928
deleteTicket := func() {
929929
ticketKey := clientConfig.ClientSessionCache.(*lruSessionCache).q.Front().Value.(*lruSessionCacheEntry).sessionKey
@@ -1107,6 +1107,10 @@ func (c *serializingClientCache) Get(sessionKey string) (session *ClientSessionS
11071107
}
11081108

11091109
func (c *serializingClientCache) Put(sessionKey string, cs *ClientSessionState) {
1110+
if cs == nil {
1111+
c.ticket, c.state = nil, nil
1112+
return
1113+
}
11101114
ticket, state, err := cs.ResumptionState()
11111115
if err != nil {
11121116
c.t.Error(err)

src/crypto/tls/handshake_client_tls13.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -783,8 +783,12 @@ func (c *Conn) handleNewSessionTicket(msg *newSessionTicketMsgTLS13) error {
783783
session.useBy = uint64(c.config.time().Add(lifetime).Unix())
784784
session.ageAdd = msg.ageAdd
785785
session.EarlyData = c.quic != nil && msg.maxEarlyData == 0xffffffff // RFC 9001, Section 4.6.1
786-
cs := &ClientSessionState{ticket: msg.label, session: session}
787-
786+
session.ticket = msg.label
787+
if c.quic != nil && c.quic.enableStoreSessionEvent {
788+
c.quicStoreSession(session)
789+
return nil
790+
}
791+
cs := &ClientSessionState{session: session}
788792
if cacheKey := c.clientSessionCacheKey(); cacheKey != "" {
789793
c.config.ClientSessionCache.Put(cacheKey, cs)
790794
}

src/crypto/tls/handshake_server_tls13.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,12 @@ func (hs *serverHandshakeStateTLS13) checkForResumption() error {
377377
continue
378378
}
379379

380+
if c.quic != nil {
381+
if err := c.quicResumeSession(sessionState); err != nil {
382+
return err
383+
}
384+
}
385+
380386
hs.earlySecret = hs.suite.extract(sessionState.secret, nil)
381387
binderKey := hs.suite.deriveSecret(hs.earlySecret, resumptionBinderLabel, nil)
382388
// Clone the transcript in case a HelloRetryRequest was recorded.
@@ -856,10 +862,10 @@ func (hs *serverHandshakeStateTLS13) sendSessionTickets() error {
856862
if !hs.shouldSendSessionTickets() {
857863
return nil
858864
}
859-
return c.sendSessionTicket(false)
865+
return c.sendSessionTicket(false, nil)
860866
}
861867

862-
func (c *Conn) sendSessionTicket(earlyData bool) error {
868+
func (c *Conn) sendSessionTicket(earlyData bool, extra [][]byte) error {
863869
suite := cipherSuiteTLS13ByID(c.cipherSuite)
864870
if suite == nil {
865871
return errors.New("tls: internal error: unknown cipher suite")
@@ -874,6 +880,7 @@ func (c *Conn) sendSessionTicket(earlyData bool) error {
874880
state := c.sessionState()
875881
state.secret = psk
876882
state.EarlyData = earlyData
883+
state.Extra = extra
877884
if c.config.WrapSession != nil {
878885
var err error
879886
m.label, err = c.config.WrapSession(c.connectionStateLocked(), state)

src/crypto/tls/quic.go

+85-6
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ type QUICConn struct {
4949
// A QUICConfig configures a [QUICConn].
5050
type QUICConfig struct {
5151
TLSConfig *Config
52+
53+
// EnableStoreSessionEvent may be set to true to enable the
54+
// [QUICStoreSession] event for client connections.
55+
// When this event is enabled, sessions are not automatically
56+
// stored in the client session cache.
57+
// The application should use [QUICConn.StoreSession] to store sessions.
58+
EnableStoreSessionEvent bool
5259
}
5360

5461
// A QUICEventKind is a type of operation on a QUIC connection.
@@ -87,10 +94,29 @@ const (
8794
// QUICRejectedEarlyData indicates that the server rejected 0-RTT data even
8895
// if we offered it. It's returned before QUICEncryptionLevelApplication
8996
// keys are returned.
97+
// This event only occurs on client connections.
9098
QUICRejectedEarlyData
9199

92100
// QUICHandshakeDone indicates that the TLS handshake has completed.
93101
QUICHandshakeDone
102+
103+
// QUICResumeSession indicates that a client is attempting to resume a previous session.
104+
// [QUICEvent.SessionState] is set.
105+
//
106+
// For client connections, this event occurs when the session ticket is selected.
107+
// For server connections, this event occurs when receiving the client's session ticket.
108+
//
109+
// The application may set [QUICEvent.SessionState.EarlyData] to false before the
110+
// next call to [QUICConn.NextEvent] to decline 0-RTT even if the session supports it.
111+
QUICResumeSession
112+
113+
// QUICStoreSession indicates that the server has provided state permitting
114+
// the client to resume the session.
115+
// [QUICEvent.SessionState] is set.
116+
// The application should use [QUICConn.Store] session to store the [SessionState].
117+
// The application may modify the [SessionState] before storing it.
118+
// This event only occurs on client connections.
119+
QUICStoreSession
94120
)
95121

96122
// A QUICEvent is an event occurring on a QUIC connection.
@@ -109,6 +135,9 @@ type QUICEvent struct {
109135

110136
// Set for QUICSetReadSecret and QUICSetWriteSecret.
111137
Suite uint16
138+
139+
// Set for QUICResumeSession and QUICStoreSession.
140+
SessionState *SessionState
112141
}
113142

114143
type quicState struct {
@@ -127,34 +156,39 @@ type quicState struct {
127156
cancelc <-chan struct{} // handshake has been canceled
128157
cancel context.CancelFunc
129158

159+
waitingForDrain bool
160+
130161
// readbuf is shared between HandleData and the handshake goroutine.
131162
// HandshakeCryptoData passes ownership to the handshake goroutine by
132163
// reading from signalc, and reclaims ownership by reading from blockedc.
133164
readbuf []byte
134165

135166
transportParams []byte // to send to the peer
167+
168+
enableStoreSessionEvent bool
136169
}
137170

138171
// QUICClient returns a new TLS client side connection using QUICTransport as the
139172
// underlying transport. The config cannot be nil.
140173
//
141174
// The config's MinVersion must be at least TLS 1.3.
142175
func QUICClient(config *QUICConfig) *QUICConn {
143-
return newQUICConn(Client(nil, config.TLSConfig))
176+
return newQUICConn(Client(nil, config.TLSConfig), config)
144177
}
145178

146179
// QUICServer returns a new TLS server side connection using QUICTransport as the
147180
// underlying transport. The config cannot be nil.
148181
//
149182
// The config's MinVersion must be at least TLS 1.3.
150183
func QUICServer(config *QUICConfig) *QUICConn {
151-
return newQUICConn(Server(nil, config.TLSConfig))
184+
return newQUICConn(Server(nil, config.TLSConfig), config)
152185
}
153186

154-
func newQUICConn(conn *Conn) *QUICConn {
187+
func newQUICConn(conn *Conn, config *QUICConfig) *QUICConn {
155188
conn.quic = &quicState{
156-
signalc: make(chan struct{}),
157-
blockedc: make(chan struct{}),
189+
signalc: make(chan struct{}),
190+
blockedc: make(chan struct{}),
191+
enableStoreSessionEvent: config.EnableStoreSessionEvent,
158192
}
159193
conn.quic.events = conn.quic.eventArr[:0]
160194
return &QUICConn{
@@ -190,6 +224,11 @@ func (q *QUICConn) NextEvent() QUICEvent {
190224
// to catch callers erroniously retaining it.
191225
qs.events[last].Data[0] = 0
192226
}
227+
if qs.nextEvent >= len(qs.events) && qs.waitingForDrain {
228+
qs.waitingForDrain = false
229+
<-qs.signalc
230+
<-qs.blockedc
231+
}
193232
if qs.nextEvent >= len(qs.events) {
194233
qs.events = qs.events[:0]
195234
qs.nextEvent = 0
@@ -255,6 +294,7 @@ func (q *QUICConn) HandleData(level QUICEncryptionLevel, data []byte) error {
255294
type QUICSessionTicketOptions struct {
256295
// EarlyData specifies whether the ticket may be used for 0-RTT.
257296
EarlyData bool
297+
Extra [][]byte
258298
}
259299

260300
// SendSessionTicket sends a session ticket to the client.
@@ -272,7 +312,25 @@ func (q *QUICConn) SendSessionTicket(opts QUICSessionTicketOptions) error {
272312
return quicError(errors.New("tls: SendSessionTicket called multiple times"))
273313
}
274314
q.sessionTicketSent = true
275-
return quicError(c.sendSessionTicket(opts.EarlyData))
315+
return quicError(c.sendSessionTicket(opts.EarlyData, opts.Extra))
316+
}
317+
318+
// StoreSession stores a session previously received in a QUICStoreSession event
319+
// in the ClientSessionCache.
320+
// The application may process additional events or modify the SessionState
321+
// before storing the session.
322+
func (q *QUICConn) StoreSession(session *SessionState) error {
323+
c := q.conn
324+
if !c.isClient {
325+
return quicError(errors.New("tls: StoreSessionTicket called on the server"))
326+
}
327+
cacheKey := c.clientSessionCacheKey()
328+
if cacheKey == "" {
329+
return nil
330+
}
331+
cs := &ClientSessionState{session: session}
332+
c.config.ClientSessionCache.Put(cacheKey, cs)
333+
return nil
276334
}
277335

278336
// ConnectionState returns basic TLS details about the connection.
@@ -356,6 +414,27 @@ func (c *Conn) quicWriteCryptoData(level QUICEncryptionLevel, data []byte) {
356414
last.Data = append(last.Data, data...)
357415
}
358416

417+
func (c *Conn) quicResumeSession(session *SessionState) error {
418+
c.quic.events = append(c.quic.events, QUICEvent{
419+
Kind: QUICResumeSession,
420+
SessionState: session,
421+
})
422+
c.quic.waitingForDrain = true
423+
for c.quic.waitingForDrain {
424+
if err := c.quicWaitForSignal(); err != nil {
425+
return err
426+
}
427+
}
428+
return nil
429+
}
430+
431+
func (c *Conn) quicStoreSession(session *SessionState) {
432+
c.quic.events = append(c.quic.events, QUICEvent{
433+
Kind: QUICStoreSession,
434+
SessionState: session,
435+
})
436+
}
437+
359438
func (c *Conn) quicSetTransportParameters(params []byte) {
360439
c.quic.events = append(c.quic.events, QUICEvent{
361440
Kind: QUICTransportParameters,

0 commit comments

Comments
 (0)