Commit d669cc47 authored by Filippo Valsorda's avatar Filippo Valsorda

crypto/tls: implement TLS 1.3 PSK authentication (client side)

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/147420Reviewed-by: default avatarAdam Langley <agl@golang.org>
parent dc0be727
......@@ -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 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 cipherSuiteTLS13ByID(id)
}
}
return nil
}
func cipherSuiteTLS13ByID(id uint16) *cipherSuiteTLS13 {
for _, cipherSuite := range cipherSuitesTLS13 {
if cipherSuite.id == id {
return cipherSuite
}
}
return nil
......
......@@ -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 {
if cs == nil {
c.q.Remove(elem)
delete(c.m, sessionKey)
} else {
entry := elem.Value.(*lruSessionCacheEntry)
entry.state = cs
c.q.MoveToFront(elem)
}
return
}
......
......@@ -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))
}
......
......@@ -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,14 +190,14 @@ func (c *Conn) clientHandshake() error {
hello: hello,
ecdheParams: ecdheParams,
session: session,
earlySecret: earlySecret,
binderKey: binderKey,
}
if err := hs.handshake(); err != nil {
return err
// In TLS 1.3, session tickets are delivered after the handshake.
return hs.handshake()
}
newSession = hs.session
} else {
hs := &clientHandshakeState{
c: c,
serverHello: serverHello,
......@@ -196,62 +209,128 @@ func (c *Conn) clientHandshake() error {
return err
}
newSession = hs.session
}
// 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
......
......@@ -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)
......@@ -747,7 +747,6 @@ func TestHandshakeClientCHACHA20SHA256(t *testing.T) {
func TestHandshakeClientECDSATLS13(t *testing.T) {
test := &clientTest{
name: "ECDSA",
command: []string{"openssl", "s_server"},
cert: testECDSACertificate,
key: testECDSAPrivateKey,
}
......@@ -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)
}
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"},
test := &clientTest{
name: "SCT",
config: config,
extensions: [][]byte{scts},
validate: func(state ConnectionState) error {
......@@ -1238,7 +1276,6 @@ func TestRenegotiateTwiceRejected(t *testing.T) {
func TestHandshakeClientExportKeyingMaterial(t *testing.T) {
test := &clientTest{
name: "ExportKeyingMaterial",
command: []string{"openssl", "s_server"},
config: testConfig.Clone(),
validate: func(state ConnectionState) error {
if km, err := state.ExportKeyingMaterial("test", nil, 42); err != nil {
......
......@@ -11,22 +11,30 @@ import (
"errors"
"hash"
"sync/atomic"
"time"
)
type clientHandshakeStateTLS13 struct {
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
}
......@@ -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)
......
......@@ -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
}
......
......@@ -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")
}
......
......@@ -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) {
......
......@@ -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",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment