| // Copyright 2012 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 ( |
| "crypto/aes" |
| "crypto/cipher" |
| "crypto/hmac" |
| "crypto/sha256" |
| "crypto/subtle" |
| "crypto/x509" |
| "errors" |
| "io" |
| |
| "golang.org/x/crypto/cryptobyte" |
| ) |
| |
| // A SessionState is a resumable session. |
| type SessionState struct { |
| // Encoded as a SessionState (in the language of RFC 8446, Section 3). |
| // |
| // enum { server(1), client(2) } SessionStateType; |
| // |
| // opaque Certificate<1..2^24-1>; |
| // |
| // 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>; |
| // Extra extra<0..2^24-1>; |
| // uint8 ext_master_secret = { 0, 1 }; |
| // uint8 early_data = { 0, 1 }; |
| // CertificateEntry certificate_list<0..2^24-1>; |
| // CertificateChain verified_chains<0..2^24-1>; /* excluding leaf */ |
| // select (SessionState.early_data) { |
| // case 0: Empty; |
| // case 1: opaque alpn<1..2^8-1>; |
| // }; |
| // select (SessionState.type) { |
| // case server: Empty; |
| // case client: struct { |
| // select (SessionState.version) { |
| // case VersionTLS10..VersionTLS12: Empty; |
| // case VersionTLS13: struct { |
| // uint64 use_by; |
| // uint32 age_add; |
| // }; |
| // }; |
| // }; |
| // }; |
| // } SessionState; |
| // |
| |
| // Extra is ignored by crypto/tls, but is encoded by [SessionState.Bytes] |
| // and parsed by [ParseSessionState]. |
| // |
| // This allows [Config.UnwrapSession]/[Config.WrapSession] and |
| // [ClientSessionCache] implementations to store and retrieve additional |
| // data alongside this session. |
| // |
| // 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 an 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 |
| // decline to offer 0-RTT even if supported. |
| EarlyData bool |
| |
| version uint16 |
| isClient bool |
| cipherSuite uint16 |
| // createdAt is the generation time of the secret on the sever (which for |
| // TLS 1.0–1.2 might be earlier than the current session) and the time at |
| // which the ticket was received on the client. |
| createdAt uint64 // seconds since UNIX epoch |
| secret []byte // master secret for TLS 1.2, or the PSK for TLS 1.3 |
| extMasterSecret bool |
| peerCertificates []*x509.Certificate |
| activeCertHandles []*activeCert |
| ocspResponse []byte |
| scts [][]byte |
| verifiedChains [][]*x509.Certificate |
| alpnProtocol string // only set if EarlyData is true |
| |
| // Client-side TLS 1.3-only fields. |
| useBy uint64 // seconds since UNIX epoch |
| ageAdd uint32 |
| } |
| |
| // Bytes encodes the session, including any private fields, so that it can be |
| // parsed by [ParseSessionState]. The encoding contains secret values critical |
| // to the security of future and possibly past sessions. |
| // |
| // The specific encoding should be considered opaque and may change incompatibly |
| // between Go versions. |
| func (s *SessionState) Bytes() ([]byte, error) { |
| var b cryptobyte.Builder |
| b.AddUint16(s.version) |
| if s.isClient { |
| b.AddUint8(2) // client |
| } else { |
| b.AddUint8(1) // server |
| } |
| b.AddUint16(s.cipherSuite) |
| addUint64(&b, s.createdAt) |
| b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { |
| b.AddBytes(s.secret) |
| }) |
| b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) { |
| for _, extra := range s.Extra { |
| b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) { |
| b.AddBytes(extra) |
| }) |
| } |
| }) |
| if s.extMasterSecret { |
| b.AddUint8(1) |
| } else { |
| b.AddUint8(0) |
| } |
| if s.EarlyData { |
| b.AddUint8(1) |
| } else { |
| b.AddUint8(0) |
| } |
| marshalCertificate(&b, Certificate{ |
| Certificate: certificatesToBytesSlice(s.peerCertificates), |
| OCSPStaple: s.ocspResponse, |
| SignedCertificateTimestamps: s.scts, |
| }) |
| b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) { |
| for _, chain := range s.verifiedChains { |
| b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) { |
| // We elide the first certificate because it's always the leaf. |
| if len(chain) == 0 { |
| b.SetError(errors.New("tls: internal error: empty verified chain")) |
| return |
| } |
| for _, cert := range chain[1:] { |
| b.AddUint24LengthPrefixed(func(b *cryptobyte.Builder) { |
| b.AddBytes(cert.Raw) |
| }) |
| } |
| }) |
| } |
| }) |
| if s.EarlyData { |
| b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) { |
| b.AddBytes([]byte(s.alpnProtocol)) |
| }) |
| } |
| if s.isClient { |
| if s.version >= VersionTLS13 { |
| addUint64(&b, s.useBy) |
| b.AddUint32(s.ageAdd) |
| } |
| } |
| return b.Bytes() |
| } |
| |
| func certificatesToBytesSlice(certs []*x509.Certificate) [][]byte { |
| s := make([][]byte, 0, len(certs)) |
| for _, c := range certs { |
| s = append(s, c.Raw) |
| } |
| return s |
| } |
| |
| // ParseSessionState parses a [SessionState] encoded by [SessionState.Bytes]. |
| func ParseSessionState(data []byte) (*SessionState, error) { |
| ss := &SessionState{} |
| 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) || |
| !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 |
| case 1: |
| ss.extMasterSecret = true |
| default: |
| 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 { |
| c, err := globalCertCache.newCert(cert) |
| if err != nil { |
| return nil, err |
| } |
| ss.activeCertHandles = append(ss.activeCertHandles, c) |
| ss.peerCertificates = append(ss.peerCertificates, c.cert) |
| } |
| ss.ocspResponse = cert.OCSPStaple |
| ss.scts = cert.SignedCertificateTimestamps |
| var chainList cryptobyte.String |
| if !s.ReadUint24LengthPrefixed(&chainList) { |
| return nil, errors.New("tls: invalid session encoding") |
| } |
| for !chainList.Empty() { |
| var certList cryptobyte.String |
| if !chainList.ReadUint24LengthPrefixed(&certList) { |
| return nil, errors.New("tls: invalid session encoding") |
| } |
| var chain []*x509.Certificate |
| if len(ss.peerCertificates) == 0 { |
| return nil, errors.New("tls: invalid session encoding") |
| } |
| chain = append(chain, ss.peerCertificates[0]) |
| for !certList.Empty() { |
| var cert []byte |
| if !readUint24LengthPrefixed(&certList, &cert) { |
| return nil, errors.New("tls: invalid session encoding") |
| } |
| c, err := globalCertCache.newCert(cert) |
| if err != nil { |
| return nil, err |
| } |
| ss.activeCertHandles = append(ss.activeCertHandles, c) |
| chain = append(chain, c.cert) |
| } |
| ss.verifiedChains = append(ss.verifiedChains, chain) |
| } |
| 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 !s.Empty() { |
| return nil, errors.New("tls: invalid session encoding") |
| } |
| return ss, nil |
| } |
| ss.isClient = true |
| if len(ss.peerCertificates) == 0 { |
| return nil, errors.New("tls: no server certificates in client session") |
| } |
| if ss.version < VersionTLS13 { |
| if !s.Empty() { |
| return nil, errors.New("tls: invalid session encoding") |
| } |
| return ss, nil |
| } |
| if !s.ReadUint64(&ss.useBy) || !s.ReadUint32(&ss.ageAdd) || !s.Empty() { |
| return nil, errors.New("tls: invalid session encoding") |
| } |
| return ss, nil |
| } |
| |
| // sessionState returns a partially filled-out [SessionState] with information |
| // from the current connection. |
| func (c *Conn) sessionState() (*SessionState, error) { |
| return &SessionState{ |
| version: c.vers, |
| cipherSuite: c.cipherSuite, |
| createdAt: uint64(c.config.time().Unix()), |
| alpnProtocol: c.clientProtocol, |
| peerCertificates: c.peerCertificates, |
| activeCertHandles: c.activeCertHandles, |
| ocspResponse: c.ocspResponse, |
| scts: c.scts, |
| isClient: c.isClient, |
| extMasterSecret: c.extMasterSecret, |
| verifiedChains: c.verifiedChains, |
| }, nil |
| } |
| |
| // EncryptTicket encrypts a ticket with the [Config]'s configured (or default) |
| // session ticket keys. It can be used as a [Config.WrapSession] implementation. |
| func (c *Config) EncryptTicket(cs ConnectionState, ss *SessionState) ([]byte, error) { |
| ticketKeys := c.ticketKeys(nil) |
| stateBytes, err := ss.Bytes() |
| if err != nil { |
| return nil, err |
| } |
| return c.encryptTicket(stateBytes, ticketKeys) |
| } |
| |
| func (c *Config) encryptTicket(state []byte, ticketKeys []ticketKey) ([]byte, error) { |
| if len(ticketKeys) == 0 { |
| return nil, errors.New("tls: internal error: session ticket keys unavailable") |
| } |
| |
| encrypted := make([]byte, aes.BlockSize+len(state)+sha256.Size) |
| iv := encrypted[:aes.BlockSize] |
| ciphertext := encrypted[aes.BlockSize : len(encrypted)-sha256.Size] |
| authenticated := encrypted[:len(encrypted)-sha256.Size] |
| macBytes := encrypted[len(encrypted)-sha256.Size:] |
| |
| if _, err := io.ReadFull(c.rand(), iv); err != nil { |
| return nil, err |
| } |
| key := ticketKeys[0] |
| block, err := aes.NewCipher(key.aesKey[:]) |
| if err != nil { |
| return nil, errors.New("tls: failed to create cipher while encrypting ticket: " + err.Error()) |
| } |
| cipher.NewCTR(block, iv).XORKeyStream(ciphertext, state) |
| |
| mac := hmac.New(sha256.New, key.hmacKey[:]) |
| mac.Write(authenticated) |
| mac.Sum(macBytes[:0]) |
| |
| return encrypted, nil |
| } |
| |
| // DecryptTicket decrypts a ticket encrypted by [Config.EncryptTicket]. It can |
| // be used as a [Config.UnwrapSession] implementation. |
| // |
| // If the ticket can't be decrypted or parsed, DecryptTicket returns (nil, nil). |
| func (c *Config) DecryptTicket(identity []byte, cs ConnectionState) (*SessionState, error) { |
| ticketKeys := c.ticketKeys(nil) |
| stateBytes := c.decryptTicket(identity, ticketKeys) |
| if stateBytes == nil { |
| return nil, nil |
| } |
| s, err := ParseSessionState(stateBytes) |
| if err != nil { |
| return nil, nil // drop unparsable tickets on the floor |
| } |
| return s, nil |
| } |
| |
| func (c *Config) decryptTicket(encrypted []byte, ticketKeys []ticketKey) []byte { |
| if len(encrypted) < aes.BlockSize+sha256.Size { |
| return nil |
| } |
| |
| iv := encrypted[:aes.BlockSize] |
| ciphertext := encrypted[aes.BlockSize : len(encrypted)-sha256.Size] |
| authenticated := encrypted[:len(encrypted)-sha256.Size] |
| macBytes := encrypted[len(encrypted)-sha256.Size:] |
| |
| for _, key := range ticketKeys { |
| mac := hmac.New(sha256.New, key.hmacKey[:]) |
| mac.Write(authenticated) |
| expected := mac.Sum(nil) |
| |
| if subtle.ConstantTimeCompare(macBytes, expected) != 1 { |
| continue |
| } |
| |
| block, err := aes.NewCipher(key.aesKey[:]) |
| if err != nil { |
| return nil |
| } |
| plaintext := make([]byte, len(ciphertext)) |
| cipher.NewCTR(block, iv).XORKeyStream(plaintext, ciphertext) |
| |
| return plaintext |
| } |
| |
| return nil |
| } |
| |
| // ClientSessionState contains the state needed by a client to |
| // resume a previous TLS session. |
| type ClientSessionState struct { |
| ticket []byte |
| session *SessionState |
| } |
| |
| // ResumptionState returns the session ticket sent by the server (also known as |
| // the session's identity) and the state necessary to resume this session. |
| // |
| // It can be called by [ClientSessionCache.Put] to serialize (with |
| // [SessionState.Bytes]) and store the session. |
| func (cs *ClientSessionState) ResumptionState() (ticket []byte, state *SessionState, err error) { |
| return cs.ticket, cs.session, nil |
| } |
| |
| // NewResumptionState returns a state value that can be returned by |
| // [ClientSessionCache.Get] to resume a previous session. |
| // |
| // state needs to be returned by [ParseSessionState], and the ticket and session |
| // state must have been returned by [ClientSessionState.ResumptionState]. |
| func NewResumptionState(ticket []byte, state *SessionState) (*ClientSessionState, error) { |
| return &ClientSessionState{ |
| ticket: ticket, session: state, |
| }, nil |
| } |