Skip to content

crypto/tls: improved 0-RTT QUIC APIs #63691

Description

@neild

crypto/tls: improved 0-RTT QUIC APIs

This is a proposal to add additional support for 0-RTT (early data) sessions to crypto/tls. This adds additional features on top of #60107 to improve the interactions between the QUIC and TLS layers when resuming sessions with 0-RTT.

Background

QUIC connections negotiate a set of shared state, such as limits on the number of streams each peer may create and the amount of data which may be sent on each stream. When resuming a session with 0-RTT, both the QUIC client and server remember these limits. The client must abide by the remembered limits when sending 0-RTT data, and the server may reject 0-RTT if it is no longer willing to abide by the limits from the previous session.

Application protocols running on QUIC may have similar requirements. For example, HTTP/3 clients may remember stored settings such as QPACK limits.

This means that:

  • Servers add additional data to early data session tickets provided to clients.
  • Servers recover this data from the ticket when resuming a session.
  • Servers can reject early data based on information in the session ticket.
  • Clients add additional data to stored session tickets.
  • Clients recover this data when attempting to resume a session.

It is mostly possible to do this using existing crypto/tls APIs, but with some limitations which I'll discuss below. The following proposal avoids those limitations and provides an approach which is more consistent with the existing QUIC support APIs.

Proposal

type QUICConfig struct { // existing fields unchanged
	// EnableStoreSessionEvent may be set to true to enable the
	// [QUICStoreSession] event for client connections.
	// When this event is enabled, the application is responsible
	// for storing sessions in the client session cache by calling
	// [QUICConn.StoreSession].
	EnableStoreSessionEvent bool
}

const (
	QUICNoEvent QUICEventKind = iota // unchanged

	// QUICResumeSession indicates that a client is attempting to resume a previous session.
	// QUICEvent.SessionState is set.
	//
	// For client connections, this event occurs when the session ticket is selected.
	// For server connections, this event occurs when receiving the client's session ticket.
	//
	// The application may set [QUICEvent.SessionState.EarlyData] to false before the
	// next call to [QUICConn.NextEvent] to decline 0-RTT even if the session supports it.
	QUICResumeSession

	// QUICStoreSession indicates that the server has provided state permitting
	// the client to resume the session.
	// QUICEvent.SessionState is set.
	// The application should use QUICConn.Store session to store the SessionState.
	// The application may modify the SessionState before storing it.
	// This event only occurs on client connections.
	QUICStoreSession
)

type QUICEvent struct { // existing fields unchanged
	// Set for QUICAttemptEarlyData, QUICResumeSession, and QUICStoreSession.
	SessionState *SessionState
}

type QUICSessionTicketOptions struct { // existing fields unchanged
	// Extra contains additional data to store in the session ticket.
	// See the documentation for [SessionState.Extra].
	Extra  [][]byte
}

// RejectEarlyData rejects a client's attempt to resume a prior session.
// It must be called after NextEvent returns a QUICAttemptEarlyData event,
// and before the next call to NextEvent.
func (q *QUICConn) RejectEarlyData() error {}

// StoreSession stores a session previously received in a QUICStoreSession event
// in the ClientSessionCache.
// The application may process additional events or modify the SessionState
// before storing the session.
func (q *QUICConn) StoreSession(ss *SessionState) error {}

To summarize these changes:

  • New events report on the state of session resumption.
  • The QUIC layer may add data to session tickets and session cache entries.
  • The QUIC layer may reject early data.

This permits a QUIC implementation to fully manage 0-RTT through the QUICConn event stream.

Motivation

The current crypto/tls API does permit a QUIC implementation to support 0-RTT. However, there are limitations to the current approach.

A server can use the Config.WrapSession and Config.UnwrapSession hooks to add and remove additional data from session tickets. A client can use a Config.ClientSessionCache wrapper to do the same for stored sessions.

In both cases, the QUIC layer needs to take steps to integrate with WrapSession, UnwrapSession, and ClientSessionCache values provided by the user. Users may use these values for offline storage of sessions, or other purposes.

Practically, this means that a QUIC implementation which accepts a tls.Config from the user will need to clone the config and wrap existing WrapSession/UnwrapSession/ClientSessionCache values. The implementation will need to clone the config for, at a minimum, each server with a unique set of transport parameters and for each client connection.

Cloned tls.Configs do not share session ticket keys. Cloning a server Config using auto-rotation will cause that server to not share keys or a rotation schedule with the original Config. For Configs using manual key rotation, calls to Config.SetSessionTicketKeys on the parent Config will not propagate to the cloned Config. There are some ways around this problem, but they aren't obvious and will have an impact on the QUIC API.

Cloning the client Config is less troublesome; I believe all relevant client state can be cloned safely.

Less importantly, but still relevant, the current API is cumbersome, difficult to use, and inconsistent. The QUIC API added in #44886 is based on an event loop and synchronous operations. Using WrapSession/UnwrapSession/ClientSessionCache for ticket management is callback-oriented and asynchronous. This adds a fair amount of mandatory complexity to the QUIC implementation.

The proposal here avoids these issues by permitting the QUIC layer to pass through a tls.Config from the application unchanged. All necessary interactions between the QUIC and TLS layers are managed through the tls.QUICConn.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions