Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/crypto/tls/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -978,6 +978,10 @@ func (c *Config) ticketKeyFromBytes(b [32]byte) (key ticketKey) {
// ticket, and the lifetime we set for all tickets we send.
const maxSessionTicketLifetime = 7 * 24 * time.Hour

// Maximum allowed mismatch between the stated age of a ticket and the server-observed one.
// See https://datatracker.ietf.org/doc/html/rfc8446#section-8.3
const maxSessionTicketSkewAllowance = 10 * time.Second

// Clone returns a shallow clone of c or nil if c is nil. It is safe to clone a [Config] that is
// being used concurrently by a TLS client or server.
func (c *Config) Clone() *Config {
Expand Down
2 changes: 1 addition & 1 deletion src/crypto/tls/handshake_messages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,9 +421,9 @@ func (*SessionState) Generate(rand *rand.Rand, size int) reflect.Value {
s.alpnProtocol = string(randomBytes(rand.Intn(10), rand))
}
if isTLS13 {
s.ageAdd = uint32(rand.Int63() & math.MaxUint32)
if s.isClient {
s.useBy = uint64(rand.Int63())
s.ageAdd = uint32(rand.Int63() & math.MaxUint32)
}
} else {
s.curveID = CurveID(rand.Intn(30000) + 1)
Expand Down
68 changes: 41 additions & 27 deletions src/crypto/tls/handshake_server_tls13.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,8 @@ func (hs *serverHandshakeStateTLS13) checkForResumption() error {
}

createdAt := time.Unix(int64(sessionState.createdAt), 0)
if c.config.time().Sub(createdAt) > maxSessionTicketLifetime {
serverAge := c.config.time().Sub(createdAt)
if serverAge > maxSessionTicketLifetime || serverAge < -maxSessionTicketLifetime {
continue
}

Expand Down Expand Up @@ -374,12 +375,6 @@ func (hs *serverHandshakeStateTLS13) checkForResumption() error {
continue
}

if c.quic != nil && c.quic.enableSessionEvents {
if err := c.quicResumeSession(sessionState); err != nil {
return err
}
}

hs.earlySecret = tls13.NewEarlySecret(hs.suite.hash.New, sessionState.secret)
binderKey := hs.earlySecret.ResumptionBinderKey()
// Clone the transcript in case a HelloRetryRequest was recorded.
Expand All @@ -400,17 +395,8 @@ func (hs *serverHandshakeStateTLS13) checkForResumption() error {
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.earlySecret.ClientEarlyTrafficSecret(transcript)
if err := c.quicSetReadSecret(QUICEncryptionLevelEarly, hs.suite.id, earlyTrafficSecret); err != nil {
if c.quic != nil && c.quic.enableSessionEvents {
if err := c.quicResumeSession(sessionState); err != nil {
return err
}
}
Expand All @@ -424,6 +410,33 @@ func (hs *serverHandshakeStateTLS13) checkForResumption() error {
hs.hello.selectedIdentityPresent = true
hs.hello.selectedIdentity = uint16(i)
hs.usingPSK = true

if !hs.clientHello.earlyData || !sessionState.EarlyData || i != 0 {
return nil
}

clientAge := time.Duration(identity.obfuscatedTicketAge-sessionState.ageAdd) * time.Millisecond
if ageDiff := serverAge - clientAge; ageDiff > maxSessionTicketSkewAllowance || ageDiff < -maxSessionTicketSkewAllowance {
return nil
}

if sessionState.cipherSuite != hs.suite.id || sessionState.alpnProtocol != c.clientProtocol {
return nil
}

hs.earlyData = true

earlyTranscript := hs.suite.hash.New()
if err := transcriptMsg(hs.clientHello, earlyTranscript); err != nil {
return err
}

earlyTrafficSecret := hs.earlySecret.ClientEarlyTrafficSecret(earlyTranscript)
if c.quic != nil {
if err := c.quicSetReadSecret(QUICEncryptionLevelEarly, hs.suite.id, earlyTrafficSecret); err != nil {
return err
}
}
return nil
}

Expand Down Expand Up @@ -978,6 +991,15 @@ func (c *Conn) sendSessionTicket(earlyData bool, extra [][]byte) error {
state.secret = psk
state.EarlyData = earlyData
state.Extra = extra

// ticket_age_add is a random 32-bit value.
// See RFC 8446, section 4.6.1
ageAdd := make([]byte, 4)
if _, err := c.config.rand().Read(ageAdd); err != nil {
return err
}
state.ageAdd = byteorder.LEUint32(ageAdd)

if c.config.WrapSession != nil {
var err error
m.label, err = c.config.WrapSession(c.connectionStateLocked(), state)
Expand All @@ -996,15 +1018,7 @@ func (c *Conn) sendSessionTicket(earlyData bool, extra [][]byte) error {
}
}
m.lifetime = uint32(maxSessionTicketLifetime / time.Second)

// ticket_age_add is a random 32-bit value. See RFC 8446, section 4.6.1
// The value is not stored anywhere; we never need to check the ticket age
// because 0-RTT is not supported.
ageAdd := make([]byte, 4)
if _, err := c.config.rand().Read(ageAdd); err != nil {
return err
}
m.ageAdd = byteorder.LEUint32(ageAdd)
m.ageAdd = state.ageAdd

if earlyData {
// RFC 9001, Section 4.6.1
Expand Down
36 changes: 27 additions & 9 deletions src/crypto/tls/ticket.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ import (
"golang.org/x/crypto/cryptobyte"
)

// TicketVersion 0x03 are reserved for older TLS version first byte.
// TicketVersion 0xff is reserved as an indication to read one more byte if
// we run out of all non-reserved version numbers.
const currentTicketVersion = 0

// A SessionState is a resumable session.
type SessionState struct {
// Encoded as a SessionState (in the language of RFC 8446, Section 3).
Expand All @@ -30,6 +35,7 @@ type SessionState struct {
// opaque Extra<0..2^24-1>;
//
// struct {
// uint8 ticket_version;
// uint16 version;
// SessionStateType type;
// uint16 cipher_suite;
Expand All @@ -45,14 +51,16 @@ type SessionState struct {
// case 1: opaque alpn<1..2^8-1>;
// };
// select (SessionState.version) {
// case VersionTLS10..VersionTLS12: uint16 curve_id;
// case VersionTLS13: select (SessionState.type) {
// case server: Empty;
// case client: struct {
// uint64 use_by;
// uint32 age_add;
// case VersionTLS10..VersionTLS12:
// uint16 curve_id;
// case VersionTLS13:
// uint32 age_add;
// select (SessionState.type) {
// case server: Empty;
// case client: struct {
// uint64 use_by;
// };
// };
// };
// };
// } SessionState;
//
Expand Down Expand Up @@ -110,6 +118,7 @@ type SessionState struct {
// between Go versions.
func (s *SessionState) Bytes() ([]byte, error) {
var b cryptobyte.Builder
b.AddUint8(currentTicketVersion)
b.AddUint16(s.version)
if s.isClient {
b.AddUint8(2) // client
Expand Down Expand Up @@ -165,9 +174,9 @@ func (s *SessionState) Bytes() ([]byte, error) {
})
}
if s.version >= VersionTLS13 {
b.AddUint32(s.ageAdd)
if s.isClient {
addUint64(&b, s.useBy)
b.AddUint32(s.ageAdd)
}
} else {
b.AddUint16(uint16(s.curveID))
Expand All @@ -187,6 +196,12 @@ func certificatesToBytesSlice(certs []*x509.Certificate) [][]byte {
func ParseSessionState(data []byte) (*SessionState, error) {
ss := &SessionState{}
s := cryptobyte.String(data)

var ticketVersion uint8
if !s.ReadUint8(&ticketVersion) || ticketVersion != ticketVersion {
return nil, errors.New("tls: invalid session encoding")
}

var typ, extMasterSecret, earlyData uint8
var cert Certificate
var extra cryptobyte.String
Expand Down Expand Up @@ -280,8 +295,11 @@ func ParseSessionState(data []byte) (*SessionState, error) {
ss.alpnProtocol = string(alpn)
}
if ss.version >= VersionTLS13 {
if !s.ReadUint32(&ss.ageAdd) {
return nil, errors.New("tls: invalid session encoding")
}
if ss.isClient {
if !s.ReadUint64(&ss.useBy) || !s.ReadUint32(&ss.ageAdd) {
if !s.ReadUint64(&ss.useBy) {
return nil, errors.New("tls: invalid session encoding")
}
}
Expand Down