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
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
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)
}
// 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
}
}
-// 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
}
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
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:
}
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))
}
"strconv"
"strings"
"sync/atomic"
+ "time"
)
type clientHandshakeState struct {
return hello, params, nil
}
-func (c *Conn) clientHandshake() error {
+func (c *Conn) clientHandshake() (err error) {
if c.config == nil {
c.config = defaultConfig()
}
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
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
}
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
masterSecret: hs.masterSecret,
serverCertificates: c.peerCertificates,
verifiedChains: c.verifiedChains,
+ receivedAt: c.config.time(),
}
return nil
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)
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)
}
}
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,
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 {
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)
}
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])
+ }
}
}
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 {
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)
"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
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())
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 {
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")
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
}
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
}
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))
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
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())
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())
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
}
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)
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
}
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())
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")
}
import (
"crypto/elliptic"
+ "crypto/hmac"
"errors"
"golang_org/x/crypto/cryptobyte"
"golang_org/x/crypto/curve25519"
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) {
}
}
-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",
},
{
VersionTLS10,
- cipherSuiteById(TLS_RSA_WITH_RC4_128_SHA),
+ cipherSuiteByID(TLS_RSA_WITH_RC4_128_SHA),
"03023f7527316bc12cbcd69e4b9e8275d62c028f27e65c745cfcddc7ce01bd3570a111378b63848127f1c36e5f9e4890",
"4ae66364b5ea56b20ce4e25555aed2d7e67f42788dd03f3fee4adae0459ab106",
"4ae66363ab815cbf6a248b87d6b556184e945e9b97fbdf247858b0bdafacfa1c",
},
{
VersionTLS10,
- cipherSuiteById(TLS_RSA_WITH_RC4_128_SHA),
+ cipherSuiteByID(TLS_RSA_WITH_RC4_128_SHA),
"832d515f1d61eebb2be56ba0ef79879efb9b527504abb386fb4310ed5d0e3b1f220d3bb6b455033a2773e6d8bdf951d278a187482b400d45deb88a5d5a6bb7d6a7a1decc04eb9ef0642876cd4a82d374d3b6ff35f0351dc5d411104de431375355addc39bfb1f6329fb163b0bc298d658338930d07d313cd980a7e3d9196cac1",
"4ae663b2ee389c0de147c509d8f18f5052afc4aaf9699efe8cb05ece883d3a5e",
"4ae664d503fd4cff50cfc1fb8fc606580f87b0fcdac9554ba0e01d785bdf278e",
},
{
VersionSSL30,
- cipherSuiteById(TLS_RSA_WITH_RC4_128_SHA),
+ cipherSuiteByID(TLS_RSA_WITH_RC4_128_SHA),
"832d515f1d61eebb2be56ba0ef79879efb9b527504abb386fb4310ed5d0e3b1f220d3bb6b455033a2773e6d8bdf951d278a187482b400d45deb88a5d5a6bb7d6a7a1decc04eb9ef0642876cd4a82d374d3b6ff35f0351dc5d411104de431375355addc39bfb1f6329fb163b0bc298d658338930d07d313cd980a7e3d9196cac1",
"4ae663b2ee389c0de147c509d8f18f5052afc4aaf9699efe8cb05ece883d3a5e",
"4ae664d503fd4cff50cfc1fb8fc606580f87b0fcdac9554ba0e01d785bdf278e",