"golang.org/x/crypto/cryptobyte"
)
-// sessionState contains the information that is serialized into a session
-// ticket in order to later resume a connection.
-type sessionState struct {
- vers uint16
- cipherSuite uint16
- createdAt uint64
- masterSecret []byte // opaque master_secret<1..2^16-1>;
- // struct { opaque certificate<1..2^24-1> } Certificate;
- certificates [][]byte // Certificate certificate_list<0..2^24-1>;
+// A SessionState is a resumable session.
+type SessionState struct {
+ version uint16 // uint16 version;
+ // uint8 revision = 1;
+ cipherSuite uint16
+ createdAt uint64
+ secret []byte // opaque master_secret<1..2^8-1>;
+ certificate Certificate // CertificateEntry certificate_list<0..2^24-1>;
}
-func (m *sessionState) marshal() ([]byte, error) {
+// Bytes encodes the session, including any private fields, so that it can be
+// parsed by [ParseSessionState]. The encoding contains secret values.
+//
+// The specific encoding should be considered opaque and may change incompatibly
+// between Go versions.
+func (m *SessionState) Bytes() ([]byte, error) {
var b cryptobyte.Builder
- b.AddUint16(m.vers)
- b.AddUint16(m.cipherSuite)
- addUint64(&b, m.createdAt)
- b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
- b.AddBytes(m.masterSecret)
- })
- b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) {
- for _, cert := range m.certificates {
- b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) {
- b.AddBytes(cert)
- })
- }
- })
- return b.Bytes()
-}
-
-func (m *sessionState) unmarshal(data []byte) bool {
- *m = sessionState{}
- s := cryptobyte.String(data)
- if ok := s.ReadUint16(&m.vers) &&
- s.ReadUint16(&m.cipherSuite) &&
- readUint64(&s, &m.createdAt) &&
- readUint16LengthPrefixed(&s, &m.masterSecret) &&
- len(m.masterSecret) != 0; !ok {
- return false
- }
- var certList cryptobyte.String
- if !s.ReadUint24LengthPrefixed(&certList) {
- return false
- }
- for !certList.Empty() {
- var cert []byte
- if !readUint24LengthPrefixed(&certList, &cert) {
- return false
- }
- m.certificates = append(m.certificates, cert)
- }
- return s.Empty()
-}
-
-// sessionStateTLS13 is the content of a TLS 1.3 session ticket. Its first
-// version (revision = 0) doesn't carry any of the information needed for 0-RTT
-// validation and the nonce is always empty.
-type sessionStateTLS13 struct {
- // uint8 version = 0x0304;
- // uint8 revision = 0;
- cipherSuite uint16
- createdAt uint64
- resumptionSecret []byte // opaque resumption_master_secret<1..2^8-1>;
- certificate Certificate // CertificateEntry certificate_list<0..2^24-1>;
-}
-
-func (m *sessionStateTLS13) marshal() ([]byte, error) {
- var b cryptobyte.Builder
- b.AddUint16(VersionTLS13)
- b.AddUint8(0) // revision
+ b.AddUint16(m.version)
+ b.AddUint8(1) // revision
b.AddUint16(m.cipherSuite)
addUint64(&b, m.createdAt)
b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
- b.AddBytes(m.resumptionSecret)
+ b.AddBytes(m.secret)
})
marshalCertificate(&b, m.certificate)
return b.Bytes()
}
-func (m *sessionStateTLS13) unmarshal(data []byte) bool {
- *m = sessionStateTLS13{}
+// ParseSessionState parses a [SessionState] encoded by [SessionState.Bytes].
+func ParseSessionState(data []byte) (*SessionState, error) {
+ ss := &SessionState{}
s := cryptobyte.String(data)
- var version uint16
var revision uint8
- return s.ReadUint16(&version) &&
- version == VersionTLS13 &&
- s.ReadUint8(&revision) &&
- revision == 0 &&
- s.ReadUint16(&m.cipherSuite) &&
- readUint64(&s, &m.createdAt) &&
- readUint8LengthPrefixed(&s, &m.resumptionSecret) &&
- len(m.resumptionSecret) != 0 &&
- unmarshalCertificate(&s, &m.certificate) &&
- s.Empty()
+ if !s.ReadUint16(&ss.version) ||
+ !s.ReadUint8(&revision) ||
+ revision != 1 ||
+ !s.ReadUint16(&ss.cipherSuite) ||
+ !readUint64(&s, &ss.createdAt) ||
+ !readUint8LengthPrefixed(&s, &ss.secret) ||
+ len(ss.secret) == 0 ||
+ !unmarshalCertificate(&s, &ss.certificate) ||
+ !s.Empty() {
+ return nil, errors.New("tls: invalid session encoding")
+ }
+ return ss, nil
}
func (c *Conn) encryptTicket(state []byte) ([]byte, error) {