mirror of
https://github.com/XTLS/REALITY.git
synced 2025-08-23 15:08:37 +00:00
crypto/tls: add QUIC 0-RTT APIs
Fixes #60107 Change-Id: I158b1c2d80d8ebb5ed7a8e6f313f69060754e220 Reviewed-on: https://go-review.googlesource.com/c/go/+/496995 Reviewed-by: Damien Neil <dneil@google.com> Reviewed-by: Roland Shoemaker <roland@golang.org> Run-TryBot: Filippo Valsorda <filippo@golang.org> Auto-Submit: Filippo Valsorda <filippo@golang.org> TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
parent
e6eabfc665
commit
aa68126eeb
2
conn.go
2
conn.go
@ -70,7 +70,7 @@ type Conn struct {
|
|||||||
// ekm is a closure for exporting keying material.
|
// ekm is a closure for exporting keying material.
|
||||||
ekm func(label string, context []byte, length int) ([]byte, error)
|
ekm func(label string, context []byte, length int) ([]byte, error)
|
||||||
// resumptionSecret is the resumption_master_secret for handling
|
// resumptionSecret is the resumption_master_secret for handling
|
||||||
// NewSessionTicket messages. nil if config.SessionTicketsDisabled.
|
// or sending NewSessionTicket messages.
|
||||||
resumptionSecret []byte
|
resumptionSecret []byte
|
||||||
|
|
||||||
// ticketKeys is the set of active session ticket keys for this
|
// ticketKeys is the set of active session ticket keys for this
|
||||||
|
@ -202,6 +202,16 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if hello.earlyData {
|
||||||
|
suite := cipherSuiteTLS13ByID(session.cipherSuite)
|
||||||
|
transcript := suite.hash.New()
|
||||||
|
if err := transcriptMsg(hello, transcript); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
earlyTrafficSecret := suite.deriveSecret(earlySecret, clientEarlyTrafficLabel, transcript)
|
||||||
|
c.quicSetWriteSecret(QUICEncryptionLevelEarly, suite.id, earlyTrafficSecret)
|
||||||
|
}
|
||||||
|
|
||||||
// serverHelloMsg is not included in the transcript
|
// serverHelloMsg is not included in the transcript
|
||||||
msg, err := c.readHandshake(nil)
|
msg, err := c.readHandshake(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -359,6 +369,19 @@ func (c *Conn) loadSession(hello *clientHelloMsg) (
|
|||||||
return nil, nil, nil, nil
|
return nil, nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.quic != nil && session.EarlyData {
|
||||||
|
// For 0-RTT, the cipher suite has to match exactly, and we need to be
|
||||||
|
// offering the same ALPN.
|
||||||
|
if mutualCipherSuite(hello.cipherSuites, session.cipherSuite) != nil {
|
||||||
|
for _, alpn := range hello.alpnProtocols {
|
||||||
|
if alpn == session.alpnProtocol {
|
||||||
|
hello.earlyData = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Set the pre_shared_key extension. See RFC 8446, Section 4.2.11.1.
|
// Set the pre_shared_key extension. See RFC 8446, Section 4.2.11.1.
|
||||||
ticketAge := c.config.time().Sub(time.Unix(int64(session.createdAt), 0))
|
ticketAge := c.config.time().Sub(time.Unix(int64(session.createdAt), 0))
|
||||||
identity := pskIdentity{
|
identity := pskIdentity{
|
||||||
|
@ -281,6 +281,11 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if hs.hello.earlyData {
|
||||||
|
hs.hello.earlyData = false
|
||||||
|
c.quicRejectedEarlyData()
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := hs.c.writeHandshakeRecord(hs.hello, hs.transcript); err != nil {
|
if _, err := hs.c.writeHandshakeRecord(hs.hello, hs.transcript); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -455,6 +460,24 @@ func (hs *clientHandshakeStateTLS13) readServerParameters() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !hs.hello.earlyData && encryptedExtensions.earlyData {
|
||||||
|
c.sendAlert(alertUnsupportedExtension)
|
||||||
|
return errors.New("tls: server sent an unexpected early_data extension")
|
||||||
|
}
|
||||||
|
if hs.hello.earlyData && !encryptedExtensions.earlyData {
|
||||||
|
c.quicRejectedEarlyData()
|
||||||
|
}
|
||||||
|
if encryptedExtensions.earlyData {
|
||||||
|
if hs.session.cipherSuite != c.cipherSuite {
|
||||||
|
c.sendAlert(alertHandshakeFailure)
|
||||||
|
return errors.New("tls: server accepted 0-RTT with the wrong cipher suite")
|
||||||
|
}
|
||||||
|
if hs.session.alpnProtocol != c.clientProtocol {
|
||||||
|
c.sendAlert(alertHandshakeFailure)
|
||||||
|
return errors.New("tls: server accepted 0-RTT with the wrong ALPN")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -715,6 +738,12 @@ func (c *Conn) handleNewSessionTicket(msg *newSessionTicketMsgTLS13) error {
|
|||||||
return errors.New("tls: received a session ticket with invalid lifetime")
|
return errors.New("tls: received a session ticket with invalid lifetime")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RFC 9001, Section 4.6.1
|
||||||
|
if c.quic != nil && msg.maxEarlyData != 0 && msg.maxEarlyData != 0xffffffff {
|
||||||
|
c.sendAlert(alertIllegalParameter)
|
||||||
|
return errors.New("tls: invalid early data for QUIC connection")
|
||||||
|
}
|
||||||
|
|
||||||
cipherSuite := cipherSuiteTLS13ByID(c.cipherSuite)
|
cipherSuite := cipherSuiteTLS13ByID(c.cipherSuite)
|
||||||
if cipherSuite == nil || c.resumptionSecret == nil {
|
if cipherSuite == nil || c.resumptionSecret == nil {
|
||||||
return c.sendAlert(alertInternalError)
|
return c.sendAlert(alertInternalError)
|
||||||
@ -731,6 +760,7 @@ func (c *Conn) handleNewSessionTicket(msg *newSessionTicketMsgTLS13) error {
|
|||||||
session.secret = psk
|
session.secret = psk
|
||||||
session.useBy = uint64(c.config.time().Add(lifetime).Unix())
|
session.useBy = uint64(c.config.time().Add(lifetime).Unix())
|
||||||
session.ageAdd = msg.ageAdd
|
session.ageAdd = msg.ageAdd
|
||||||
|
session.EarlyData = c.quic != nil && msg.maxEarlyData == 0xffffffff // RFC 9001, Section 4.6.1
|
||||||
cs := &ClientSessionState{ticket: msg.label, session: session}
|
cs := &ClientSessionState{ticket: msg.label, session: session}
|
||||||
|
|
||||||
if cacheKey := c.clientSessionCacheKey(); cacheKey != "" {
|
if cacheKey := c.clientSessionCacheKey(); cacheKey != "" {
|
||||||
|
@ -876,6 +876,7 @@ type encryptedExtensionsMsg struct {
|
|||||||
raw []byte
|
raw []byte
|
||||||
alpnProtocol string
|
alpnProtocol string
|
||||||
quicTransportParameters []byte
|
quicTransportParameters []byte
|
||||||
|
earlyData bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *encryptedExtensionsMsg) marshal() ([]byte, error) {
|
func (m *encryptedExtensionsMsg) marshal() ([]byte, error) {
|
||||||
@ -904,6 +905,11 @@ func (m *encryptedExtensionsMsg) marshal() ([]byte, error) {
|
|||||||
b.AddBytes(m.quicTransportParameters)
|
b.AddBytes(m.quicTransportParameters)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if m.earlyData {
|
||||||
|
// RFC 8446, Section 4.2.10
|
||||||
|
b.AddUint16(extensionEarlyData)
|
||||||
|
b.AddUint16(0) // empty extension_data
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -947,6 +953,9 @@ func (m *encryptedExtensionsMsg) unmarshal(data []byte) bool {
|
|||||||
if !extData.CopyBytes(m.quicTransportParameters) {
|
if !extData.CopyBytes(m.quicTransportParameters) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
case extensionEarlyData:
|
||||||
|
// RFC 8446, Section 4.2.10
|
||||||
|
m.earlyData = true
|
||||||
default:
|
default:
|
||||||
// Ignore unknown extensions.
|
// Ignore unknown extensions.
|
||||||
continue
|
continue
|
||||||
|
@ -34,6 +34,7 @@ type serverHandshakeStateTLS13 struct {
|
|||||||
hello *serverHelloMsg
|
hello *serverHelloMsg
|
||||||
sentDummyCCS bool
|
sentDummyCCS bool
|
||||||
usingPSK bool
|
usingPSK bool
|
||||||
|
earlyData bool
|
||||||
suite *cipherSuiteTLS13
|
suite *cipherSuiteTLS13
|
||||||
cert *Certificate
|
cert *Certificate
|
||||||
sigAlg SignatureScheme
|
sigAlg SignatureScheme
|
||||||
@ -191,7 +192,12 @@ func (hs *serverHandshakeStateTLS13) processClientHello() error {
|
|||||||
return errors.New("tls: initial handshake had non-empty renegotiation extension")
|
return errors.New("tls: initial handshake had non-empty renegotiation extension")
|
||||||
}
|
}
|
||||||
|
|
||||||
if hs.clientHello.earlyData {
|
if hs.clientHello.earlyData && c.quic != nil {
|
||||||
|
if len(hs.clientHello.pskIdentities) == 0 {
|
||||||
|
c.sendAlert(alertIllegalParameter)
|
||||||
|
return errors.New("tls: early_data without pre_shared_key")
|
||||||
|
}
|
||||||
|
} else if hs.clientHello.earlyData {
|
||||||
// See RFC 8446, Section 4.2.10 for the complicated behavior required
|
// See RFC 8446, Section 4.2.10 for the complicated behavior required
|
||||||
// here. The scenario is that a different server at our address offered
|
// here. The scenario is that a different server at our address offered
|
||||||
// to accept early data in the past, which we can't handle. For now, all
|
// to accept early data in the past, which we can't handle. For now, all
|
||||||
@ -278,6 +284,13 @@ GroupSelection:
|
|||||||
return errors.New("tls: invalid client key share")
|
return errors.New("tls: invalid client key share")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selectedProto, err := negotiateALPN(c.config.NextProtos, hs.clientHello.alpnProtocols, c.quic != nil)
|
||||||
|
if err != nil {
|
||||||
|
c.sendAlert(alertNoApplicationProtocol)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.clientProtocol = selectedProto
|
||||||
|
|
||||||
if c.quic != nil {
|
if c.quic != nil {
|
||||||
if hs.clientHello.quicTransportParameters == nil {
|
if hs.clientHello.quicTransportParameters == nil {
|
||||||
// RFC 9001 Section 8.2.
|
// RFC 9001 Section 8.2.
|
||||||
@ -358,10 +371,6 @@ func (hs *serverHandshakeStateTLS13) checkForResumption() error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't check the obfuscated ticket age because it's affected by
|
|
||||||
// clock skew and it's only a freshness signal useful for shrinking the
|
|
||||||
// window for replay attacks, which don't affect us as we don't do 0-RTT.
|
|
||||||
|
|
||||||
pskSuite := cipherSuiteTLS13ByID(sessionState.cipherSuite)
|
pskSuite := cipherSuiteTLS13ByID(sessionState.cipherSuite)
|
||||||
if pskSuite == nil || pskSuite.hash != hs.suite.hash {
|
if pskSuite == nil || pskSuite.hash != hs.suite.hash {
|
||||||
continue
|
continue
|
||||||
@ -399,6 +408,19 @@ func (hs *serverHandshakeStateTLS13) checkForResumption() error {
|
|||||||
return errors.New("tls: invalid PSK binder")
|
return errors.New("tls: invalid PSK binder")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.quic != nil && hs.clientHello.earlyData && i == 0 &&
|
||||||
|
sessionState.EarlyData && sessionState.cipherSuite == hs.suite.id &&
|
||||||
|
sessionState.alpnProtocol == c.clientProtocol {
|
||||||
|
hs.earlyData = true
|
||||||
|
|
||||||
|
transcript := hs.suite.hash.New()
|
||||||
|
if err := transcriptMsg(hs.clientHello, transcript); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
earlyTrafficSecret := hs.suite.deriveSecret(hs.earlySecret, clientEarlyTrafficLabel, transcript)
|
||||||
|
c.quicSetReadSecret(QUICEncryptionLevelEarly, hs.suite.id, earlyTrafficSecret)
|
||||||
|
}
|
||||||
|
|
||||||
c.didResume = true
|
c.didResume = true
|
||||||
if err := c.processCertsFromClient(sessionState.certificate()); err != nil {
|
if err := c.processCertsFromClient(sessionState.certificate()); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -657,14 +679,7 @@ func (hs *serverHandshakeStateTLS13) sendServerParameters() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
encryptedExtensions := new(encryptedExtensionsMsg)
|
encryptedExtensions := new(encryptedExtensionsMsg)
|
||||||
|
encryptedExtensions.alpnProtocol = c.clientProtocol
|
||||||
selectedProto, err := negotiateALPN(c.config.NextProtos, hs.clientHello.alpnProtocols, c.quic != nil)
|
|
||||||
if err != nil {
|
|
||||||
c.sendAlert(alertNoApplicationProtocol)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
encryptedExtensions.alpnProtocol = selectedProto
|
|
||||||
c.clientProtocol = selectedProto
|
|
||||||
|
|
||||||
if c.quic != nil {
|
if c.quic != nil {
|
||||||
p, err := c.quicGetTransportParameters()
|
p, err := c.quicGetTransportParameters()
|
||||||
@ -672,6 +687,7 @@ func (hs *serverHandshakeStateTLS13) sendServerParameters() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
encryptedExtensions.quicTransportParameters = p
|
encryptedExtensions.quicTransportParameters = p
|
||||||
|
encryptedExtensions.earlyData = hs.earlyData
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := hs.c.writeHandshakeRecord(encryptedExtensions, hs.transcript); err != nil {
|
if _, err := hs.c.writeHandshakeRecord(encryptedExtensions, hs.transcript); err != nil {
|
||||||
@ -812,6 +828,11 @@ func (hs *serverHandshakeStateTLS13) shouldSendSessionTickets() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// QUIC tickets are sent by QUICConn.SendSessionTicket, not automatically.
|
||||||
|
if hs.c.quic != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// Don't send tickets the client wouldn't use. See RFC 8446, Section 4.2.9.
|
// Don't send tickets the client wouldn't use. See RFC 8446, Section 4.2.9.
|
||||||
for _, pskMode := range hs.clientHello.pskModes {
|
for _, pskMode := range hs.clientHello.pskModes {
|
||||||
if pskMode == pskModeDHE {
|
if pskMode == pskModeDHE {
|
||||||
@ -832,16 +853,24 @@ func (hs *serverHandshakeStateTLS13) sendSessionTickets() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.resumptionSecret = hs.suite.deriveSecret(hs.masterSecret,
|
||||||
|
resumptionLabel, hs.transcript)
|
||||||
|
|
||||||
if !hs.shouldSendSessionTickets() {
|
if !hs.shouldSendSessionTickets() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
return c.sendSessionTicket(false)
|
||||||
|
}
|
||||||
|
|
||||||
resumptionSecret := hs.suite.deriveSecret(hs.masterSecret,
|
func (c *Conn) sendSessionTicket(earlyData bool) error {
|
||||||
resumptionLabel, hs.transcript)
|
suite := cipherSuiteTLS13ByID(c.cipherSuite)
|
||||||
|
if suite == nil {
|
||||||
|
return errors.New("tls: internal error: unknown cipher suite")
|
||||||
|
}
|
||||||
// ticket_nonce, which must be unique per connection, is always left at
|
// ticket_nonce, which must be unique per connection, is always left at
|
||||||
// zero because we only ever send one ticket per connection.
|
// zero because we only ever send one ticket per connection.
|
||||||
psk := hs.suite.expandLabel(resumptionSecret, "resumption",
|
psk := suite.expandLabel(c.resumptionSecret, "resumption",
|
||||||
nil, hs.suite.hash.Size())
|
nil, suite.hash.Size())
|
||||||
|
|
||||||
m := new(newSessionTicketMsgTLS13)
|
m := new(newSessionTicketMsgTLS13)
|
||||||
|
|
||||||
@ -850,6 +879,7 @@ func (hs *serverHandshakeStateTLS13) sendSessionTickets() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
state.secret = psk
|
state.secret = psk
|
||||||
|
state.EarlyData = earlyData
|
||||||
if c.config.WrapSession != nil {
|
if c.config.WrapSession != nil {
|
||||||
m.label, err = c.config.WrapSession(c.connectionStateLocked(), state)
|
m.label, err = c.config.WrapSession(c.connectionStateLocked(), state)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -872,12 +902,17 @@ func (hs *serverHandshakeStateTLS13) sendSessionTickets() error {
|
|||||||
// The value is not stored anywhere; we never need to check the ticket age
|
// The value is not stored anywhere; we never need to check the ticket age
|
||||||
// because 0-RTT is not supported.
|
// because 0-RTT is not supported.
|
||||||
ageAdd := make([]byte, 4)
|
ageAdd := make([]byte, 4)
|
||||||
_, err = hs.c.config.rand().Read(ageAdd)
|
_, err = c.config.rand().Read(ageAdd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
m.ageAdd = binary.LittleEndian.Uint32(ageAdd)
|
m.ageAdd = binary.LittleEndian.Uint32(ageAdd)
|
||||||
|
|
||||||
|
if earlyData {
|
||||||
|
// RFC 9001, Section 4.6.1
|
||||||
|
m.maxEarlyData = 0xffffffff
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := c.writeHandshakeRecord(m, nil); err != nil {
|
if _, err := c.writeHandshakeRecord(m, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
resumptionBinderLabel = "res binder"
|
resumptionBinderLabel = "res binder"
|
||||||
|
clientEarlyTrafficLabel = "c e traffic"
|
||||||
clientHandshakeTrafficLabel = "c hs traffic"
|
clientHandshakeTrafficLabel = "c hs traffic"
|
||||||
serverHandshakeTrafficLabel = "s hs traffic"
|
serverHandshakeTrafficLabel = "s hs traffic"
|
||||||
clientApplicationTrafficLabel = "c ap traffic"
|
clientApplicationTrafficLabel = "c ap traffic"
|
||||||
|
42
quic.go
42
quic.go
@ -16,6 +16,7 @@ type QUICEncryptionLevel int
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
QUICEncryptionLevelInitial = QUICEncryptionLevel(iota)
|
QUICEncryptionLevelInitial = QUICEncryptionLevel(iota)
|
||||||
|
QUICEncryptionLevelEarly
|
||||||
QUICEncryptionLevelHandshake
|
QUICEncryptionLevelHandshake
|
||||||
QUICEncryptionLevelApplication
|
QUICEncryptionLevelApplication
|
||||||
)
|
)
|
||||||
@ -24,6 +25,8 @@ func (l QUICEncryptionLevel) String() string {
|
|||||||
switch l {
|
switch l {
|
||||||
case QUICEncryptionLevelInitial:
|
case QUICEncryptionLevelInitial:
|
||||||
return "Initial"
|
return "Initial"
|
||||||
|
case QUICEncryptionLevelEarly:
|
||||||
|
return "Early"
|
||||||
case QUICEncryptionLevelHandshake:
|
case QUICEncryptionLevelHandshake:
|
||||||
return "Handshake"
|
return "Handshake"
|
||||||
case QUICEncryptionLevelApplication:
|
case QUICEncryptionLevelApplication:
|
||||||
@ -39,6 +42,8 @@ func (l QUICEncryptionLevel) String() string {
|
|||||||
// Methods of QUICConn are not safe for concurrent use.
|
// Methods of QUICConn are not safe for concurrent use.
|
||||||
type QUICConn struct {
|
type QUICConn struct {
|
||||||
conn *Conn
|
conn *Conn
|
||||||
|
|
||||||
|
sessionTicketSent bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// A QUICConfig configures a QUICConn.
|
// A QUICConfig configures a QUICConn.
|
||||||
@ -79,6 +84,11 @@ const (
|
|||||||
// connection will never generate a QUICTransportParametersRequired event.
|
// connection will never generate a QUICTransportParametersRequired event.
|
||||||
QUICTransportParametersRequired
|
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 indicates that the TLS handshake has completed.
|
||||||
QUICHandshakeDone
|
QUICHandshakeDone
|
||||||
)
|
)
|
||||||
@ -106,10 +116,10 @@ type quicState struct {
|
|||||||
nextEvent int
|
nextEvent int
|
||||||
|
|
||||||
// eventArr is a statically allocated event array, large enough to handle
|
// eventArr is a statically allocated event array, large enough to handle
|
||||||
// the usual maximum number of events resulting from a single call:
|
// the usual maximum number of events resulting from a single call: transport
|
||||||
// transport parameters, Initial data, Handshake write and read secrets,
|
// parameters, Initial data, Early read secret, Handshake write and read
|
||||||
// Handshake data, Application write secret, Application data.
|
// secrets, Handshake data, Application write secret, Application data.
|
||||||
eventArr [7]QUICEvent
|
eventArr [8]QUICEvent
|
||||||
|
|
||||||
started bool
|
started bool
|
||||||
signalc chan struct{} // handshake data is available to be read
|
signalc chan struct{} // handshake data is available to be read
|
||||||
@ -237,6 +247,24 @@ func (q *QUICConn) HandleData(level QUICEncryptionLevel, data []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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(earlyData bool) 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(earlyData))
|
||||||
|
}
|
||||||
|
|
||||||
// ConnectionState returns basic TLS details about the connection.
|
// ConnectionState returns basic TLS details about the connection.
|
||||||
func (q *QUICConn) ConnectionState() ConnectionState {
|
func (q *QUICConn) ConnectionState() ConnectionState {
|
||||||
return q.conn.ConnectionState()
|
return q.conn.ConnectionState()
|
||||||
@ -345,6 +373,12 @@ func (c *Conn) quicHandshakeComplete() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Conn) quicRejectedEarlyData() {
|
||||||
|
c.quic.events = append(c.quic.events, QUICEvent{
|
||||||
|
Kind: QUICRejectedEarlyData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// quicWaitForSignal notifies the QUICConn that handshake progress is blocked,
|
// quicWaitForSignal notifies the QUICConn that handshake progress is blocked,
|
||||||
// and waits for a signal that the handshake should proceed.
|
// and waits for a signal that the handshake should proceed.
|
||||||
//
|
//
|
||||||
|
40
ticket.go
40
ticket.go
@ -34,7 +34,12 @@ type SessionState struct {
|
|||||||
// uint64 created_at;
|
// uint64 created_at;
|
||||||
// opaque secret<1..2^8-1>;
|
// opaque secret<1..2^8-1>;
|
||||||
// opaque extra<0..2^24-1>;
|
// opaque extra<0..2^24-1>;
|
||||||
|
// uint8 early_data = { 0, 1 };
|
||||||
// CertificateEntry certificate_list<0..2^24-1>;
|
// CertificateEntry certificate_list<0..2^24-1>;
|
||||||
|
// select (SessionState.early_data) {
|
||||||
|
// case 0: Empty;
|
||||||
|
// case 1: opaque alpn<1..2^8-1>;
|
||||||
|
// };
|
||||||
// select (SessionState.type) {
|
// select (SessionState.type) {
|
||||||
// case server: /* empty */;
|
// case server: /* empty */;
|
||||||
// case client: struct {
|
// case client: struct {
|
||||||
@ -63,6 +68,11 @@ type SessionState struct {
|
|||||||
// fixed-length suffix.
|
// fixed-length suffix.
|
||||||
Extra []byte
|
Extra []byte
|
||||||
|
|
||||||
|
// EarlyData indicates whether the ticket can be used for 0-RTT in a QUIC
|
||||||
|
// connection. The application may set this to false if it is true to
|
||||||
|
// decline to offer 0-RTT even if supported.
|
||||||
|
EarlyData bool
|
||||||
|
|
||||||
version uint16
|
version uint16
|
||||||
isClient bool
|
isClient bool
|
||||||
cipherSuite uint16
|
cipherSuite uint16
|
||||||
@ -75,6 +85,7 @@ type SessionState struct {
|
|||||||
activeCertHandles []*activeCert
|
activeCertHandles []*activeCert
|
||||||
ocspResponse []byte
|
ocspResponse []byte
|
||||||
scts [][]byte
|
scts [][]byte
|
||||||
|
alpnProtocol string // only set if EarlyData is true
|
||||||
|
|
||||||
// Client-side fields.
|
// Client-side fields.
|
||||||
verifiedChains [][]*x509.Certificate
|
verifiedChains [][]*x509.Certificate
|
||||||
@ -106,7 +117,17 @@ func (s *SessionState) Bytes() ([]byte, error) {
|
|||||||
b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) {
|
b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||||
b.AddBytes(s.Extra)
|
b.AddBytes(s.Extra)
|
||||||
})
|
})
|
||||||
|
if s.EarlyData {
|
||||||
|
b.AddUint8(1)
|
||||||
|
} else {
|
||||||
|
b.AddUint8(0)
|
||||||
|
}
|
||||||
marshalCertificate(&b, s.certificate())
|
marshalCertificate(&b, s.certificate())
|
||||||
|
if s.EarlyData {
|
||||||
|
b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||||
|
b.AddBytes([]byte(s.alpnProtocol))
|
||||||
|
})
|
||||||
|
}
|
||||||
if s.isClient {
|
if s.isClient {
|
||||||
b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) {
|
b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||||
for _, chain := range s.verifiedChains {
|
for _, chain := range s.verifiedChains {
|
||||||
@ -152,7 +173,7 @@ func certificatesToBytesSlice(certs []*x509.Certificate) [][]byte {
|
|||||||
func ParseSessionState(data []byte) (*SessionState, error) {
|
func ParseSessionState(data []byte) (*SessionState, error) {
|
||||||
ss := &SessionState{}
|
ss := &SessionState{}
|
||||||
s := cryptobyte.String(data)
|
s := cryptobyte.String(data)
|
||||||
var typ uint8
|
var typ, earlyData uint8
|
||||||
var cert Certificate
|
var cert Certificate
|
||||||
if !s.ReadUint16(&ss.version) ||
|
if !s.ReadUint16(&ss.version) ||
|
||||||
!s.ReadUint8(&typ) ||
|
!s.ReadUint8(&typ) ||
|
||||||
@ -161,10 +182,19 @@ func ParseSessionState(data []byte) (*SessionState, error) {
|
|||||||
!readUint64(&s, &ss.createdAt) ||
|
!readUint64(&s, &ss.createdAt) ||
|
||||||
!readUint8LengthPrefixed(&s, &ss.secret) ||
|
!readUint8LengthPrefixed(&s, &ss.secret) ||
|
||||||
!readUint24LengthPrefixed(&s, &ss.Extra) ||
|
!readUint24LengthPrefixed(&s, &ss.Extra) ||
|
||||||
|
!s.ReadUint8(&earlyData) ||
|
||||||
len(ss.secret) == 0 ||
|
len(ss.secret) == 0 ||
|
||||||
!unmarshalCertificate(&s, &cert) {
|
!unmarshalCertificate(&s, &cert) {
|
||||||
return nil, errors.New("tls: invalid session encoding")
|
return nil, errors.New("tls: invalid session encoding")
|
||||||
}
|
}
|
||||||
|
switch earlyData {
|
||||||
|
case 0:
|
||||||
|
ss.EarlyData = false
|
||||||
|
case 1:
|
||||||
|
ss.EarlyData = true
|
||||||
|
default:
|
||||||
|
return nil, errors.New("tls: invalid session encoding")
|
||||||
|
}
|
||||||
for _, cert := range cert.Certificate {
|
for _, cert := range cert.Certificate {
|
||||||
c, err := globalCertCache.newCert(cert)
|
c, err := globalCertCache.newCert(cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -175,6 +205,13 @@ func ParseSessionState(data []byte) (*SessionState, error) {
|
|||||||
}
|
}
|
||||||
ss.ocspResponse = cert.OCSPStaple
|
ss.ocspResponse = cert.OCSPStaple
|
||||||
ss.scts = cert.SignedCertificateTimestamps
|
ss.scts = cert.SignedCertificateTimestamps
|
||||||
|
if ss.EarlyData {
|
||||||
|
var alpn []byte
|
||||||
|
if !readUint8LengthPrefixed(&s, &alpn) {
|
||||||
|
return nil, errors.New("tls: invalid session encoding")
|
||||||
|
}
|
||||||
|
ss.alpnProtocol = string(alpn)
|
||||||
|
}
|
||||||
if isClient := typ == 2; !isClient {
|
if isClient := typ == 2; !isClient {
|
||||||
if !s.Empty() {
|
if !s.Empty() {
|
||||||
return nil, errors.New("tls: invalid session encoding")
|
return nil, errors.New("tls: invalid session encoding")
|
||||||
@ -236,6 +273,7 @@ func (c *Conn) sessionState() (*SessionState, error) {
|
|||||||
version: c.vers,
|
version: c.vers,
|
||||||
cipherSuite: c.cipherSuite,
|
cipherSuite: c.cipherSuite,
|
||||||
createdAt: uint64(c.config.time().Unix()),
|
createdAt: uint64(c.config.time().Unix()),
|
||||||
|
alpnProtocol: c.clientProtocol,
|
||||||
peerCertificates: c.peerCertificates,
|
peerCertificates: c.peerCertificates,
|
||||||
activeCertHandles: c.activeCertHandles,
|
activeCertHandles: c.activeCertHandles,
|
||||||
ocspResponse: c.ocspResponse,
|
ocspResponse: c.ocspResponse,
|
||||||
|
Loading…
Reference in New Issue
Block a user