]> Cypherpunks.ru repositories - gostls13.git/commitdiff
crypto/tls: implement TLS 1.3 PSK authentication (client side)
authorFilippo Valsorda <filippo@golang.org>
Sun, 4 Nov 2018 23:41:37 +0000 (18:41 -0500)
committerFilippo Valsorda <filippo@golang.org>
Mon, 12 Nov 2018 20:43:23 +0000 (20:43 +0000)
Also check original certificate validity when resuming TLS 1.0–1.2. Will
refuse to resume a session if the certificate is expired or if the
original connection had InsecureSkipVerify and the resumed one doesn't.

Support only PSK+DHE to protect forward secrecy even with lack of a
strong session ticket rotation story.

Tested with NSS because s_server does not provide any way of getting the
same session ticket key across invocations. Will self-test like TLS
1.0–1.2 once server side is implemented.

Incorporates CL 128477 by @santoshankr.

Fixes #24919
Updates #9671

Change-Id: Id3eaa5b6c77544a1357668bf9ff255f3420ecc34
Reviewed-on: https://go-review.googlesource.com/c/147420
Reviewed-by: Adam Langley <agl@golang.org>
src/crypto/tls/cipher_suites.go
src/crypto/tls/common.go
src/crypto/tls/conn.go
src/crypto/tls/handshake_client.go
src/crypto/tls/handshake_client_test.go
src/crypto/tls/handshake_client_tls13.go
src/crypto/tls/handshake_messages.go
src/crypto/tls/handshake_server.go
src/crypto/tls/handshake_server_tls13.go
src/crypto/tls/key_schedule.go
src/crypto/tls/prf_test.go

index 41ab2ba882b5be3f474c1e9157ce1654cf4277be..26e14c992489412682a3db1f2fbf7444cd562bcd 100644 (file)
@@ -399,12 +399,16 @@ func ecdheRSAKA(version uint16) keyAgreement {
 func mutualCipherSuite(have []uint16, want uint16) *cipherSuite {
        for _, id := range have {
                if id == want {
-                       for _, suite := range cipherSuites {
-                               if suite.id == want {
-                                       return suite
-                               }
-                       }
-                       return nil
+                       return cipherSuiteByID(id)
+               }
+       }
+       return nil
+}
+
+func cipherSuiteByID(id uint16) *cipherSuite {
+       for _, cipherSuite := range cipherSuites {
+               if cipherSuite.id == id {
+                       return cipherSuite
                }
        }
        return nil
@@ -413,12 +417,16 @@ func mutualCipherSuite(have []uint16, want uint16) *cipherSuite {
 func mutualCipherSuiteTLS13(have []uint16, want uint16) *cipherSuiteTLS13 {
        for _, id := range have {
                if id == want {
-                       for _, suite := range cipherSuitesTLS13 {
-                               if suite.id == want {
-                                       return suite
-                               }
-                       }
-                       return nil
+                       return cipherSuiteTLS13ByID(id)
+               }
+       }
+       return nil
+}
+
+func cipherSuiteTLS13ByID(id uint16) *cipherSuiteTLS13 {
+       for _, cipherSuite := range cipherSuitesTLS13 {
+               if cipherSuite.id == id {
+                       return cipherSuite
                }
        }
        return nil
index 1d9e0bd0b52b1d5752e2ea5f8eae63dfb04fd2fa..1412e828a378a48985b90360fdfe8cff6d4003a1 100644 (file)
@@ -240,22 +240,32 @@ type ClientSessionState struct {
        sessionTicket      []uint8               // Encrypted ticket used for session resumption with server
        vers               uint16                // SSL/TLS version negotiated for the session
        cipherSuite        uint16                // Ciphersuite negotiated for the session
-       masterSecret       []byte                // MasterSecret generated by client on a full handshake
+       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
+
+       // 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
 // by a client to resume a TLS session with a given server. ClientSessionCache
 // implementations should expect to be called concurrently from different
-// goroutines. Only ticket-based resumption is supported, not SessionID-based
-// resumption.
+// goroutines. Up to TLS 1.2, only ticket-based resumption is supported, not
+// SessionID-based resumption. In TLS 1.3 they were merged into PSK modes, which
+// are supported via this interface.
 type ClientSessionCache interface {
        // Get searches for a ClientSessionState associated with the given key.
        // On return, ok is true if one was found.
        Get(sessionKey string) (session *ClientSessionState, ok bool)
 
-       // Put adds the ClientSessionState to the cache with the given key.
+       // Put adds the ClientSessionState to the cache with the given key. It might
+       // get called multiple times in a connection if a TLS 1.3 server provides
+       // more than one session ticket. If called with a nil *ClientSessionState,
+       // it should remove the cache entry.
        Put(sessionKey string, cs *ClientSessionState)
 }
 
@@ -502,19 +512,19 @@ type Config struct {
        // the order of elements in CipherSuites, is used.
        PreferServerCipherSuites bool
 
-       // SessionTicketsDisabled may be set to true to disable session ticket
-       // (resumption) support. Note that on clients, session ticket support is
+       // SessionTicketsDisabled may be set to true to disable session ticket and
+       // PSK (resumption) support. Note that on clients, session ticket support is
        // also disabled if ClientSessionCache is nil.
        SessionTicketsDisabled bool
 
-       // SessionTicketKey is used by TLS servers to provide session
-       // resumption. See RFC 5077. If zero, it will be filled with
-       // random data before the first server handshake.
+       // SessionTicketKey is used by TLS servers to provide session resumption.
+       // See RFC 5077 and the PSK mode of RFC 8446. If zero, it will be filled
+       // with random data before the first server handshake.
        //
        // If multiple servers are terminating connections for the same host
        // they should all have the same SessionTicketKey. If the
        // SessionTicketKey leaks, previously recorded and future TLS
-       // connections using that key are compromised.
+       // connections using that key might be compromised.
        SessionTicketKey [32]byte
 
        // ClientSessionCache is a cache of ClientSessionState entries for TLS
@@ -937,15 +947,21 @@ func NewLRUClientSessionCache(capacity int) ClientSessionCache {
        }
 }
 
-// Put adds the provided (sessionKey, cs) pair to the cache.
+// Put adds the provided (sessionKey, cs) pair to the cache. If cs is nil, the entry
+// corresponding to sessionKey is removed from the cache instead.
 func (c *lruSessionCache) Put(sessionKey string, cs *ClientSessionState) {
        c.Lock()
        defer c.Unlock()
 
        if elem, ok := c.m[sessionKey]; ok {
-               entry := elem.Value.(*lruSessionCacheEntry)
-               entry.state = cs
-               c.q.MoveToFront(elem)
+               if cs == nil {
+                       c.q.Remove(elem)
+                       delete(c.m, sessionKey)
+               } else {
+                       entry := elem.Value.(*lruSessionCacheEntry)
+                       entry.state = cs
+                       c.q.MoveToFront(elem)
+               }
                return
        }
 
index 5b0db5304044312352fed2c1fee84f9f8adb6d54..fa366eba2f65a2f28bd248fa82c3de83dcfb330f 100644 (file)
@@ -57,6 +57,9 @@ type Conn struct {
        secureRenegotiation bool
        // ekm is a closure for exporting keying material.
        ekm func(label string, context []byte, length int) ([]byte, error)
+       // resumptionSecret is the resumption_master_secret for generating or
+       // handling NewSessionTicket messages. nil if config.SessionTicketsDisabled.
+       resumptionSecret []byte
 
        // clientFinishedIsFirst is true if the client sent the first Finished
        // message during the most recent handshake. This is recorded because
@@ -1169,10 +1172,15 @@ func (c *Conn) handlePostHandshakeMessage() error {
                return err
        }
 
+       c.retryCount++
+       if c.retryCount > maxUselessRecords {
+               c.sendAlert(alertUnexpectedMessage)
+               return c.in.setErrorLocked(errors.New("tls: too many non-advancing records"))
+       }
+
        switch msg := msg.(type) {
        case *newSessionTicketMsgTLS13:
-               // TODO(filippo): TLS 1.3 session ticket not implemented.
-               return nil
+               return c.handleNewSessionTicket(msg)
        case *keyUpdateMsg:
                return c.handleKeyUpdate(msg)
        default:
@@ -1182,19 +1190,7 @@ func (c *Conn) handlePostHandshakeMessage() error {
 }
 
 func (c *Conn) handleKeyUpdate(keyUpdate *keyUpdateMsg) error {
-       c.retryCount++
-       if c.retryCount > maxUselessRecords {
-               c.sendAlert(alertUnexpectedMessage)
-               return c.in.setErrorLocked(errors.New("tls: too many non-advancing records"))
-       }
-
-       var cipherSuite *cipherSuiteTLS13
-       for _, suite := range cipherSuitesTLS13 {
-               if suite.id == c.cipherSuite {
-                       cipherSuite = suite
-                       break
-               }
-       }
+       cipherSuite := cipherSuiteTLS13ByID(c.cipherSuite)
        if cipherSuite == nil {
                return c.in.setErrorLocked(c.sendAlert(alertInternalError))
        }
index 1d2ba12864986c797b21dd46f39f72954f07d8c2..8a3d3d93bf9aeb56b290e543b9cdd58665d9214b 100644 (file)
@@ -18,6 +18,7 @@ import (
        "strconv"
        "strings"
        "sync/atomic"
+       "time"
 )
 
 type clientHandshakeState struct {
@@ -134,7 +135,7 @@ NextCipherSuite:
        return hello, params, nil
 }
 
-func (c *Conn) clientHandshake() error {
+func (c *Conn) clientHandshake() (err error) {
        if c.config == nil {
                c.config = defaultConfig()
        }
@@ -148,8 +149,20 @@ func (c *Conn) clientHandshake() error {
                return err
        }
 
-       var newSession *ClientSessionState
-       cacheKey, session := c.loadSession(hello)
+       cacheKey, session, earlySecret, binderKey := c.loadSession(hello)
+       if cacheKey != "" && session != nil {
+               defer func() {
+                       // If we got a handshake failure when resuming a session, throw away
+                       // the session ticket. See RFC 5077, Section 3.2.
+                       //
+                       // RFC 8446 makes no mention of dropping tickets on failure, but it
+                       // does require servers to abort on invalid binders, so we need to
+                       // delete tickets to recover from a corrupted PSK.
+                       if err != nil {
+                               c.config.ClientSessionCache.Put(cacheKey, nil)
+                       }
+               }()
+       }
 
        if _, err := c.writeRecord(recordTypeHandshake, hello.marshal()); err != nil {
                return err
@@ -177,81 +190,147 @@ func (c *Conn) clientHandshake() error {
                        hello:       hello,
                        ecdheParams: ecdheParams,
                        session:     session,
+                       earlySecret: earlySecret,
+                       binderKey:   binderKey,
                }
 
-               if err := hs.handshake(); err != nil {
-                       return err
-               }
-
-               newSession = hs.session
-       } else {
-               hs := &clientHandshakeState{
-                       c:           c,
-                       serverHello: serverHello,
-                       hello:       hello,
-                       session:     session,
-               }
+               // In TLS 1.3, session tickets are delivered after the handshake.
+               return hs.handshake()
+       }
 
-               if err := hs.handshake(); err != nil {
-                       return err
-               }
+       hs := &clientHandshakeState{
+               c:           c,
+               serverHello: serverHello,
+               hello:       hello,
+               session:     session,
+       }
 
-               newSession = hs.session
+       if err := hs.handshake(); err != nil {
+               return err
        }
 
        // If we had a successful handshake and hs.session is different from
        // the one already cached - cache a new one.
-       if hello.ticketSupported && newSession != nil && session != newSession {
-               c.config.ClientSessionCache.Put(cacheKey, newSession)
+       if cacheKey != "" && hs.session != nil && session != hs.session {
+               c.config.ClientSessionCache.Put(cacheKey, hs.session)
        }
 
        return nil
 }
 
-func (c *Conn) loadSession(hello *clientHelloMsg) (cacheKey string, session *ClientSessionState) {
+func (c *Conn) loadSession(hello *clientHelloMsg) (cacheKey string,
+       session *ClientSessionState, earlySecret, binderKey []byte) {
        if c.config.SessionTicketsDisabled || c.config.ClientSessionCache == nil {
-               return
+               return "", nil, nil, nil
        }
 
        hello.ticketSupported = true
 
+       if hello.supportedVersions[0] == VersionTLS13 {
+               // Require DHE on resumption as it guarantees forward secrecy against
+               // compromise of the session ticket key. See RFC 8446, Section 4.2.9.
+               hello.pskModes = []uint8{pskModeDHE}
+       }
+
        // Session resumption is not allowed if renegotiating because
        // renegotiation is primarily used to allow a client to send a client
        // certificate, which would be skipped if session resumption occurred.
        if c.handshakes != 0 {
-               return
+               return "", nil, nil, nil
        }
 
        // Try to resume a previously negotiated TLS session, if available.
        cacheKey = clientSessionCacheKey(c.conn.RemoteAddr(), c.config)
-       candidateSession, ok := c.config.ClientSessionCache.Get(cacheKey)
-       if !ok {
-               return
-       }
-
-       // Check that the ciphersuite and version used for the previous session
-       // are still valid.
-       cipherSuiteOk := false
-       for _, id := range hello.cipherSuites {
-               if id == candidateSession.cipherSuite {
-                       cipherSuiteOk = true
-                       break
-               }
+       session, ok := c.config.ClientSessionCache.Get(cacheKey)
+       if !ok || session == nil {
+               return cacheKey, nil, nil, nil
        }
 
+       // Check that version used for the previous session is still valid.
        versOk := false
        for _, v := range hello.supportedVersions {
-               if v == candidateSession.vers {
+               if v == session.vers {
                        versOk = true
                        break
                }
        }
+       if !versOk {
+               return cacheKey, nil, nil, nil
+       }
+
+       // Check that the cached server certificate is not expired, and that it's
+       // valid for the ServerName. This should be ensured by the cache key, but
+       // protect the application from a faulty ClientSessionCache implementation.
+       if !c.config.InsecureSkipVerify {
+               if len(session.verifiedChains) == 0 {
+                       // The original connection had InsecureSkipVerify, while this doesn't.
+                       return cacheKey, nil, nil, nil
+               }
+               serverCert := session.serverCertificates[0]
+               if c.config.time().After(serverCert.NotAfter) {
+                       // Expired certificate, delete the entry.
+                       c.config.ClientSessionCache.Put(cacheKey, nil)
+                       return cacheKey, nil, nil, nil
+               }
+               if err := serverCert.VerifyHostname(c.config.ServerName); err != nil {
+                       return cacheKey, nil, nil, nil
+               }
+       }
+
+       if session.vers != VersionTLS13 {
+               // In TLS 1.2 the cipher suite must match the resumed session. Ensure we
+               // are still offering it.
+               if mutualCipherSuite(hello.cipherSuites, session.cipherSuite) == nil {
+                       return cacheKey, nil, nil, nil
+               }
 
-       if versOk && cipherSuiteOk {
-               session = candidateSession
                hello.sessionTicket = session.sessionTicket
+               return
        }
 
+       // Check that the session ticket is not expired.
+       if c.config.time().After(session.useBy) {
+               c.config.ClientSessionCache.Put(cacheKey, nil)
+               return cacheKey, nil, nil, nil
+       }
+
+       // In TLS 1.3 the KDF hash must match the resumed session. Ensure we
+       // offer at least one cipher suite with that hash.
+       cipherSuite := cipherSuiteTLS13ByID(session.cipherSuite)
+       if cipherSuite == nil {
+               return cacheKey, nil, nil, nil
+       }
+       cipherSuiteOk := false
+       for _, offeredID := range hello.cipherSuites {
+               offeredSuite := cipherSuiteTLS13ByID(offeredID)
+               if offeredSuite != nil && offeredSuite.hash == cipherSuite.hash {
+                       cipherSuiteOk = true
+                       break
+               }
+       }
+       if !cipherSuiteOk {
+               return cacheKey, nil, nil, nil
+       }
+
+       // Set the pre_shared_key extension. See RFC 8446, Section 4.2.11.1.
+       ticketAge := uint32(c.config.time().Sub(session.receivedAt) / time.Millisecond)
+       identity := pskIdentity{
+               label:               session.sessionTicket,
+               obfuscatedTicketAge: ticketAge + session.ageAdd,
+       }
+       hello.pskIdentities = []pskIdentity{identity}
+       hello.pskBinders = [][]byte{make([]byte, cipherSuite.hash.Size())}
+
+       // Compute the PSK binders. See RFC 8446, Section 4.2.11.2.
+       psk := cipherSuite.expandLabel(session.masterSecret, "resumption",
+               session.nonce, cipherSuite.hash.Size())
+       earlySecret = cipherSuite.extract(psk, nil)
+       binderKey = cipherSuite.deriveSecret(earlySecret, resumptionBinderLabel, nil)
+       transcript := cipherSuite.hash.New()
+       transcript.Write(hello.marshalWithoutBinders())
+       pskBinders := [][]byte{cipherSuite.finishedHash(binderKey, transcript)}
+       hello.updateBinders(pskBinders)
+
        return
 }
 
@@ -275,8 +354,8 @@ func (c *Conn) pickTLSVersion(serverHello *serverHelloMsg) error {
        return nil
 }
 
-// Does the handshake, either a full one or resumes old session.
-// Requires hs.c, hs.hello, and, optionally, hs.session to be set.
+// Does the handshake, either a full one or resumes old session. Requires hs.c,
+// hs.hello, hs.serverHello, and, optionally, hs.session to be set.
 func (hs *clientHandshakeState) handshake() error {
        c := hs.c
 
@@ -692,6 +771,7 @@ func (hs *clientHandshakeState) readSessionTicket() error {
                masterSecret:       hs.masterSecret,
                serverCertificates: c.peerCertificates,
                verifiedChains:     c.verifiedChains,
+               receivedAt:         c.config.time(),
        }
 
        return nil
index c60fb62bc3bb07f6810d0232a1afdf47ba24f950..103dfa44595c401da8c05b18e67b62d3a80448fc 100644 (file)
@@ -529,7 +529,7 @@ func runClientTestForVersion(t *testing.T, template *clientTest, version, option
 
                test.name = version + "-" + test.name
                if len(test.command) == 0 {
-                       test.command = defaultClientCommand
+                       test.command = defaultServerCommand
                }
                test.command = append([]string(nil), test.command...)
                test.command = append(test.command, option)
@@ -746,10 +746,9 @@ func TestHandshakeClientCHACHA20SHA256(t *testing.T) {
 
 func TestHandshakeClientECDSATLS13(t *testing.T) {
        test := &clientTest{
-               name:    "ECDSA",
-               command: []string{"openssl", "s_server"},
-               cert:    testECDSACertificate,
-               key:     testECDSAPrivateKey,
+               name: "ECDSA",
+               cert: testECDSACertificate,
+               key:  testECDSAPrivateKey,
        }
        runClientTestTLS13(t, test)
 }
@@ -871,6 +870,9 @@ func TestClientKeyUpdate(t *testing.T) {
 }
 
 func TestClientResumption(t *testing.T) {
+       // TODO(filippo): update to test both TLS 1.3 and 1.2 once PSK are
+       // supported server-side.
+
        serverConfig := &Config{
                CipherSuites: []uint16{TLS_RSA_WITH_RC4_128_SHA, TLS_ECDHE_RSA_WITH_RC4_128_SHA},
                Certificates: testConfig.Certificates,
@@ -907,6 +909,10 @@ func TestClientResumption(t *testing.T) {
        getTicket := func() []byte {
                return clientConfig.ClientSessionCache.(*lruSessionCache).q.Front().Value.(*lruSessionCacheEntry).state.sessionTicket
        }
+       deleteTicket := func() {
+               ticketKey := clientConfig.ClientSessionCache.(*lruSessionCache).q.Front().Value.(*lruSessionCacheEntry).sessionKey
+               clientConfig.ClientSessionCache.Put(ticketKey, nil)
+       }
        randomKey := func() [32]byte {
                var k [32]byte
                if _, err := io.ReadFull(serverConfig.rand(), k[:]); err != nil {
@@ -951,6 +957,28 @@ func TestClientResumption(t *testing.T) {
        testResumeState("DifferentCipherSuite", false)
        testResumeState("DifferentCipherSuiteRecovers", true)
 
+       deleteTicket()
+       testResumeState("WithoutSessionTicket", false)
+
+       // Session resumption should work when using client certificates
+       deleteTicket()
+       serverConfig.ClientCAs = rootCAs
+       serverConfig.ClientAuth = RequireAndVerifyClientCert
+       clientConfig.Certificates = serverConfig.Certificates
+       testResumeState("InitialHandshake", false)
+       testResumeState("WithClientCertificates", true)
+
+       // Tickets should be removed from the session cache on TLS handshake failure
+       farFuture := func() time.Time { return time.Unix(16725225600, 0) }
+       serverConfig.Time = farFuture
+       _, _, err = testHandshake(t, clientConfig, serverConfig)
+       if err == nil {
+               t.Fatalf("handshake did not fail after client certificate expiry")
+       }
+       serverConfig.Time = nil
+       testResumeState("AfterHandshakeFailure", false)
+       serverConfig.ClientAuth = NoClientCert
+
        clientConfig.ClientSessionCache = nil
        testResumeState("WithoutSessionCache", false)
 }
@@ -994,10 +1022,21 @@ func TestLRUClientSessionCache(t *testing.T) {
                t.Fatalf("session cache failed update for key 0")
        }
 
-       // Adding a nil entry is valid.
+       // Calling Put with a nil entry deletes the key.
        cache.Put(keys[0], nil)
-       if s, ok := cache.Get(keys[0]); !ok || s != nil {
-               t.Fatalf("failed to add nil entry to cache")
+       if _, ok := cache.Get(keys[0]); ok {
+               t.Fatalf("session cache failed to delete key 0")
+       }
+
+       // Delete entry 2. LRU should keep 4 and 5
+       cache.Put(keys[2], nil)
+       if _, ok := cache.Get(keys[2]); ok {
+               t.Fatalf("session cache failed to delete key 4")
+       }
+       for i := 4; i < 6; i++ {
+               if s, ok := cache.Get(keys[i]); !ok || s != &cs[i] {
+                       t.Fatalf("session cache should not have deleted key: %s", keys[i])
+               }
        }
 }
 
@@ -1128,11 +1167,10 @@ func TestHandshakClientSCTs(t *testing.T) {
                t.Fatal(err)
        }
 
+       // Note that this needs OpenSSL 1.0.2 because that is the first
+       // version that supports the -serverinfo flag.
        test := &clientTest{
-               name: "SCT",
-               // Note that this needs OpenSSL 1.0.2 because that is the first
-               // version that supports the -serverinfo flag.
-               command:    []string{"openssl", "s_server"},
+               name:       "SCT",
                config:     config,
                extensions: [][]byte{scts},
                validate: func(state ConnectionState) error {
@@ -1237,9 +1275,8 @@ func TestRenegotiateTwiceRejected(t *testing.T) {
 
 func TestHandshakeClientExportKeyingMaterial(t *testing.T) {
        test := &clientTest{
-               name:    "ExportKeyingMaterial",
-               command: []string{"openssl", "s_server"},
-               config:  testConfig.Clone(),
+               name:   "ExportKeyingMaterial",
+               config: testConfig.Clone(),
                validate: func(state ConnectionState) error {
                        if km, err := state.ExportKeyingMaterial("test", nil, 42); err != nil {
                                return fmt.Errorf("ExportKeyingMaterial failed: %v", err)
index 83287dd9267ba7e97e9d65076ff7fc266de551c7..233b163882232231bc9f2e1fcf8b1660387cc21c 100644 (file)
@@ -11,22 +11,30 @@ import (
        "errors"
        "hash"
        "sync/atomic"
+       "time"
 )
 
 type clientHandshakeStateTLS13 struct {
-       c             *Conn
-       serverHello   *serverHelloMsg
-       hello         *clientHelloMsg
+       c           *Conn
+       serverHello *serverHelloMsg
+       hello       *clientHelloMsg
+       ecdheParams ecdheParameters
+
+       session     *ClientSessionState
+       earlySecret []byte
+       binderKey   []byte
+
        certReq       *certificateRequestMsgTLS13
+       usingPSK      bool
        sentDummyCCS  bool
-       ecdheParams   ecdheParameters
        suite         *cipherSuiteTLS13
        transcript    hash.Hash
        masterSecret  []byte
        trafficSecret []byte // client_application_traffic_secret_0
-       session       *ClientSessionState
 }
 
+// handshake requires hs.c, hs.hello, hs.serverHello, hs.ecdheParams, and,
+// optionally, hs.session, hs.earlySecret and hs.binderKey to be set.
 func (hs *clientHandshakeStateTLS13) handshake() error {
        c := hs.c
 
@@ -50,22 +58,12 @@ func (hs *clientHandshakeStateTLS13) handshake() error {
        hs.transcript.Write(hs.hello.marshal())
 
        if bytes.Equal(hs.serverHello.random, helloRetryRequestRandom) {
-               // The first ClientHello gets double-hashed into the transcript upon a
-               // HelloRetryRequest. See RFC 8446, Section 4.4.1.
-               chHash := hs.transcript.Sum(nil)
-               hs.transcript.Reset()
-               hs.transcript.Write([]byte{typeMessageHash, 0, 0, uint8(len(chHash))})
-               hs.transcript.Write(chHash)
-               hs.transcript.Write(hs.serverHello.marshal())
-
                if err := hs.sendDummyChangeCipherSpec(); err != nil {
                        return err
                }
                if err := hs.processHelloRetryRequest(); err != nil {
                        return err
                }
-
-               hs.transcript.Write(hs.hello.marshal())
        }
 
        hs.transcript.Write(hs.serverHello.marshal())
@@ -83,7 +81,7 @@ func (hs *clientHandshakeStateTLS13) handshake() error {
        if err := hs.readServerParameters(); err != nil {
                return err
        }
-       if err := hs.doFullHandshake(); err != nil {
+       if err := hs.readServerCertificate(); err != nil {
                return err
        }
        if err := hs.readServerFinished(); err != nil {
@@ -178,6 +176,14 @@ func (hs *clientHandshakeStateTLS13) sendDummyChangeCipherSpec() error {
 func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error {
        c := hs.c
 
+       // The first ClientHello gets double-hashed into the transcript upon a
+       // HelloRetryRequest. See RFC 8446, Section 4.4.1.
+       chHash := hs.transcript.Sum(nil)
+       hs.transcript.Reset()
+       hs.transcript.Write([]byte{typeMessageHash, 0, 0, uint8(len(chHash))})
+       hs.transcript.Write(chHash)
+       hs.transcript.Write(hs.serverHello.marshal())
+
        if hs.serverHello.serverShare.group != 0 {
                c.sendAlert(alertDecodeError)
                return errors.New("tls: received malformed key_share extension")
@@ -218,6 +224,31 @@ func (hs *clientHandshakeStateTLS13) processHelloRetryRequest() error {
        hs.hello.cookie = hs.serverHello.cookie
 
        hs.hello.raw = nil
+       if len(hs.hello.pskIdentities) > 0 {
+               pskSuite := cipherSuiteTLS13ByID(hs.session.cipherSuite)
+               if pskSuite == nil {
+                       return c.sendAlert(alertInternalError)
+               }
+               if pskSuite.hash == hs.suite.hash {
+                       // Update binders and obfuscated_ticket_age.
+                       ticketAge := uint32(c.config.time().Sub(hs.session.receivedAt) / time.Millisecond)
+                       hs.hello.pskIdentities[0].obfuscatedTicketAge = ticketAge + hs.session.ageAdd
+
+                       transcript := hs.suite.hash.New()
+                       transcript.Write([]byte{typeMessageHash, 0, 0, uint8(len(chHash))})
+                       transcript.Write(chHash)
+                       transcript.Write(hs.serverHello.marshal())
+                       transcript.Write(hs.hello.marshalWithoutBinders())
+                       pskBinders := [][]byte{hs.suite.finishedHash(hs.binderKey, transcript)}
+                       hs.hello.updateBinders(pskBinders)
+               } else {
+                       // Server selected a cipher suite incompatible with the PSK.
+                       hs.hello.pskIdentities = nil
+                       hs.hello.pskBinders = nil
+               }
+       }
+
+       hs.transcript.Write(hs.hello.marshal())
        if _, err := c.writeRecord(recordTypeHandshake, hs.hello.marshal()); err != nil {
                return err
        }
@@ -259,11 +290,40 @@ func (hs *clientHandshakeStateTLS13) processServerHello() error {
                return errors.New("tls: malformed key_share extension")
        }
 
+       if hs.serverHello.serverShare.group == 0 {
+               c.sendAlert(alertIllegalParameter)
+               return errors.New("tls: server did not send a key share")
+       }
        if hs.serverHello.serverShare.group != hs.ecdheParams.CurveID() {
                c.sendAlert(alertIllegalParameter)
                return errors.New("tls: server selected unsupported group")
        }
 
+       if !hs.serverHello.selectedIdentityPresent {
+               return nil
+       }
+
+       if int(hs.serverHello.selectedIdentity) >= len(hs.hello.pskIdentities) {
+               c.sendAlert(alertIllegalParameter)
+               return errors.New("tls: server selected an invalid PSK")
+       }
+
+       if len(hs.hello.pskIdentities) != 1 || hs.session == nil {
+               return c.sendAlert(alertInternalError)
+       }
+       pskSuite := cipherSuiteTLS13ByID(hs.session.cipherSuite)
+       if pskSuite == nil {
+               return c.sendAlert(alertInternalError)
+       }
+       if pskSuite.hash != hs.suite.hash {
+               c.sendAlert(alertIllegalParameter)
+               return errors.New("tls: server selected an invalid PSK and cipher suite pair")
+       }
+
+       hs.usingPSK = true
+       c.didResume = true
+       c.peerCertificates = hs.session.serverCertificates
+       c.verifiedChains = hs.session.verifiedChains
        return nil
 }
 
@@ -276,7 +336,10 @@ func (hs *clientHandshakeStateTLS13) establishHandshakeKeys() error {
                return errors.New("tls: invalid server key share")
        }
 
-       earlySecret := hs.suite.extract(nil, nil)
+       earlySecret := hs.earlySecret
+       if !hs.usingPSK {
+               earlySecret = hs.suite.extract(nil, nil)
+       }
        handshakeSecret := hs.suite.extract(sharedKey,
                hs.suite.deriveSecret(earlySecret, "derived", nil))
 
@@ -328,9 +391,15 @@ func (hs *clientHandshakeStateTLS13) readServerParameters() error {
        return nil
 }
 
-func (hs *clientHandshakeStateTLS13) doFullHandshake() error {
+func (hs *clientHandshakeStateTLS13) readServerCertificate() error {
        c := hs.c
 
+       // Either a PSK or a certificate is always used, but not both.
+       // See RFC 8446, Section 4.1.1.
+       if hs.usingPSK {
+               return nil
+       }
+
        msg, err := c.readHandshake()
        if err != nil {
                return err
@@ -419,13 +488,10 @@ func (hs *clientHandshakeStateTLS13) readServerFinished() error {
                return unexpectedMessageError(finished, msg)
        }
 
-       // See RFC 8446, sections 4.4.4 and 4.4.
-       finishedKey := hs.suite.expandLabel(c.in.trafficSecret, "finished", nil, hs.suite.hash.Size())
-       expectedMAC := hmac.New(hs.suite.hash.New, finishedKey)
-       expectedMAC.Write(hs.transcript.Sum(nil))
-       if !hmac.Equal(expectedMAC.Sum(nil), finished.verifyData) {
+       expectedMAC := hs.suite.finishedHash(c.in.trafficSecret, hs.transcript)
+       if !hmac.Equal(expectedMAC, finished.verifyData) {
                c.sendAlert(alertDecryptError)
-               return errors.New("tls: invalid finished hash")
+               return errors.New("tls: invalid server finished hash")
        }
 
        hs.transcript.Write(finished.marshal())
@@ -465,11 +531,8 @@ func (hs *clientHandshakeStateTLS13) sendClientCertificate() error {
 func (hs *clientHandshakeStateTLS13) sendClientFinished() error {
        c := hs.c
 
-       finishedKey := hs.suite.expandLabel(c.out.trafficSecret, "finished", nil, hs.suite.hash.Size())
-       verifyData := hmac.New(hs.suite.hash.New, finishedKey)
-       verifyData.Write(hs.transcript.Sum(nil))
        finished := &finishedMsg{
-               verifyData: verifyData.Sum(nil),
+               verifyData: hs.suite.finishedHash(c.out.trafficSecret, hs.transcript),
        }
 
        hs.transcript.Write(finished.marshal())
@@ -479,5 +542,58 @@ func (hs *clientHandshakeStateTLS13) sendClientFinished() error {
 
        c.out.setTrafficSecret(hs.suite, hs.trafficSecret)
 
+       if !c.config.SessionTicketsDisabled && c.config.ClientSessionCache != nil {
+               c.resumptionSecret = hs.suite.deriveSecret(hs.masterSecret,
+                       resumptionLabel, hs.transcript)
+       }
+
+       return nil
+}
+
+func (c *Conn) handleNewSessionTicket(msg *newSessionTicketMsgTLS13) error {
+       if !c.isClient {
+               c.sendAlert(alertUnexpectedMessage)
+               return errors.New("tls: received new session ticket from a client")
+       }
+
+       if c.config.SessionTicketsDisabled || c.config.ClientSessionCache == nil {
+               return nil
+       }
+
+       // See RFC 8446, Section 4.6.1.
+       if msg.lifetime == 0 {
+               return nil
+       }
+       lifetime := time.Duration(msg.lifetime) * time.Second
+       if lifetime > 7*24*time.Hour {
+               c.sendAlert(alertIllegalParameter)
+               return errors.New("tls: received a session ticket with invalid lifetime")
+       }
+
+       cipherSuite := cipherSuiteTLS13ByID(c.cipherSuite)
+       if cipherSuite == nil || c.resumptionSecret == nil {
+               return c.sendAlert(alertInternalError)
+       }
+
+       // Save the resumption_master_secret and nonce instead of deriving the PSK
+       // to do the least amount of work on NewSessionTicket messages before we
+       // 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,
+       }
+
+       cacheKey := clientSessionCacheKey(c.conn.RemoteAddr(), c.config)
+       c.config.ClientSessionCache.Put(cacheKey, session)
+
        return nil
 }
index 98b7bd511fc0bfc8a7917d07926c2f646c60784e..c1f86f638ce17b50871a52839411c89ad528d3b9 100644 (file)
@@ -288,6 +288,50 @@ func (m *clientHelloMsg) marshal() []byte {
        return m.raw
 }
 
+// marshalWithoutBinders returns the ClientHello through the
+// PreSharedKeyExtension.identities field, according to RFC 8446, Section
+// 4.2.11.2. Note that m.pskBinders must be set to slices of the correct length.
+func (m *clientHelloMsg) marshalWithoutBinders() []byte {
+       bindersLen := 2 // uint16 length prefix
+       for _, binder := range m.pskBinders {
+               bindersLen += 1 // uint8 length prefix
+               bindersLen += len(binder)
+       }
+
+       fullMessage := m.marshal()
+       return fullMessage[:len(fullMessage)-bindersLen]
+}
+
+// updateBinders updates the m.pskBinders field, if necessary updating the
+// cached marshalled representation. The supplied binders must have the same
+// length as the current m.pskBinders.
+func (m *clientHelloMsg) updateBinders(pskBinders [][]byte) {
+       if len(pskBinders) != len(m.pskBinders) {
+               panic("tls: internal error: pskBinders length mismatch")
+       }
+       for i := range m.pskBinders {
+               if len(pskBinders[i]) != len(m.pskBinders[i]) {
+                       panic("tls: internal error: pskBinders length mismatch")
+               }
+       }
+       m.pskBinders = pskBinders
+       if m.raw != nil {
+               lenWithoutBinders := len(m.marshalWithoutBinders())
+               // TODO(filippo): replace with NewFixedBuilder once CL 148882 is imported.
+               b := cryptobyte.NewBuilder(m.raw[:lenWithoutBinders])
+               b.AddUint16LengthPrefixed(func(b *cryptobyte.Builder) {
+                       for _, binder := range m.pskBinders {
+                               b.AddUint8LengthPrefixed(func(b *cryptobyte.Builder) {
+                                       b.AddBytes(binder)
+                               })
+                       }
+               })
+               if len(b.BytesOrPanic()) != len(m.raw) {
+                       panic("tls: internal error: failed to update binders")
+               }
+       }
+}
+
 func (m *clientHelloMsg) unmarshal(data []byte) bool {
        *m = clientHelloMsg{raw: data}
        s := cryptobyte.String(data)
index 3b4f8b7edc90c89cff7da29003a535b7067dd58b..f24ebf1c8ece494147b8b7a96d79438efc2c492f 100644 (file)
@@ -756,14 +756,7 @@ func (hs *serverHandshakeState) processCertsFromClient(certificates [][]byte) (c
 func (hs *serverHandshakeState) setCipherSuite(id uint16, supportedCipherSuites []uint16, version uint16) bool {
        for _, supported := range supportedCipherSuites {
                if id == supported {
-                       var candidate *cipherSuite
-
-                       for _, s := range cipherSuites {
-                               if s.id == id {
-                                       candidate = s
-                                       break
-                               }
-                       }
+                       candidate := cipherSuiteByID(id)
                        if candidate == nil {
                                continue
                        }
index 2da0760acf36a55b81a879d6e1e7b17fd5cc6974..4daf62b0707771126f28b163704064e83ca45519 100644 (file)
@@ -434,12 +434,8 @@ func (hs *serverHandshakeStateTLS13) sendServerCertificate() error {
 func (hs *serverHandshakeStateTLS13) sendServerFinished() error {
        c := hs.c
 
-       // See RFC 8446, sections 4.4.4 and 4.4.
-       finishedKey := hs.suite.expandLabel(c.out.trafficSecret, "finished", nil, hs.suite.hash.Size())
-       verifyData := hmac.New(hs.suite.hash.New, finishedKey)
-       verifyData.Write(hs.transcript.Sum(nil))
        finished := &finishedMsg{
-               verifyData: verifyData.Sum(nil),
+               verifyData: hs.suite.finishedHash(c.out.trafficSecret, hs.transcript),
        }
 
        hs.transcript.Write(finished.marshal())
@@ -488,10 +484,8 @@ func (hs *serverHandshakeStateTLS13) readClientFinished() error {
                return unexpectedMessageError(finished, msg)
        }
 
-       finishedKey := hs.suite.expandLabel(c.in.trafficSecret, "finished", nil, hs.suite.hash.Size())
-       expectedMAC := hmac.New(hs.suite.hash.New, finishedKey)
-       expectedMAC.Write(hs.transcript.Sum(nil))
-       if !hmac.Equal(expectedMAC.Sum(nil), finished.verifyData) {
+       expectedMAC := hs.suite.finishedHash(c.in.trafficSecret, hs.transcript)
+       if !hmac.Equal(expectedMAC, finished.verifyData) {
                c.sendAlert(alertDecryptError)
                return errors.New("tls: invalid client finished hash")
        }
index 0a88c96acabb55b0fe8336af5dd79dbdd8c48894..310d92e2c5e3a51d46fd5edd18a49e344f6be6b2 100644 (file)
@@ -6,6 +6,7 @@ package tls
 
 import (
        "crypto/elliptic"
+       "crypto/hmac"
        "errors"
        "golang_org/x/crypto/cryptobyte"
        "golang_org/x/crypto/curve25519"
@@ -77,6 +78,16 @@ func (c *cipherSuiteTLS13) trafficKey(trafficSecret []byte) (key, iv []byte) {
        return
 }
 
+// finishedHash generates the Finished verify_data or PskBinderEntry according
+// to RFC 8446, Section 4.4.4. See sections 4.4 and 4.2.11.2 for the baseKey
+// selection.
+func (c *cipherSuiteTLS13) finishedHash(baseKey []byte, transcript hash.Hash) []byte {
+       finishedKey := c.expandLabel(baseKey, "finished", nil, c.hash.Size())
+       verifyData := hmac.New(c.hash.New, finishedKey)
+       verifyData.Write(transcript.Sum(nil))
+       return verifyData.Sum(nil)
+}
+
 // exportKeyingMaterial implements RFC5705 exporters for TLS 1.3 according to
 // RFC 8446, Section 7.5.
 func (c *cipherSuiteTLS13) exportKeyingMaterial(masterSecret []byte, transcript hash.Hash) func(string, []byte, int) ([]byte, error) {
index f201253f72ec1a8dabcd30e793499305477b51f4..ec54aac12e40f6ad7d9d21bddd7744080b2395f4 100644 (file)
@@ -87,20 +87,11 @@ func TestKeysFromPreMasterSecret(t *testing.T) {
        }
 }
 
-func cipherSuiteById(id uint16) *cipherSuite {
-       for _, cipherSuite := range cipherSuites {
-               if cipherSuite.id == id {
-                       return cipherSuite
-               }
-       }
-       panic("ciphersuite not found")
-}
-
 // These test vectors were generated from GnuTLS using `gnutls-cli --insecure -d 9 `
 var testKeysFromTests = []testKeysFromTest{
        {
                VersionTLS10,
-               cipherSuiteById(TLS_RSA_WITH_RC4_128_SHA),
+               cipherSuiteByID(TLS_RSA_WITH_RC4_128_SHA),
                "0302cac83ad4b1db3b9ab49ad05957de2a504a634a386fc600889321e1a971f57479466830ac3e6f468e87f5385fa0c5",
                "4ae66303755184a3917fcb44880605fcc53baa01912b22ed94473fc69cebd558",
                "4ae663020ec16e6bb5130be918cfcafd4d765979a3136a5d50c593446e4e44db",
@@ -116,7 +107,7 @@ var testKeysFromTests = []testKeysFromTest{
        },
        {
                VersionTLS10,
-               cipherSuiteById(TLS_RSA_WITH_RC4_128_SHA),
+               cipherSuiteByID(TLS_RSA_WITH_RC4_128_SHA),
                "03023f7527316bc12cbcd69e4b9e8275d62c028f27e65c745cfcddc7ce01bd3570a111378b63848127f1c36e5f9e4890",
                "4ae66364b5ea56b20ce4e25555aed2d7e67f42788dd03f3fee4adae0459ab106",
                "4ae66363ab815cbf6a248b87d6b556184e945e9b97fbdf247858b0bdafacfa1c",
@@ -132,7 +123,7 @@ var testKeysFromTests = []testKeysFromTest{
        },
        {
                VersionTLS10,
-               cipherSuiteById(TLS_RSA_WITH_RC4_128_SHA),
+               cipherSuiteByID(TLS_RSA_WITH_RC4_128_SHA),
                "832d515f1d61eebb2be56ba0ef79879efb9b527504abb386fb4310ed5d0e3b1f220d3bb6b455033a2773e6d8bdf951d278a187482b400d45deb88a5d5a6bb7d6a7a1decc04eb9ef0642876cd4a82d374d3b6ff35f0351dc5d411104de431375355addc39bfb1f6329fb163b0bc298d658338930d07d313cd980a7e3d9196cac1",
                "4ae663b2ee389c0de147c509d8f18f5052afc4aaf9699efe8cb05ece883d3a5e",
                "4ae664d503fd4cff50cfc1fb8fc606580f87b0fcdac9554ba0e01d785bdf278e",
@@ -148,7 +139,7 @@ var testKeysFromTests = []testKeysFromTest{
        },
        {
                VersionSSL30,
-               cipherSuiteById(TLS_RSA_WITH_RC4_128_SHA),
+               cipherSuiteByID(TLS_RSA_WITH_RC4_128_SHA),
                "832d515f1d61eebb2be56ba0ef79879efb9b527504abb386fb4310ed5d0e3b1f220d3bb6b455033a2773e6d8bdf951d278a187482b400d45deb88a5d5a6bb7d6a7a1decc04eb9ef0642876cd4a82d374d3b6ff35f0351dc5d411104de431375355addc39bfb1f6329fb163b0bc298d658338930d07d313cd980a7e3d9196cac1",
                "4ae663b2ee389c0de147c509d8f18f5052afc4aaf9699efe8cb05ece883d3a5e",
                "4ae664d503fd4cff50cfc1fb8fc606580f87b0fcdac9554ba0e01d785bdf278e",