diff options
Diffstat (limited to 'src/crypto/tls/quic.go')
-rw-r--r-- | src/crypto/tls/quic.go | 421 |
1 files changed, 421 insertions, 0 deletions
diff --git a/src/crypto/tls/quic.go b/src/crypto/tls/quic.go new file mode 100644 index 0000000..ba5c2af --- /dev/null +++ b/src/crypto/tls/quic.go @@ -0,0 +1,421 @@ +// Copyright 2023 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package tls + +import ( + "context" + "errors" + "fmt" +) + +// QUICEncryptionLevel represents a QUIC encryption level used to transmit +// handshake messages. +type QUICEncryptionLevel int + +const ( + QUICEncryptionLevelInitial = QUICEncryptionLevel(iota) + QUICEncryptionLevelEarly + QUICEncryptionLevelHandshake + QUICEncryptionLevelApplication +) + +func (l QUICEncryptionLevel) String() string { + switch l { + case QUICEncryptionLevelInitial: + return "Initial" + case QUICEncryptionLevelEarly: + return "Early" + case QUICEncryptionLevelHandshake: + return "Handshake" + case QUICEncryptionLevelApplication: + return "Application" + default: + return fmt.Sprintf("QUICEncryptionLevel(%v)", int(l)) + } +} + +// A QUICConn represents a connection which uses a QUIC implementation as the underlying +// transport as described in RFC 9001. +// +// Methods of QUICConn are not safe for concurrent use. +type QUICConn struct { + conn *Conn + + sessionTicketSent bool +} + +// A QUICConfig configures a QUICConn. +type QUICConfig struct { + TLSConfig *Config +} + +// A QUICEventKind is a type of operation on a QUIC connection. +type QUICEventKind int + +const ( + // QUICNoEvent indicates that there are no events available. + QUICNoEvent QUICEventKind = iota + + // QUICSetReadSecret and QUICSetWriteSecret provide the read and write + // secrets for a given encryption level. + // QUICEvent.Level, QUICEvent.Data, and QUICEvent.Suite are set. + // + // Secrets for the Initial encryption level are derived from the initial + // destination connection ID, and are not provided by the QUICConn. + QUICSetReadSecret + QUICSetWriteSecret + + // QUICWriteData provides data to send to the peer in CRYPTO frames. + // QUICEvent.Data is set. + QUICWriteData + + // QUICTransportParameters provides the peer's QUIC transport parameters. + // QUICEvent.Data is set. + QUICTransportParameters + + // QUICTransportParametersRequired indicates that the caller must provide + // QUIC transport parameters to send to the peer. The caller should set + // the transport parameters with QUICConn.SetTransportParameters and call + // QUICConn.NextEvent again. + // + // If transport parameters are set before calling QUICConn.Start, the + // connection will never generate a QUICTransportParametersRequired event. + QUICTransportParametersRequired + + // QUICRejectedEarlyData indicates that the server rejected 0-RTT data even + // if we offered it. It's returned before QUICEncryptionLevelApplication + // keys are returned. + QUICRejectedEarlyData + + // QUICHandshakeDone indicates that the TLS handshake has completed. + QUICHandshakeDone +) + +// A QUICEvent is an event occurring on a QUIC connection. +// +// The type of event is specified by the Kind field. +// The contents of the other fields are kind-specific. +type QUICEvent struct { + Kind QUICEventKind + + // Set for QUICSetReadSecret, QUICSetWriteSecret, and QUICWriteData. + Level QUICEncryptionLevel + + // Set for QUICTransportParameters, QUICSetReadSecret, QUICSetWriteSecret, and QUICWriteData. + // The contents are owned by crypto/tls, and are valid until the next NextEvent call. + Data []byte + + // Set for QUICSetReadSecret and QUICSetWriteSecret. + Suite uint16 +} + +type quicState struct { + events []QUICEvent + nextEvent int + + // eventArr is a statically allocated event array, large enough to handle + // the usual maximum number of events resulting from a single call: transport + // parameters, Initial data, Early read secret, Handshake write and read + // secrets, Handshake data, Application write secret, Application data. + eventArr [8]QUICEvent + + started bool + signalc chan struct{} // handshake data is available to be read + blockedc chan struct{} // handshake is waiting for data, closed when done + cancelc <-chan struct{} // handshake has been canceled + cancel context.CancelFunc + + // readbuf is shared between HandleData and the handshake goroutine. + // HandshakeCryptoData passes ownership to the handshake goroutine by + // reading from signalc, and reclaims ownership by reading from blockedc. + readbuf []byte + + transportParams []byte // to send to the peer +} + +// QUICClient returns a new TLS client side connection using QUICTransport as the +// underlying transport. The config cannot be nil. +// +// The config's MinVersion must be at least TLS 1.3. +func QUICClient(config *QUICConfig) *QUICConn { + return newQUICConn(Client(nil, config.TLSConfig)) +} + +// QUICServer returns a new TLS server side connection using QUICTransport as the +// underlying transport. The config cannot be nil. +// +// The config's MinVersion must be at least TLS 1.3. +func QUICServer(config *QUICConfig) *QUICConn { + return newQUICConn(Server(nil, config.TLSConfig)) +} + +func newQUICConn(conn *Conn) *QUICConn { + conn.quic = &quicState{ + signalc: make(chan struct{}), + blockedc: make(chan struct{}), + } + conn.quic.events = conn.quic.eventArr[:0] + return &QUICConn{ + conn: conn, + } +} + +// Start starts the client or server handshake protocol. +// It may produce connection events, which may be read with NextEvent. +// +// Start must be called at most once. +func (q *QUICConn) Start(ctx context.Context) error { + if q.conn.quic.started { + return quicError(errors.New("tls: Start called more than once")) + } + q.conn.quic.started = true + if q.conn.config.MinVersion < VersionTLS13 { + return quicError(errors.New("tls: Config MinVersion must be at least TLS 1.13")) + } + go q.conn.HandshakeContext(ctx) + if _, ok := <-q.conn.quic.blockedc; !ok { + return q.conn.handshakeErr + } + return nil +} + +// NextEvent returns the next event occurring on the connection. +// It returns an event with a Kind of QUICNoEvent when no events are available. +func (q *QUICConn) NextEvent() QUICEvent { + qs := q.conn.quic + if last := qs.nextEvent - 1; last >= 0 && len(qs.events[last].Data) > 0 { + // Write over some of the previous event's data, + // to catch callers erroniously retaining it. + qs.events[last].Data[0] = 0 + } + if qs.nextEvent >= len(qs.events) { + qs.events = qs.events[:0] + qs.nextEvent = 0 + return QUICEvent{Kind: QUICNoEvent} + } + e := qs.events[qs.nextEvent] + qs.events[qs.nextEvent] = QUICEvent{} // zero out references to data + qs.nextEvent++ + return e +} + +// Close closes the connection and stops any in-progress handshake. +func (q *QUICConn) Close() error { + if q.conn.quic.cancel == nil { + return nil // never started + } + q.conn.quic.cancel() + for range q.conn.quic.blockedc { + // Wait for the handshake goroutine to return. + } + return q.conn.handshakeErr +} + +// HandleData handles handshake bytes received from the peer. +// It may produce connection events, which may be read with NextEvent. +func (q *QUICConn) HandleData(level QUICEncryptionLevel, data []byte) error { + c := q.conn + if c.in.level != level { + return quicError(c.in.setErrorLocked(errors.New("tls: handshake data received at wrong level"))) + } + c.quic.readbuf = data + <-c.quic.signalc + _, ok := <-c.quic.blockedc + if ok { + // The handshake goroutine is waiting for more data. + return nil + } + // The handshake goroutine has exited. + c.handshakeMutex.Lock() + defer c.handshakeMutex.Unlock() + c.hand.Write(c.quic.readbuf) + c.quic.readbuf = nil + for q.conn.hand.Len() >= 4 && q.conn.handshakeErr == nil { + b := q.conn.hand.Bytes() + n := int(b[1])<<16 | int(b[2])<<8 | int(b[3]) + if n > maxHandshake { + q.conn.handshakeErr = fmt.Errorf("tls: handshake message of length %d bytes exceeds maximum of %d bytes", n, maxHandshake) + break + } + if len(b) < 4+n { + return nil + } + if err := q.conn.handlePostHandshakeMessage(); err != nil { + q.conn.handshakeErr = err + } + } + if q.conn.handshakeErr != nil { + return quicError(q.conn.handshakeErr) + } + return nil +} + +type QUICSessionTicketOptions struct { + // EarlyData specifies whether the ticket may be used for 0-RTT. + EarlyData bool +} + +// SendSessionTicket sends a session ticket to the client. +// It produces connection events, which may be read with NextEvent. +// Currently, it can only be called once. +func (q *QUICConn) SendSessionTicket(opts QUICSessionTicketOptions) error { + c := q.conn + if !c.isHandshakeComplete.Load() { + return quicError(errors.New("tls: SendSessionTicket called before handshake completed")) + } + if c.isClient { + return quicError(errors.New("tls: SendSessionTicket called on the client")) + } + if q.sessionTicketSent { + return quicError(errors.New("tls: SendSessionTicket called multiple times")) + } + q.sessionTicketSent = true + return quicError(c.sendSessionTicket(opts.EarlyData)) +} + +// ConnectionState returns basic TLS details about the connection. +func (q *QUICConn) ConnectionState() ConnectionState { + return q.conn.ConnectionState() +} + +// SetTransportParameters sets the transport parameters to send to the peer. +// +// Server connections may delay setting the transport parameters until after +// receiving the client's transport parameters. See QUICTransportParametersRequired. +func (q *QUICConn) SetTransportParameters(params []byte) { + if params == nil { + params = []byte{} + } + q.conn.quic.transportParams = params + if q.conn.quic.started { + <-q.conn.quic.signalc + <-q.conn.quic.blockedc + } +} + +// quicError ensures err is an AlertError. +// If err is not already, quicError wraps it with alertInternalError. +func quicError(err error) error { + if err == nil { + return nil + } + var ae AlertError + if errors.As(err, &ae) { + return err + } + var a alert + if !errors.As(err, &a) { + a = alertInternalError + } + // Return an error wrapping the original error and an AlertError. + // Truncate the text of the alert to 0 characters. + return fmt.Errorf("%w%.0w", err, AlertError(a)) +} + +func (c *Conn) quicReadHandshakeBytes(n int) error { + for c.hand.Len() < n { + if err := c.quicWaitForSignal(); err != nil { + return err + } + } + return nil +} + +func (c *Conn) quicSetReadSecret(level QUICEncryptionLevel, suite uint16, secret []byte) { + c.quic.events = append(c.quic.events, QUICEvent{ + Kind: QUICSetReadSecret, + Level: level, + Suite: suite, + Data: secret, + }) +} + +func (c *Conn) quicSetWriteSecret(level QUICEncryptionLevel, suite uint16, secret []byte) { + c.quic.events = append(c.quic.events, QUICEvent{ + Kind: QUICSetWriteSecret, + Level: level, + Suite: suite, + Data: secret, + }) +} + +func (c *Conn) quicWriteCryptoData(level QUICEncryptionLevel, data []byte) { + var last *QUICEvent + if len(c.quic.events) > 0 { + last = &c.quic.events[len(c.quic.events)-1] + } + if last == nil || last.Kind != QUICWriteData || last.Level != level { + c.quic.events = append(c.quic.events, QUICEvent{ + Kind: QUICWriteData, + Level: level, + }) + last = &c.quic.events[len(c.quic.events)-1] + } + last.Data = append(last.Data, data...) +} + +func (c *Conn) quicSetTransportParameters(params []byte) { + c.quic.events = append(c.quic.events, QUICEvent{ + Kind: QUICTransportParameters, + Data: params, + }) +} + +func (c *Conn) quicGetTransportParameters() ([]byte, error) { + if c.quic.transportParams == nil { + c.quic.events = append(c.quic.events, QUICEvent{ + Kind: QUICTransportParametersRequired, + }) + } + for c.quic.transportParams == nil { + if err := c.quicWaitForSignal(); err != nil { + return nil, err + } + } + return c.quic.transportParams, nil +} + +func (c *Conn) quicHandshakeComplete() { + c.quic.events = append(c.quic.events, QUICEvent{ + Kind: QUICHandshakeDone, + }) +} + +func (c *Conn) quicRejectedEarlyData() { + c.quic.events = append(c.quic.events, QUICEvent{ + Kind: QUICRejectedEarlyData, + }) +} + +// quicWaitForSignal notifies the QUICConn that handshake progress is blocked, +// and waits for a signal that the handshake should proceed. +// +// The handshake may become blocked waiting for handshake bytes +// or for the user to provide transport parameters. +func (c *Conn) quicWaitForSignal() error { + // Drop the handshake mutex while blocked to allow the user + // to call ConnectionState before the handshake completes. + c.handshakeMutex.Unlock() + defer c.handshakeMutex.Lock() + // Send on blockedc to notify the QUICConn that the handshake is blocked. + // Exported methods of QUICConn wait for the handshake to become blocked + // before returning to the user. + select { + case c.quic.blockedc <- struct{}{}: + case <-c.quic.cancelc: + return c.sendAlertLocked(alertCloseNotify) + } + // The QUICConn reads from signalc to notify us that the handshake may + // be able to proceed. (The QUICConn reads, because we close signalc to + // indicate that the handshake has completed.) + select { + case c.quic.signalc <- struct{}{}: + c.hand.Write(c.quic.readbuf) + c.quic.readbuf = nil + case <-c.quic.cancelc: + return c.sendAlertLocked(alertCloseNotify) + } + return nil +} |