diff --git a/ticket.go b/ticket.go index b34b3c2..cf50f85 100644 --- a/ticket.go +++ b/ticket.go @@ -27,13 +27,15 @@ type SessionState struct { // // Certificate CertificateChain<0..2^24-1>; // + // opaque Extra<0..2^24-1>; + // // struct { // uint16 version; // SessionStateType type; // uint16 cipher_suite; // uint64 created_at; // opaque secret<1..2^8-1>; - // opaque extra<0..2^24-1>; + // Extra extra<0..2^24-1>; // uint8 ext_master_secret = { 0, 1 }; // uint8 early_data = { 0, 1 }; // CertificateEntry certificate_list<0..2^24-1>; @@ -62,12 +64,13 @@ type SessionState struct { // // This allows [Config.UnwrapSession]/[Config.WrapSession] and // [ClientSessionCache] implementations to store and retrieve additional - // data. + // data alongside this session. // - // If Extra is already set, the implementation must preserve the previous - // value across a round-trip, for example by appending and stripping a - // fixed-length suffix. - Extra []byte + // To allow different layers in a protocol stack to share this field, + // applications must only append to it, not replace it, and must use entries + // that can be recognized even if out of order (for example, by starting + // with a id and version prefix). + 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 @@ -115,7 +118,11 @@ func (s *SessionState) Bytes() ([]byte, error) { b.AddBytes(s.secret) }) b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) { - b.AddBytes(s.Extra) + for _, extra := range s.Extra { + b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) { + b.AddBytes(extra) + }) + } }) if s.extMasterSecret { b.AddUint8(1) @@ -176,19 +183,27 @@ func ParseSessionState(data []byte) (*SessionState, error) { s := cryptobyte.String(data) var typ, extMasterSecret, earlyData uint8 var cert Certificate + var extra cryptobyte.String if !s.ReadUint16(&ss.version) || !s.ReadUint8(&typ) || (typ != 1 && typ != 2) || !s.ReadUint16(&ss.cipherSuite) || !readUint64(&s, &ss.createdAt) || !readUint8LengthPrefixed(&s, &ss.secret) || - !readUint24LengthPrefixed(&s, &ss.Extra) || + !s.ReadUint24LengthPrefixed(&extra) || !s.ReadUint8(&extMasterSecret) || !s.ReadUint8(&earlyData) || len(ss.secret) == 0 || !unmarshalCertificate(&s, &cert) { return nil, errors.New("tls: invalid session encoding") } + for !extra.Empty() { + var e []byte + if !readUint24LengthPrefixed(&extra, &e) { + return nil, errors.New("tls: invalid session encoding") + } + ss.Extra = append(ss.Extra, e) + } switch extMasterSecret { case 0: ss.extMasterSecret = false