| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421 |
- // 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 [QUICConn.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 [QUICConn.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 [QUICConn.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
- }
|