QUIC is the transport protocol underlying HTTP/3, as detailed in https://tools.ietf.org/html/draft-ietf-quic-tls-34. A QUIC implementation requires an unusually tight coupling with TLS; quoting the draft:
Rather than a strict layering, these two protocols cooperate: QUIC uses the TLS handshake; TLS uses the reliability, ordered delivery, and record layer provided by QUIC.
The crypto/tls package does not currently provide an API suitable for use by a QUIC implementation. I propose that we add one.
Related background
BoringSSL provides a set of functions specifically for use by QUIC implementations:
https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#QUIC-integration
The quic-go QUIC implementation uses a fork of crypto/tls. The quic-go fork adds a RecordLayer interface, which is quite similar to the BoringSSL API.
A QUIC implementation needs to:
- accept TLS handshake bytes to send to the peer
- provide TLS handshake bytes received from the peer
- learn the read and write secrets and cipher suites for the connection
- receive TLS alerts
- communicate transport parameters in the
quic_transport_parameters TLS extension
BoringSSL and the quic-go fork of crypto/tls both provide these capabilities.
The quic-go fork provide additional extensions to crypto/tls around the handling of early data and session tickets. Those changes are out of scope for this proposal, which addresses only the QUIC-specific need to replace the record layer.
Proposed API changes
package tls
// EncryptionLevel represents a QUIC encryption level used to transmit
// handshake messages.
type EncryptionLevel int
const (
EncryptionLevelInitial = iota
EncryptionLevelEarlyData
EncryptionLevelHandshake
EncryptionLevelApplication
)
// QUICTransport describes hooks used by a QUIC implementation.
//
// If any QUICTransport function returns an error, the QUIC handshake will
// be terminated.
//
// It is an error to call Read, Write, or CloseWrite on a connection with
// a non-nil QUICTransport.
type QUICTransport struct {
// SetReadSecret configures the read secret and cipher suite for the given
// encryption level. It will be called at most once per encryption level.
//
// QUIC ACKs packets at the same level they were received at, except that
// early data (0-RTT) packets trigger application (1-RTT) acks. ACK-writing
// keys will always be installed with SetWriteSecret before the
// packet-reading keys with SetReadSecret, ensuring that QUIC can always
// ACK any packet that it decrypts.
SetReadSecret func(level EncryptionLevel, suite uint16, secret []byte) error
// SetWriteSecret configures the write secret and cipher suite for the
// given encryption level. It will be called at most once per encryption
// level.
//
// See SetReadSecret for additional invariants between packets and their
// ACKs.
SetWriteSecret func(level EncryptionLevel, suite uint16, secret []byte) error
// WriteHandshakeData adds handshake data to the current flight at the
// given encryption level.
//
// A single handshake flight may include data from multiple encryption
// levels. QUIC implementations should defer writing data to the network
// until FlushHandshakeData to better pack QUIC packets into transport
// datagrams.
WriteHandshakeData func(level EncryptionLevel, data []byte) error
// FlushHandshakeData is called when the current flight is complete and
// should be written to the transport. Note that a flight may contain
// data at several encryption levels.
FlushHandshakeData func() error
// ReadHandshakeData is called to request handshake data. It follows the
// same contract as io.Reader's Read method, but returns the encryption
// level of the data as well as the number of bytes read and error.
//
// ReadHandshakeData must not combine data from multiple encryption levels.
//
// ReadHandshakeData must block until at least one byte of data is
// available, and must return as soon as least one byte of data is
// available.
ReadHandshakeData func(p []byte) (level EncryptionLevel, n int, err error)
// Alert sends a fatal alert at the specified encryption level.
//
// If the level is not EncryptionLevelInitial, this function will not
// be called before the write secret for the level is initialized.
Alert func(EncryptionLevel, uint8)
// SetTransportParameters provides the extension_data field of the
// quic_transport_parameters extension sent by the peer. It will
// always be called before the successful completion of a handshake.
SetTransportParameters func([]byte)
// GetTransportParameters returns the extension_data field of the
// quic_transport_parameters extension to send to the peer.
GetTransportParameters func() []byte
}
type Config struct {
// If QUICTransport is non-nil, it replaces the TLS transport layer.
// In this case, MinVersion and MaxVersion must be VersionTLS13.
QUICTransport *QUICTransport
}
// ProcessQUICPostHandshake processes data that has become available
// after the handshake has completed. It must not be called until
// Handshake has returned successfully. It causes a call to the
// QUICTransport ReadHandshakeData function requesting the new data.
func (c *Conn) ProcessQUICPostHandshake() error { … }
QUIC is the transport protocol underlying HTTP/3, as detailed in https://tools.ietf.org/html/draft-ietf-quic-tls-34. A QUIC implementation requires an unusually tight coupling with TLS; quoting the draft:
The
crypto/tlspackage does not currently provide an API suitable for use by a QUIC implementation. I propose that we add one.Related background
BoringSSL provides a set of functions specifically for use by QUIC implementations:
https://commondatastorage.googleapis.com/chromium-boringssl-docs/ssl.h.html#QUIC-integration
The
quic-goQUIC implementation uses a fork ofcrypto/tls. Thequic-gofork adds aRecordLayerinterface, which is quite similar to the BoringSSL API.A QUIC implementation needs to:
quic_transport_parametersTLS extensionBoringSSL and the
quic-gofork ofcrypto/tlsboth provide these capabilities.The
quic-gofork provide additional extensions tocrypto/tlsaround the handling of early data and session tickets. Those changes are out of scope for this proposal, which addresses only the QUIC-specific need to replace the record layer.Proposed API changes