mirror of
https://github.com/XTLS/REALITY.git
synced 2025-08-22 22:48:36 +00:00
crypto/tls: use SessionState on the client side
Another internal change, that allows exposing the new APIs easily in following CLs. For #60105 Change-Id: I9c61b9f6e9d29af633f952444f514bcbbe82fe4e Reviewed-on: https://go-review.googlesource.com/c/go/+/496819 Reviewed-by: Matthew Dempsky <mdempsky@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Damien Neil <dneil@google.com> Run-TryBot: Filippo Valsorda <filippo@golang.org>
This commit is contained in:
parent
5320b1b20a
commit
b417836544
2
cache.go
2
cache.go
@ -39,7 +39,7 @@ type certCache struct {
|
|||||||
sync.Map
|
sync.Map
|
||||||
}
|
}
|
||||||
|
|
||||||
var clientCertCache = new(certCache)
|
var globalCertCache = new(certCache)
|
||||||
|
|
||||||
// activeCert is a handle to a certificate held in the cache. Once there are
|
// activeCert is a handle to a certificate held in the cache. Once there are
|
||||||
// no alive activeCerts for a given certificate, the certificate is removed
|
// no alive activeCerts for a given certificate, the certificate is removed
|
||||||
|
19
common.go
19
common.go
@ -330,25 +330,6 @@ func requiresClientCert(c ClientAuthType) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientSessionState contains the state needed by clients to resume TLS
|
|
||||||
// sessions.
|
|
||||||
type ClientSessionState struct {
|
|
||||||
sessionTicket []uint8 // Encrypted ticket used for session resumption with server
|
|
||||||
vers uint16 // TLS version negotiated for the session
|
|
||||||
cipherSuite uint16 // Ciphersuite negotiated for the session
|
|
||||||
masterSecret []byte // Full handshake MasterSecret, or TLS 1.3 resumption_master_secret
|
|
||||||
serverCertificates []*x509.Certificate // Certificate chain presented by the server
|
|
||||||
verifiedChains [][]*x509.Certificate // Certificate chains we built for verification
|
|
||||||
receivedAt time.Time // When the session ticket was received from the server
|
|
||||||
ocspResponse []byte // Stapled OCSP response presented by the server
|
|
||||||
scts [][]byte // SCTs presented by the server
|
|
||||||
|
|
||||||
// TLS 1.3 fields.
|
|
||||||
nonce []byte // Ticket nonce sent by the server, to derive PSK
|
|
||||||
useBy time.Time // Expiration of the ticket lifetime as set by the server
|
|
||||||
ageAdd uint32 // Random obfuscation factor for sending the ticket age
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClientSessionCache is a cache of ClientSessionState objects that can be used
|
// ClientSessionCache is a cache of ClientSessionState objects that can be used
|
||||||
// by a client to resume a TLS session with a given server. ClientSessionCache
|
// by a client to resume a TLS session with a given server. ClientSessionCache
|
||||||
// implementations should expect to be called concurrently from different
|
// implementations should expect to be called concurrently from different
|
||||||
|
@ -31,7 +31,8 @@ type clientHandshakeState struct {
|
|||||||
suite *cipherSuite
|
suite *cipherSuite
|
||||||
finishedHash finishedHash
|
finishedHash finishedHash
|
||||||
masterSecret []byte
|
masterSecret []byte
|
||||||
session *ClientSessionState
|
session *SessionState // the session being resumed
|
||||||
|
ticket []byte // a fresh ticket received during this handshake
|
||||||
}
|
}
|
||||||
|
|
||||||
var testingOnlyForceClientHelloSignatureAlgorithms []SignatureScheme
|
var testingOnlyForceClientHelloSignatureAlgorithms []SignatureScheme
|
||||||
@ -177,11 +178,11 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) {
|
|||||||
}
|
}
|
||||||
c.serverName = hello.serverName
|
c.serverName = hello.serverName
|
||||||
|
|
||||||
cacheKey, session, earlySecret, binderKey, err := c.loadSession(hello)
|
session, earlySecret, binderKey, err := c.loadSession(hello)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if cacheKey != "" && session != nil {
|
if session != nil {
|
||||||
defer func() {
|
defer func() {
|
||||||
// If we got a handshake failure when resuming a session, throw away
|
// If we got a handshake failure when resuming a session, throw away
|
||||||
// the session ticket. See RFC 5077, Section 3.2.
|
// the session ticket. See RFC 5077, Section 3.2.
|
||||||
@ -190,7 +191,9 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) {
|
|||||||
// does require servers to abort on invalid binders, so we need to
|
// does require servers to abort on invalid binders, so we need to
|
||||||
// delete tickets to recover from a corrupted PSK.
|
// delete tickets to recover from a corrupted PSK.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.config.ClientSessionCache.Put(cacheKey, nil)
|
if cacheKey := c.clientSessionCacheKey(); cacheKey != "" {
|
||||||
|
c.config.ClientSessionCache.Put(cacheKey, nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@ -255,19 +258,13 @@ func (c *Conn) clientHandshake(ctx context.Context) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we had a successful handshake and hs.session is different from
|
|
||||||
// the one already cached - cache a new one.
|
|
||||||
if cacheKey != "" && hs.session != nil && session != hs.session {
|
|
||||||
c.config.ClientSessionCache.Put(cacheKey, hs.session)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) loadSession(hello *clientHelloMsg) (cacheKey string,
|
func (c *Conn) loadSession(hello *clientHelloMsg) (
|
||||||
session *ClientSessionState, earlySecret, binderKey []byte, err error) {
|
session *SessionState, earlySecret, binderKey []byte, err error) {
|
||||||
if c.config.SessionTicketsDisabled || c.config.ClientSessionCache == nil {
|
if c.config.SessionTicketsDisabled || c.config.ClientSessionCache == nil {
|
||||||
return "", nil, nil, nil, nil
|
return nil, nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
hello.ticketSupported = true
|
hello.ticketSupported = true
|
||||||
@ -282,29 +279,30 @@ func (c *Conn) loadSession(hello *clientHelloMsg) (cacheKey string,
|
|||||||
// renegotiation is primarily used to allow a client to send a client
|
// renegotiation is primarily used to allow a client to send a client
|
||||||
// certificate, which would be skipped if session resumption occurred.
|
// certificate, which would be skipped if session resumption occurred.
|
||||||
if c.handshakes != 0 {
|
if c.handshakes != 0 {
|
||||||
return "", nil, nil, nil, nil
|
return nil, nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to resume a previously negotiated TLS session, if available.
|
// Try to resume a previously negotiated TLS session, if available.
|
||||||
cacheKey = c.clientSessionCacheKey()
|
cacheKey := c.clientSessionCacheKey()
|
||||||
if cacheKey == "" {
|
if cacheKey == "" {
|
||||||
return "", nil, nil, nil, nil
|
return nil, nil, nil, nil
|
||||||
}
|
}
|
||||||
session, ok := c.config.ClientSessionCache.Get(cacheKey)
|
cs, ok := c.config.ClientSessionCache.Get(cacheKey)
|
||||||
if !ok || session == nil {
|
if !ok || cs == nil {
|
||||||
return cacheKey, nil, nil, nil, nil
|
return nil, nil, nil, nil
|
||||||
}
|
}
|
||||||
|
session = cs.session
|
||||||
|
|
||||||
// Check that version used for the previous session is still valid.
|
// Check that version used for the previous session is still valid.
|
||||||
versOk := false
|
versOk := false
|
||||||
for _, v := range hello.supportedVersions {
|
for _, v := range hello.supportedVersions {
|
||||||
if v == session.vers {
|
if v == session.version {
|
||||||
versOk = true
|
versOk = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !versOk {
|
if !versOk {
|
||||||
return cacheKey, nil, nil, nil, nil
|
return nil, nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that the cached server certificate is not expired, and that it's
|
// Check that the cached server certificate is not expired, and that it's
|
||||||
@ -313,41 +311,41 @@ func (c *Conn) loadSession(hello *clientHelloMsg) (cacheKey string,
|
|||||||
if !c.config.InsecureSkipVerify {
|
if !c.config.InsecureSkipVerify {
|
||||||
if len(session.verifiedChains) == 0 {
|
if len(session.verifiedChains) == 0 {
|
||||||
// The original connection had InsecureSkipVerify, while this doesn't.
|
// The original connection had InsecureSkipVerify, while this doesn't.
|
||||||
return cacheKey, nil, nil, nil, nil
|
return nil, nil, nil, nil
|
||||||
}
|
}
|
||||||
serverCert := session.serverCertificates[0]
|
serverCert := session.peerCertificates[0]
|
||||||
if c.config.time().After(serverCert.NotAfter) {
|
if c.config.time().After(serverCert.NotAfter) {
|
||||||
// Expired certificate, delete the entry.
|
// Expired certificate, delete the entry.
|
||||||
c.config.ClientSessionCache.Put(cacheKey, nil)
|
c.config.ClientSessionCache.Put(cacheKey, nil)
|
||||||
return cacheKey, nil, nil, nil, nil
|
return nil, nil, nil, nil
|
||||||
}
|
}
|
||||||
if err := serverCert.VerifyHostname(c.config.ServerName); err != nil {
|
if err := serverCert.VerifyHostname(c.config.ServerName); err != nil {
|
||||||
return cacheKey, nil, nil, nil, nil
|
return nil, nil, nil, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if session.vers != VersionTLS13 {
|
if session.version != VersionTLS13 {
|
||||||
// In TLS 1.2 the cipher suite must match the resumed session. Ensure we
|
// In TLS 1.2 the cipher suite must match the resumed session. Ensure we
|
||||||
// are still offering it.
|
// are still offering it.
|
||||||
if mutualCipherSuite(hello.cipherSuites, session.cipherSuite) == nil {
|
if mutualCipherSuite(hello.cipherSuites, session.cipherSuite) == nil {
|
||||||
return cacheKey, nil, nil, nil, nil
|
return nil, nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
hello.sessionTicket = session.sessionTicket
|
hello.sessionTicket = cs.ticket
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check that the session ticket is not expired.
|
// Check that the session ticket is not expired.
|
||||||
if c.config.time().After(session.useBy) {
|
if c.config.time().After(time.Unix(int64(session.useBy), 0)) {
|
||||||
c.config.ClientSessionCache.Put(cacheKey, nil)
|
c.config.ClientSessionCache.Put(cacheKey, nil)
|
||||||
return cacheKey, nil, nil, nil, nil
|
return nil, nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// In TLS 1.3 the KDF hash must match the resumed session. Ensure we
|
// In TLS 1.3 the KDF hash must match the resumed session. Ensure we
|
||||||
// offer at least one cipher suite with that hash.
|
// offer at least one cipher suite with that hash.
|
||||||
cipherSuite := cipherSuiteTLS13ByID(session.cipherSuite)
|
cipherSuite := cipherSuiteTLS13ByID(session.cipherSuite)
|
||||||
if cipherSuite == nil {
|
if cipherSuite == nil {
|
||||||
return cacheKey, nil, nil, nil, nil
|
return nil, nil, nil, nil
|
||||||
}
|
}
|
||||||
cipherSuiteOk := false
|
cipherSuiteOk := false
|
||||||
for _, offeredID := range hello.cipherSuites {
|
for _, offeredID := range hello.cipherSuites {
|
||||||
@ -358,32 +356,30 @@ func (c *Conn) loadSession(hello *clientHelloMsg) (cacheKey string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !cipherSuiteOk {
|
if !cipherSuiteOk {
|
||||||
return cacheKey, nil, nil, nil, nil
|
return nil, nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 := uint32(c.config.time().Sub(session.receivedAt) / time.Millisecond)
|
ticketAge := c.config.time().Sub(time.Unix(int64(session.createdAt), 0))
|
||||||
identity := pskIdentity{
|
identity := pskIdentity{
|
||||||
label: session.sessionTicket,
|
label: cs.ticket,
|
||||||
obfuscatedTicketAge: ticketAge + session.ageAdd,
|
obfuscatedTicketAge: uint32(ticketAge/time.Millisecond) + session.ageAdd,
|
||||||
}
|
}
|
||||||
hello.pskIdentities = []pskIdentity{identity}
|
hello.pskIdentities = []pskIdentity{identity}
|
||||||
hello.pskBinders = [][]byte{make([]byte, cipherSuite.hash.Size())}
|
hello.pskBinders = [][]byte{make([]byte, cipherSuite.hash.Size())}
|
||||||
|
|
||||||
// Compute the PSK binders. See RFC 8446, Section 4.2.11.2.
|
// Compute the PSK binders. See RFC 8446, Section 4.2.11.2.
|
||||||
psk := cipherSuite.expandLabel(session.masterSecret, "resumption",
|
earlySecret = cipherSuite.extract(session.secret, nil)
|
||||||
session.nonce, cipherSuite.hash.Size())
|
|
||||||
earlySecret = cipherSuite.extract(psk, nil)
|
|
||||||
binderKey = cipherSuite.deriveSecret(earlySecret, resumptionBinderLabel, nil)
|
binderKey = cipherSuite.deriveSecret(earlySecret, resumptionBinderLabel, nil)
|
||||||
transcript := cipherSuite.hash.New()
|
transcript := cipherSuite.hash.New()
|
||||||
helloBytes, err := hello.marshalWithoutBinders()
|
helloBytes, err := hello.marshalWithoutBinders()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
transcript.Write(helloBytes)
|
transcript.Write(helloBytes)
|
||||||
pskBinders := [][]byte{cipherSuite.finishedHash(binderKey, transcript)}
|
pskBinders := [][]byte{cipherSuite.finishedHash(binderKey, transcript)}
|
||||||
if err := hello.updateBinders(pskBinders); err != nil {
|
if err := hello.updateBinders(pskBinders); err != nil {
|
||||||
return "", nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@ -485,6 +481,9 @@ func (hs *clientHandshakeState) handshake() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err := hs.saveSessionTicket(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
c.ekm = ekmFromMasterSecret(c.vers, hs.suite, hs.masterSecret, hs.hello.random, hs.serverHello.random)
|
c.ekm = ekmFromMasterSecret(c.vers, hs.suite, hs.masterSecret, hs.hello.random, hs.serverHello.random)
|
||||||
c.isHandshakeComplete.Store(true)
|
c.isHandshakeComplete.Store(true)
|
||||||
@ -752,7 +751,7 @@ func (hs *clientHandshakeState) processServerHello() (bool, error) {
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if hs.session.vers != c.vers {
|
if hs.session.version != c.vers {
|
||||||
c.sendAlert(alertHandshakeFailure)
|
c.sendAlert(alertHandshakeFailure)
|
||||||
return false, errors.New("tls: server resumed a session with a different version")
|
return false, errors.New("tls: server resumed a session with a different version")
|
||||||
}
|
}
|
||||||
@ -762,9 +761,10 @@ func (hs *clientHandshakeState) processServerHello() (bool, error) {
|
|||||||
return false, errors.New("tls: server resumed a session with a different cipher suite")
|
return false, errors.New("tls: server resumed a session with a different cipher suite")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore masterSecret, peerCerts, and ocspResponse from previous state
|
// Restore master secret and certificates from previous state
|
||||||
hs.masterSecret = hs.session.masterSecret
|
hs.masterSecret = hs.session.secret
|
||||||
c.peerCertificates = hs.session.serverCertificates
|
c.peerCertificates = hs.session.peerCertificates
|
||||||
|
c.activeCertHandles = hs.c.activeCertHandles
|
||||||
c.verifiedChains = hs.session.verifiedChains
|
c.verifiedChains = hs.session.verifiedChains
|
||||||
c.ocspResponse = hs.session.ocspResponse
|
c.ocspResponse = hs.session.ocspResponse
|
||||||
// Let the ServerHello SCTs override the session SCTs from the original
|
// Let the ServerHello SCTs override the session SCTs from the original
|
||||||
@ -836,8 +836,13 @@ func (hs *clientHandshakeState) readSessionTicket() error {
|
|||||||
if !hs.serverHello.ticketSupported {
|
if !hs.serverHello.ticketSupported {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
c := hs.c
|
c := hs.c
|
||||||
|
|
||||||
|
if !hs.hello.ticketSupported {
|
||||||
|
c.sendAlert(alertIllegalParameter)
|
||||||
|
return errors.New("tls: server sent unrequested session ticket")
|
||||||
|
}
|
||||||
|
|
||||||
msg, err := c.readHandshake(&hs.finishedHash)
|
msg, err := c.readHandshake(&hs.finishedHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -848,18 +853,29 @@ func (hs *clientHandshakeState) readSessionTicket() error {
|
|||||||
return unexpectedMessageError(sessionTicketMsg, msg)
|
return unexpectedMessageError(sessionTicketMsg, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
hs.session = &ClientSessionState{
|
hs.ticket = sessionTicketMsg.ticket
|
||||||
sessionTicket: sessionTicketMsg.ticket,
|
return nil
|
||||||
vers: c.vers,
|
}
|
||||||
cipherSuite: hs.suite.id,
|
|
||||||
masterSecret: hs.masterSecret,
|
func (hs *clientHandshakeState) saveSessionTicket() error {
|
||||||
serverCertificates: c.peerCertificates,
|
if hs.ticket == nil {
|
||||||
verifiedChains: c.verifiedChains,
|
return nil
|
||||||
receivedAt: c.config.time(),
|
}
|
||||||
ocspResponse: c.ocspResponse,
|
c := hs.c
|
||||||
scts: c.scts,
|
|
||||||
|
cacheKey := c.clientSessionCacheKey()
|
||||||
|
if cacheKey == "" {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
session, err := c.sessionState()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
session.secret = hs.masterSecret
|
||||||
|
|
||||||
|
cs := &ClientSessionState{ticket: hs.ticket, session: session}
|
||||||
|
c.config.ClientSessionCache.Put(cacheKey, cs)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -885,7 +901,7 @@ func (c *Conn) verifyServerCertificate(certificates [][]byte) error {
|
|||||||
activeHandles := make([]*activeCert, len(certificates))
|
activeHandles := make([]*activeCert, len(certificates))
|
||||||
certs := make([]*x509.Certificate, len(certificates))
|
certs := make([]*x509.Certificate, len(certificates))
|
||||||
for i, asn1Data := range certificates {
|
for i, asn1Data := range certificates {
|
||||||
cert, err := clientCertCache.newCert(asn1Data)
|
cert, err := globalCertCache.newCert(asn1Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.sendAlert(alertBadCertificate)
|
c.sendAlert(alertBadCertificate)
|
||||||
return errors.New("tls: failed to parse certificate from server: " + err.Error())
|
return errors.New("tls: failed to parse certificate from server: " + err.Error())
|
||||||
|
@ -23,7 +23,7 @@ type clientHandshakeStateTLS13 struct {
|
|||||||
hello *clientHelloMsg
|
hello *clientHelloMsg
|
||||||
ecdheKey *ecdh.PrivateKey
|
ecdheKey *ecdh.PrivateKey
|
||||||
|
|
||||||
session *ClientSessionState
|
session *SessionState
|
||||||
earlySecret []byte
|
earlySecret []byte
|
||||||
binderKey []byte
|
binderKey []byte
|
||||||
|
|
||||||
@ -256,8 +256,8 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error {
|
|||||||
}
|
}
|
||||||
if pskSuite.hash == hs.suite.hash {
|
if pskSuite.hash == hs.suite.hash {
|
||||||
// Update binders and obfuscated_ticket_age.
|
// Update binders and obfuscated_ticket_age.
|
||||||
ticketAge := uint32(c.config.time().Sub(hs.session.receivedAt) / time.Millisecond)
|
ticketAge := c.config.time().Sub(time.Unix(int64(hs.session.createdAt), 0))
|
||||||
hs.hello.pskIdentities[0].obfuscatedTicketAge = ticketAge + hs.session.ageAdd
|
hs.hello.pskIdentities[0].obfuscatedTicketAge = uint32(ticketAge/time.Millisecond) + hs.session.ageAdd
|
||||||
|
|
||||||
transcript := hs.suite.hash.New()
|
transcript := hs.suite.hash.New()
|
||||||
transcript.Write([]byte{typeMessageHash, 0, 0, uint8(len(chHash))})
|
transcript.Write([]byte{typeMessageHash, 0, 0, uint8(len(chHash))})
|
||||||
@ -355,7 +355,8 @@ func (hs *clientHandshakeStateTLS13) processServerHello() error {
|
|||||||
|
|
||||||
hs.usingPSK = true
|
hs.usingPSK = true
|
||||||
c.didResume = true
|
c.didResume = true
|
||||||
c.peerCertificates = hs.session.serverCertificates
|
c.peerCertificates = hs.session.peerCertificates
|
||||||
|
c.activeCertHandles = hs.session.activeCertHandles
|
||||||
c.verifiedChains = hs.session.verifiedChains
|
c.verifiedChains = hs.session.verifiedChains
|
||||||
c.ocspResponse = hs.session.ocspResponse
|
c.ocspResponse = hs.session.ocspResponse
|
||||||
c.scts = hs.session.scts
|
c.scts = hs.session.scts
|
||||||
@ -719,28 +720,21 @@ func (c *Conn) handleNewSessionTicket(msg *newSessionTicketMsgTLS13) error {
|
|||||||
return c.sendAlert(alertInternalError)
|
return c.sendAlert(alertInternalError)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the resumption_master_secret and nonce instead of deriving the PSK
|
psk := cipherSuite.expandLabel(c.resumptionSecret, "resumption",
|
||||||
// to do the least amount of work on NewSessionTicket messages before we
|
msg.nonce, cipherSuite.hash.Size())
|
||||||
// know if the ticket will be used. Forward secrecy of resumed connections
|
|
||||||
// is guaranteed by the requirement for pskModeDHE.
|
|
||||||
session := &ClientSessionState{
|
|
||||||
sessionTicket: msg.label,
|
|
||||||
vers: c.vers,
|
|
||||||
cipherSuite: c.cipherSuite,
|
|
||||||
masterSecret: c.resumptionSecret,
|
|
||||||
serverCertificates: c.peerCertificates,
|
|
||||||
verifiedChains: c.verifiedChains,
|
|
||||||
receivedAt: c.config.time(),
|
|
||||||
nonce: msg.nonce,
|
|
||||||
useBy: c.config.time().Add(lifetime),
|
|
||||||
ageAdd: msg.ageAdd,
|
|
||||||
ocspResponse: c.ocspResponse,
|
|
||||||
scts: c.scts,
|
|
||||||
}
|
|
||||||
|
|
||||||
cacheKey := c.clientSessionCacheKey()
|
session, err := c.sessionState()
|
||||||
if cacheKey != "" {
|
if err != nil {
|
||||||
c.config.ClientSessionCache.Put(cacheKey, session)
|
c.sendAlert(alertInternalError)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
session.secret = psk
|
||||||
|
session.useBy = uint64(c.config.time().Add(lifetime).Unix())
|
||||||
|
session.ageAdd = msg.ageAdd
|
||||||
|
cs := &ClientSessionState{ticket: msg.label, session: session}
|
||||||
|
|
||||||
|
if cacheKey := c.clientSessionCacheKey(); cacheKey != "" {
|
||||||
|
c.config.ClientSessionCache.Put(cacheKey, cs)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -448,7 +448,7 @@ func (hs *serverHandshakeState) checkForResumption() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionHasClientCerts := len(hs.sessionState.certificate.Certificate) != 0
|
sessionHasClientCerts := len(hs.sessionState.peerCertificates) != 0
|
||||||
needClientCerts := requiresClientCert(c.config.ClientAuth)
|
needClientCerts := requiresClientCert(c.config.ClientAuth)
|
||||||
if needClientCerts && !sessionHasClientCerts {
|
if needClientCerts && !sessionHasClientCerts {
|
||||||
return false
|
return false
|
||||||
@ -481,7 +481,7 @@ func (hs *serverHandshakeState) doResumeHandshake() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.processCertsFromClient(hs.sessionState.certificate); err != nil {
|
if err := c.processCertsFromClient(hs.sessionState.certificate()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -759,27 +759,15 @@ func (hs *serverHandshakeState) sendSessionTicket() error {
|
|||||||
c := hs.c
|
c := hs.c
|
||||||
m := new(newSessionTicketMsg)
|
m := new(newSessionTicketMsg)
|
||||||
|
|
||||||
createdAt := uint64(c.config.time().Unix())
|
state, err := c.sessionState()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
state.secret = hs.masterSecret
|
||||||
if hs.sessionState != nil {
|
if hs.sessionState != nil {
|
||||||
// If this is re-wrapping an old key, then keep
|
// If this is re-wrapping an old key, then keep
|
||||||
// the original time it was created.
|
// the original time it was created.
|
||||||
createdAt = hs.sessionState.createdAt
|
state.createdAt = hs.sessionState.createdAt
|
||||||
}
|
|
||||||
|
|
||||||
var certsFromClient [][]byte
|
|
||||||
for _, cert := range c.peerCertificates {
|
|
||||||
certsFromClient = append(certsFromClient, cert.Raw)
|
|
||||||
}
|
|
||||||
state := SessionState{
|
|
||||||
version: c.vers,
|
|
||||||
cipherSuite: hs.suite.id,
|
|
||||||
createdAt: createdAt,
|
|
||||||
secret: hs.masterSecret,
|
|
||||||
certificate: Certificate{
|
|
||||||
Certificate: certsFromClient,
|
|
||||||
OCSPStaple: c.ocspResponse,
|
|
||||||
SignedCertificateTimestamps: c.scts,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
stateBytes, err := state.Bytes()
|
stateBytes, err := state.Bytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -353,7 +353,7 @@ func (hs *serverHandshakeStateTLS13) checkForResumption() error {
|
|||||||
// PSK connections don't re-establish client certificates, but carry
|
// PSK connections don't re-establish client certificates, but carry
|
||||||
// them over in the session ticket. Ensure the presence of client certs
|
// them over in the session ticket. Ensure the presence of client certs
|
||||||
// in the ticket is consistent with the configured requirements.
|
// in the ticket is consistent with the configured requirements.
|
||||||
sessionHasClientCerts := len(sessionState.certificate.Certificate) != 0
|
sessionHasClientCerts := len(sessionState.peerCertificates) != 0
|
||||||
needClientCerts := requiresClientCert(c.config.ClientAuth)
|
needClientCerts := requiresClientCert(c.config.ClientAuth)
|
||||||
if needClientCerts && !sessionHasClientCerts {
|
if needClientCerts && !sessionHasClientCerts {
|
||||||
continue
|
continue
|
||||||
@ -383,7 +383,7 @@ func (hs *serverHandshakeStateTLS13) checkForResumption() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -828,21 +828,11 @@ func (hs *serverHandshakeStateTLS13) sendSessionTickets() error {
|
|||||||
|
|
||||||
m := new(newSessionTicketMsgTLS13)
|
m := new(newSessionTicketMsgTLS13)
|
||||||
|
|
||||||
var certsFromClient [][]byte
|
state, err := c.sessionState()
|
||||||
for _, cert := range c.peerCertificates {
|
if err != nil {
|
||||||
certsFromClient = append(certsFromClient, cert.Raw)
|
return err
|
||||||
}
|
|
||||||
state := &SessionState{
|
|
||||||
version: c.vers,
|
|
||||||
cipherSuite: hs.suite.id,
|
|
||||||
createdAt: uint64(c.config.time().Unix()),
|
|
||||||
secret: psk,
|
|
||||||
certificate: Certificate{
|
|
||||||
Certificate: certsFromClient,
|
|
||||||
OCSPStaple: c.ocspResponse,
|
|
||||||
SignedCertificateTimestamps: c.scts,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
state.secret = psk
|
||||||
stateBytes, err := state.Bytes()
|
stateBytes, err := state.Bytes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.sendAlert(alertInternalError)
|
c.sendAlert(alertInternalError)
|
||||||
|
205
ticket.go
205
ticket.go
@ -10,6 +10,7 @@ import (
|
|||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
|
"crypto/x509"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
@ -18,12 +19,63 @@ import (
|
|||||||
|
|
||||||
// A SessionState is a resumable session.
|
// A SessionState is a resumable session.
|
||||||
type SessionState struct {
|
type SessionState struct {
|
||||||
version uint16 // uint16 version;
|
// Encoded as a SessionState (in the language of RFC 8446, Section 3).
|
||||||
// uint8 revision = 1;
|
//
|
||||||
|
// enum { server(1), client(2) } SessionStateType;
|
||||||
|
//
|
||||||
|
// opaque Certificate<1..2^24-1>;
|
||||||
|
//
|
||||||
|
// Certificate CertificateChain<0..2^24-1>;
|
||||||
|
//
|
||||||
|
// struct {
|
||||||
|
// uint16 version;
|
||||||
|
// SessionStateType type;
|
||||||
|
// uint16 cipher_suite;
|
||||||
|
// uint64 created_at;
|
||||||
|
// opaque secret<1..2^8-1>;
|
||||||
|
// CertificateEntry certificate_list<0..2^24-1>;
|
||||||
|
// select (SessionState.type) {
|
||||||
|
// case server: /* empty */;
|
||||||
|
// case client: struct {
|
||||||
|
// CertificateChain verified_chains<0..2^24-1>; /* excluding leaf */
|
||||||
|
// select (SessionState.version) {
|
||||||
|
// case VersionTLS10..VersionTLS12: /* empty */;
|
||||||
|
// case VersionTLS13: struct {
|
||||||
|
// uint64 use_by;
|
||||||
|
// uint32 age_add;
|
||||||
|
// };
|
||||||
|
// };
|
||||||
|
// };
|
||||||
|
// };
|
||||||
|
// } SessionState;
|
||||||
|
//
|
||||||
|
|
||||||
|
version uint16
|
||||||
|
isClient bool
|
||||||
cipherSuite uint16
|
cipherSuite uint16
|
||||||
createdAt uint64
|
// createdAt is the generation time of the secret on the sever (which for
|
||||||
secret []byte // opaque master_secret<1..2^8-1>;
|
// TLS 1.0–1.2 might be earlier than the current session) and the time at
|
||||||
certificate Certificate // CertificateEntry certificate_list<0..2^24-1>;
|
// 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
|
||||||
|
peerCertificates []*x509.Certificate
|
||||||
|
activeCertHandles []*activeCert
|
||||||
|
ocspResponse []byte
|
||||||
|
scts [][]byte
|
||||||
|
|
||||||
|
// Client-side fields.
|
||||||
|
verifiedChains [][]*x509.Certificate
|
||||||
|
|
||||||
|
// Client-side TLS 1.3-only fields.
|
||||||
|
useBy uint64 // seconds since UNIX epoch
|
||||||
|
ageAdd uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientSessionState contains the state needed by clients to resume TLS
|
||||||
|
// sessions.
|
||||||
|
type ClientSessionState struct {
|
||||||
|
ticket []byte
|
||||||
|
session *SessionState
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bytes encodes the session, including any private fields, so that it can be
|
// Bytes encodes the session, including any private fields, so that it can be
|
||||||
@ -31,38 +83,157 @@ type SessionState struct {
|
|||||||
//
|
//
|
||||||
// The specific encoding should be considered opaque and may change incompatibly
|
// The specific encoding should be considered opaque and may change incompatibly
|
||||||
// between Go versions.
|
// between Go versions.
|
||||||
func (m *SessionState) Bytes() ([]byte, error) {
|
func (s *SessionState) Bytes() ([]byte, error) {
|
||||||
var b cryptobyte.Builder
|
var b cryptobyte.Builder
|
||||||
b.AddUint16(m.version)
|
b.AddUint16(s.version)
|
||||||
b.AddUint8(1) // revision
|
if s.isClient {
|
||||||
b.AddUint16(m.cipherSuite)
|
b.AddUint8(2) // client
|
||||||
addUint64(&b, m.createdAt)
|
} else {
|
||||||
|
b.AddUint8(1) // server
|
||||||
|
}
|
||||||
|
b.AddUint16(s.cipherSuite)
|
||||||
|
addUint64(&b, s.createdAt)
|
||||||
b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
|
b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
|
||||||
b.AddBytes(m.secret)
|
b.AddBytes(s.secret)
|
||||||
})
|
})
|
||||||
marshalCertificate(&b, m.certificate)
|
marshalCertificate(&b, s.certificate())
|
||||||
|
if s.isClient {
|
||||||
|
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.version >= VersionTLS13 {
|
||||||
|
addUint64(&b, s.useBy)
|
||||||
|
b.AddUint32(s.ageAdd)
|
||||||
|
}
|
||||||
|
}
|
||||||
return b.Bytes()
|
return b.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SessionState) certificate() Certificate {
|
||||||
|
return Certificate{
|
||||||
|
Certificate: certificatesToBytesSlice(s.peerCertificates),
|
||||||
|
OCSPStaple: s.ocspResponse,
|
||||||
|
SignedCertificateTimestamps: s.scts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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].
|
// ParseSessionState parses a [SessionState] encoded by [SessionState.Bytes].
|
||||||
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 revision uint8
|
var typ uint8
|
||||||
|
var cert Certificate
|
||||||
if !s.ReadUint16(&ss.version) ||
|
if !s.ReadUint16(&ss.version) ||
|
||||||
!s.ReadUint8(&revision) ||
|
!s.ReadUint8(&typ) ||
|
||||||
revision != 1 ||
|
(typ != 1 && typ != 2) ||
|
||||||
!s.ReadUint16(&ss.cipherSuite) ||
|
!s.ReadUint16(&ss.cipherSuite) ||
|
||||||
!readUint64(&s, &ss.createdAt) ||
|
!readUint64(&s, &ss.createdAt) ||
|
||||||
!readUint8LengthPrefixed(&s, &ss.secret) ||
|
!readUint8LengthPrefixed(&s, &ss.secret) ||
|
||||||
len(ss.secret) == 0 ||
|
len(ss.secret) == 0 ||
|
||||||
!unmarshalCertificate(&s, &ss.certificate) ||
|
!unmarshalCertificate(&s, &cert) {
|
||||||
!s.Empty() {
|
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
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
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
|
||||||
|
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.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 nil, errors.New("tls: invalid session encoding")
|
||||||
}
|
}
|
||||||
return ss, nil
|
return ss, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sessionState returns a partially filled-out [SessionState] with information
|
||||||
|
// from the current connection.
|
||||||
|
func (c *Conn) sessionState() (*SessionState, error) {
|
||||||
|
var verifiedChains [][]*x509.Certificate
|
||||||
|
if c.isClient {
|
||||||
|
verifiedChains = c.verifiedChains
|
||||||
|
if len(c.peerCertificates) == 0 {
|
||||||
|
return nil, errors.New("tls: internal error: empty peer certificates")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &SessionState{
|
||||||
|
version: c.vers,
|
||||||
|
cipherSuite: c.cipherSuite,
|
||||||
|
createdAt: uint64(c.config.time().Unix()),
|
||||||
|
peerCertificates: c.peerCertificates,
|
||||||
|
activeCertHandles: c.activeCertHandles,
|
||||||
|
ocspResponse: c.ocspResponse,
|
||||||
|
scts: c.scts,
|
||||||
|
isClient: c.isClient,
|
||||||
|
verifiedChains: verifiedChains,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Conn) encryptTicket(state []byte) ([]byte, error) {
|
func (c *Conn) encryptTicket(state []byte) ([]byte, error) {
|
||||||
if len(c.ticketKeys) == 0 {
|
if len(c.ticketKeys) == 0 {
|
||||||
return nil, errors.New("tls: internal error: session ticket keys unavailable")
|
return nil, errors.New("tls: internal error: session ticket keys unavailable")
|
||||||
|
Loading…
Reference in New Issue
Block a user