Change-Id: I998f69269cdf813da19ccccc208b476f3501c8c4 Reviewed-on: https://boringssl-review.googlesource.com/8991 Reviewed-by: Steven Valdez <svaldez@google.com> Reviewed-by: David Benjamin <davidben@google.com> Commit-Queue: David Benjamin <davidben@google.com> CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>kris/onging/CECPQ3_patch15
@@ -41,6 +41,7 @@ const ( | |||
alertNoRenegotiation alert = 100 | |||
alertMissingExtension alert = 109 | |||
alertUnsupportedExtension alert = 110 | |||
alertUnknownPSKIdentity alert = 115 | |||
) | |||
var alertText = map[alert]string{ | |||
@@ -69,6 +70,7 @@ var alertText = map[alert]string{ | |||
alertNoRenegotiation: "no renegotiation", | |||
alertMissingExtension: "missing extension", | |||
alertUnsupportedExtension: "unsupported extension", | |||
alertUnknownPSKIdentity: "unknown PSK identity", | |||
} | |||
func (e alert) String() string { | |||
@@ -101,6 +101,27 @@ func (cs cipherSuite) hash() crypto.Hash { | |||
return crypto.SHA256 | |||
} | |||
// TODO(nharper): Remove this function when TLS 1.3 cipher suites get | |||
// refactored to break out the AEAD/PRF from everything else. Once that's | |||
// done, this won't be necessary anymore. | |||
func ecdhePSKSuite(id uint16) uint16 { | |||
switch id { | |||
case TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, | |||
TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, | |||
TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256: | |||
return TLS_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256 | |||
case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, | |||
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, | |||
TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256: | |||
return TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256 | |||
case TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, | |||
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, | |||
TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384: | |||
return TLS_ECDHE_PSK_WITH_AES_256_GCM_SHA384 | |||
} | |||
return 0 | |||
} | |||
var cipherSuites = []*cipherSuite{ | |||
// Ciphersuite order is chosen so that ECDHE comes before plain RSA | |||
// and RC4 comes before AES (because of the Lucky13 attack). | |||
@@ -28,7 +28,7 @@ const ( | |||
// The draft version of TLS 1.3 that is implemented here and sent in the draft | |||
// indicator extension. | |||
const tls13DraftVersion = 13 | |||
const tls13DraftVersion = 14 | |||
const ( | |||
maxPlaintext = 16384 // maximum plaintext payload length | |||
@@ -242,6 +242,10 @@ type ClientSessionState struct { | |||
extendedMasterSecret bool // Whether an extended master secret was used to generate the session | |||
sctList []byte | |||
ocspResponse []byte | |||
ticketCreationTime time.Time | |||
ticketExpiration time.Time | |||
ticketFlags uint32 | |||
ticketAgeAdd uint32 | |||
} | |||
// ClientSessionCache is a cache of ClientSessionState objects that can be used | |||
@@ -1389,6 +1389,10 @@ func (c *Conn) handlePostHandshakeMessage() error { | |||
serverCertificates: c.peerCertificates, | |||
sctList: c.sctList, | |||
ocspResponse: c.ocspResponse, | |||
ticketCreationTime: c.config.time(), | |||
ticketExpiration: c.config.time().Add(time.Duration(newSessionTicket.ticketLifetime) * time.Second), | |||
ticketFlags: newSessionTicket.ticketFlags, | |||
ticketAgeAdd: newSessionTicket.ticketAgeAdd, | |||
} | |||
cacheKey := clientSessionCacheKey(c.conn.RemoteAddr(), c.config) | |||
@@ -1667,11 +1671,10 @@ func (c *Conn) SendNewSessionTicket() error { | |||
for _, cert := range c.peerCertificates { | |||
peerCertificatesRaw = append(peerCertificatesRaw, cert.Raw) | |||
} | |||
state := sessionState{ | |||
vers: c.vers, | |||
cipherSuite: c.cipherSuite.id, | |||
masterSecret: c.resumptionSecret, | |||
certificates: peerCertificatesRaw, | |||
var ageAdd uint32 | |||
if err := binary.Read(c.config.rand(), binary.LittleEndian, &ageAdd); err != nil { | |||
return err | |||
} | |||
// TODO(davidben): Allow configuring these values. | |||
@@ -1679,7 +1682,20 @@ func (c *Conn) SendNewSessionTicket() error { | |||
version: c.vers, | |||
ticketLifetime: uint32(24 * time.Hour / time.Second), | |||
ticketFlags: ticketAllowDHEResumption | ticketAllowPSKResumption, | |||
ticketAgeAdd: ageAdd, | |||
} | |||
state := sessionState{ | |||
vers: c.vers, | |||
cipherSuite: c.cipherSuite.id, | |||
masterSecret: c.resumptionSecret, | |||
certificates: peerCertificatesRaw, | |||
ticketCreationTime: c.config.time(), | |||
ticketExpiration: c.config.time().Add(time.Duration(m.ticketLifetime) * time.Second), | |||
ticketFlags: m.ticketFlags, | |||
ticketAgeAdd: ageAdd, | |||
} | |||
if !c.config.Bugs.SendEmptySessionTicket { | |||
var err error | |||
m.ticket, err = c.encryptTicket(&state) | |||
@@ -18,6 +18,7 @@ import ( | |||
"math/big" | |||
"net" | |||
"strconv" | |||
"time" | |||
) | |||
type clientHandshakeState struct { | |||
@@ -197,6 +198,8 @@ NextCipherSuite: | |||
// Try to resume a previously negotiated TLS session, if | |||
// available. | |||
cacheKey = clientSessionCacheKey(c.conn.RemoteAddr(), c.config) | |||
// TODO(nharper): Support storing more than one session | |||
// ticket for TLS 1.3. | |||
candidateSession, ok := sessionCache.Get(cacheKey) | |||
if ok { | |||
ticketOk := !c.config.SessionTicketsDisabled || candidateSession.sessionTicket == nil | |||
@@ -219,7 +222,7 @@ NextCipherSuite: | |||
} | |||
} | |||
if session != nil { | |||
if session != nil && c.config.time().Before(session.ticketExpiration) { | |||
ticket := session.sessionTicket | |||
if c.config.Bugs.CorruptTicket && len(ticket) > 0 { | |||
ticket = make([]byte, len(session.sessionTicket)) | |||
@@ -232,7 +235,21 @@ NextCipherSuite: | |||
} | |||
if session.vers >= VersionTLS13 { | |||
// TODO(davidben): Offer TLS 1.3 tickets. | |||
// TODO(nharper): Support sending more | |||
// than one PSK identity. | |||
if session.ticketFlags&ticketAllowDHEResumption != 0 { | |||
var found bool | |||
for _, id := range hello.cipherSuites { | |||
if id == session.cipherSuite { | |||
found = true | |||
break | |||
} | |||
} | |||
if found { | |||
hello.pskIdentities = [][]uint8{ticket} | |||
hello.cipherSuites = append(hello.cipherSuites, ecdhePSKSuite(session.cipherSuite)) | |||
} | |||
} | |||
} else if ticket != nil { | |||
hello.sessionTicket = ticket | |||
// A random session ID is used to detect when the | |||
@@ -411,7 +428,7 @@ NextCipherSuite: | |||
} | |||
} | |||
suite := mutualCipherSuite(c.config.cipherSuites(), serverHello.cipherSuite) | |||
suite := mutualCipherSuite(hello.cipherSuites, serverHello.cipherSuite) | |||
if suite == nil { | |||
c.sendAlert(alertHandshakeFailure) | |||
return fmt.Errorf("tls: server selected an unsupported cipher suite") | |||
@@ -546,9 +563,18 @@ func (hs *clientHandshakeState) doTLS13Handshake() error { | |||
return errors.New("tls: server omitted the PSK identity extension") | |||
} | |||
// TODO(davidben): Support PSK ciphers and PSK resumption. Set | |||
// the resumption context appropriately if resuming. | |||
return errors.New("tls: PSK ciphers not implemented for TLS 1.3") | |||
// We send at most one PSK identity. | |||
if hs.session == nil || hs.serverHello.pskIdentity != 0 { | |||
c.sendAlert(alertUnknownPSKIdentity) | |||
return errors.New("tls: server sent unknown PSK identity") | |||
} | |||
if ecdhePSKSuite(hs.session.cipherSuite) != hs.suite.id { | |||
c.sendAlert(alertHandshakeFailure) | |||
return errors.New("tls: server sent invalid cipher suite for PSK") | |||
} | |||
psk = deriveResumptionPSK(hs.suite, hs.session.masterSecret) | |||
hs.finishedHash.setResumptionContext(deriveResumptionContext(hs.suite, hs.session.masterSecret)) | |||
c.didResume = true | |||
} else { | |||
if hs.serverHello.hasPSKIdentity { | |||
c.sendAlert(alertUnsupportedExtension) | |||
@@ -626,6 +652,11 @@ func (hs *clientHandshakeState) doTLS13Handshake() error { | |||
c.sendAlert(alertUnsupportedExtension) | |||
return errors.New("tls: server sent SCT list without a certificate") | |||
} | |||
// Copy over authentication from the session. | |||
c.peerCertificates = hs.session.serverCertificates | |||
c.sctList = hs.session.sctList | |||
c.ocspResponse = hs.session.ocspResponse | |||
} else { | |||
c.ocspResponse = encryptedExtensions.extensions.ocspResponse | |||
c.sctList = encryptedExtensions.extensions.sctList | |||
@@ -1223,6 +1254,7 @@ func (hs *clientHandshakeState) readSessionTicket() error { | |||
serverCertificates: c.peerCertificates, | |||
sctList: c.sctList, | |||
ocspResponse: c.ocspResponse, | |||
ticketExpiration: c.config.time().Add(time.Duration(7 * 24 * time.Hour)), | |||
} | |||
if !hs.serverHello.extensions.ticketSupported { | |||
@@ -4,7 +4,10 @@ | |||
package runner | |||
import "bytes" | |||
import ( | |||
"bytes" | |||
"encoding/binary" | |||
) | |||
func writeLen(buf []byte, v, size int) { | |||
for i := 0; i < size; i++ { | |||
@@ -67,6 +70,13 @@ func (bb *byteBuilder) addU32(u uint32) { | |||
*bb.buf = append(*bb.buf, byte(u>>24), byte(u>>16), byte(u>>8), byte(u)) | |||
} | |||
func (bb *byteBuilder) addU64(u uint64) { | |||
bb.flush() | |||
var b [8]byte | |||
binary.BigEndian.PutUint64(b[:], u) | |||
*bb.buf = append(*bb.buf, b[:]...) | |||
} | |||
func (bb *byteBuilder) addU8LengthPrefixed() *byteBuilder { | |||
return bb.createChild(1) | |||
} | |||
@@ -79,6 +89,10 @@ func (bb *byteBuilder) addU24LengthPrefixed() *byteBuilder { | |||
return bb.createChild(3) | |||
} | |||
func (bb *byteBuilder) addU32LengthPrefixed() *byteBuilder { | |||
return bb.createChild(4) | |||
} | |||
func (bb *byteBuilder) addBytes(b []byte) { | |||
bb.flush() | |||
*bb.buf = append(*bb.buf, b...) | |||
@@ -305,22 +305,54 @@ Curves: | |||
_, ecdsaOk := hs.cert.PrivateKey.(*ecdsa.PrivateKey) | |||
// TODO(davidben): Implement PSK support. | |||
pskOk := false | |||
// Select the cipher suite. | |||
var preferenceList, supportedList []uint16 | |||
if config.PreferServerCipherSuites { | |||
preferenceList = config.cipherSuites() | |||
supportedList = hs.clientHello.cipherSuites | |||
} else { | |||
preferenceList = hs.clientHello.cipherSuites | |||
supportedList = config.cipherSuites() | |||
for i, pskIdentity := range hs.clientHello.pskIdentities { | |||
sessionState, ok := c.decryptTicket(pskIdentity) | |||
if !ok { | |||
continue | |||
} | |||
if sessionState.vers != c.vers { | |||
continue | |||
} | |||
if sessionState.ticketFlags&ticketAllowDHEResumption == 0 { | |||
continue | |||
} | |||
if sessionState.ticketExpiration.Before(c.config.time()) { | |||
continue | |||
} | |||
suiteId := ecdhePSKSuite(sessionState.cipherSuite) | |||
suite := mutualCipherSuite(hs.clientHello.cipherSuites, suiteId) | |||
var found bool | |||
for _, id := range config.cipherSuites() { | |||
if id == sessionState.cipherSuite { | |||
found = true | |||
break | |||
} | |||
} | |||
if suite != nil && found { | |||
hs.sessionState = sessionState | |||
hs.suite = suite | |||
hs.hello.hasPSKIdentity = true | |||
hs.hello.pskIdentity = uint16(i) | |||
c.didResume = true | |||
break | |||
} | |||
} | |||
for _, id := range preferenceList { | |||
if hs.suite = c.tryCipherSuite(id, supportedList, c.vers, supportedCurve, ecdsaOk, pskOk); hs.suite != nil { | |||
break | |||
// If not resuming, select the cipher suite. | |||
if hs.suite == nil { | |||
var preferenceList, supportedList []uint16 | |||
if config.PreferServerCipherSuites { | |||
preferenceList = config.cipherSuites() | |||
supportedList = hs.clientHello.cipherSuites | |||
} else { | |||
preferenceList = hs.clientHello.cipherSuites | |||
supportedList = config.cipherSuites() | |||
} | |||
for _, id := range preferenceList { | |||
if hs.suite = c.tryCipherSuite(id, supportedList, c.vers, supportedCurve, ecdsaOk, false); hs.suite != nil { | |||
break | |||
} | |||
} | |||
} | |||
@@ -339,9 +371,19 @@ Curves: | |||
hs.writeClientHash(hs.clientHello.marshal()) | |||
// Resolve PSK and compute the early secret. | |||
// TODO(davidben): Implement PSK in TLS 1.3. | |||
psk := hs.finishedHash.zeroSecret() | |||
hs.finishedHash.setResumptionContext(hs.finishedHash.zeroSecret()) | |||
var psk []byte | |||
// The only way for hs.suite to be a PSK suite yet for there to be | |||
// no sessionState is if config.Bugs.EnableAllCiphers is true and | |||
// the test runner forced us to negotiated a PSK suite. It doesn't | |||
// really matter what we do here so long as we continue the | |||
// handshake and let the client error out. | |||
if hs.suite.flags&suitePSK != 0 && hs.sessionState != nil { | |||
psk = deriveResumptionPSK(hs.suite, hs.sessionState.masterSecret) | |||
hs.finishedHash.setResumptionContext(deriveResumptionContext(hs.suite, hs.sessionState.masterSecret)) | |||
} else { | |||
psk = hs.finishedHash.zeroSecret() | |||
hs.finishedHash.setResumptionContext(hs.finishedHash.zeroSecret()) | |||
} | |||
earlySecret := hs.finishedHash.extractKey(hs.finishedHash.zeroSecret(), psk) | |||
@@ -494,9 +536,7 @@ Curves: | |||
c.out.useTrafficSecret(c.vers, hs.suite, handshakeTrafficSecret, handshakePhase, serverWrite) | |||
c.in.useTrafficSecret(c.vers, hs.suite, handshakeTrafficSecret, handshakePhase, clientWrite) | |||
if hs.suite.flags&suitePSK != 0 { | |||
return errors.New("tls: PSK ciphers not implemented for TLS 1.3") | |||
} else { | |||
if hs.suite.flags&suitePSK == 0 { | |||
if hs.clientHello.ocspStapling { | |||
encryptedExtensions.extensions.ocspResponse = hs.cert.OCSPStaple | |||
} | |||
@@ -574,6 +614,15 @@ Curves: | |||
hs.writeServerHash(certVerify.marshal()) | |||
c.writeRecord(recordTypeHandshake, certVerify.marshal()) | |||
} else { | |||
// Pick up certificates from the session instead. | |||
// hs.sessionState may be nil if config.Bugs.EnableAllCiphers is | |||
// true. | |||
if hs.sessionState != nil && len(hs.sessionState.certificates) > 0 { | |||
if _, err := hs.processCertsFromClient(hs.sessionState.certificates); err != nil { | |||
return err | |||
} | |||
} | |||
} | |||
finished := new(finishedMsg) | |||
@@ -497,3 +497,11 @@ func deriveTrafficAEAD(version uint16, suite *cipherSuite, secret, phase []byte, | |||
func updateTrafficSecret(hash crypto.Hash, secret []byte) []byte { | |||
return hkdfExpandLabel(hash, secret, applicationTrafficLabel, nil, hash.Size()) | |||
} | |||
func deriveResumptionPSK(suite *cipherSuite, resumptionSecret []byte) []byte { | |||
return hkdfExpandLabel(suite.hash(), resumptionSecret, []byte("resumption psk"), nil, suite.hash().Size()) | |||
} | |||
func deriveResumptionContext(suite *cipherSuite, resumptionSecret []byte) []byte { | |||
return hkdfExpandLabel(suite.hash(), resumptionSecret, []byte("resumption context"), nil, suite.hash().Size()) | |||
} |
@@ -5,14 +5,15 @@ | |||
package runner | |||
import ( | |||
"bytes" | |||
"crypto/aes" | |||
"crypto/cipher" | |||
"crypto/hmac" | |||
"crypto/sha256" | |||
"crypto/subtle" | |||
"encoding/binary" | |||
"errors" | |||
"io" | |||
"time" | |||
) | |||
// sessionState contains the information that is serialized into a session | |||
@@ -24,79 +25,40 @@ type sessionState struct { | |||
handshakeHash []byte | |||
certificates [][]byte | |||
extendedMasterSecret bool | |||
} | |||
func (s *sessionState) equal(i interface{}) bool { | |||
s1, ok := i.(*sessionState) | |||
if !ok { | |||
return false | |||
} | |||
if s.vers != s1.vers || | |||
s.cipherSuite != s1.cipherSuite || | |||
!bytes.Equal(s.masterSecret, s1.masterSecret) || | |||
!bytes.Equal(s.handshakeHash, s1.handshakeHash) || | |||
s.extendedMasterSecret != s1.extendedMasterSecret { | |||
return false | |||
} | |||
if len(s.certificates) != len(s1.certificates) { | |||
return false | |||
} | |||
for i := range s.certificates { | |||
if !bytes.Equal(s.certificates[i], s1.certificates[i]) { | |||
return false | |||
} | |||
} | |||
return true | |||
ticketCreationTime time.Time | |||
ticketExpiration time.Time | |||
ticketFlags uint32 | |||
ticketAgeAdd uint32 | |||
} | |||
func (s *sessionState) marshal() []byte { | |||
length := 2 + 2 + 2 + len(s.masterSecret) + 2 + len(s.handshakeHash) + 2 | |||
msg := newByteBuilder() | |||
msg.addU16(s.vers) | |||
msg.addU16(s.cipherSuite) | |||
masterSecret := msg.addU16LengthPrefixed() | |||
masterSecret.addBytes(s.masterSecret) | |||
handshakeHash := msg.addU16LengthPrefixed() | |||
handshakeHash.addBytes(s.handshakeHash) | |||
msg.addU16(uint16(len(s.certificates))) | |||
for _, cert := range s.certificates { | |||
length += 4 + len(cert) | |||
} | |||
length++ | |||
ret := make([]byte, length) | |||
x := ret | |||
x[0] = byte(s.vers >> 8) | |||
x[1] = byte(s.vers) | |||
x[2] = byte(s.cipherSuite >> 8) | |||
x[3] = byte(s.cipherSuite) | |||
x[4] = byte(len(s.masterSecret) >> 8) | |||
x[5] = byte(len(s.masterSecret)) | |||
x = x[6:] | |||
copy(x, s.masterSecret) | |||
x = x[len(s.masterSecret):] | |||
x[0] = byte(len(s.handshakeHash) >> 8) | |||
x[1] = byte(len(s.handshakeHash)) | |||
x = x[2:] | |||
copy(x, s.handshakeHash) | |||
x = x[len(s.handshakeHash):] | |||
x[0] = byte(len(s.certificates) >> 8) | |||
x[1] = byte(len(s.certificates)) | |||
x = x[2:] | |||
for _, cert := range s.certificates { | |||
x[0] = byte(len(cert) >> 24) | |||
x[1] = byte(len(cert) >> 16) | |||
x[2] = byte(len(cert) >> 8) | |||
x[3] = byte(len(cert)) | |||
copy(x[4:], cert) | |||
x = x[4+len(cert):] | |||
certMsg := msg.addU32LengthPrefixed() | |||
certMsg.addBytes(cert) | |||
} | |||
if s.extendedMasterSecret { | |||
x[0] = 1 | |||
msg.addU8(1) | |||
} else { | |||
msg.addU8(0) | |||
} | |||
if s.vers >= VersionTLS13 { | |||
msg.addU64(uint64(s.ticketCreationTime.UnixNano())) | |||
msg.addU64(uint64(s.ticketExpiration.UnixNano())) | |||
msg.addU32(s.ticketFlags) | |||
msg.addU32(s.ticketAgeAdd) | |||
} | |||
x = x[1:] | |||
return ret | |||
return msg.finish() | |||
} | |||
func (s *sessionState) unmarshal(data []byte) bool { | |||
@@ -162,6 +124,20 @@ func (s *sessionState) unmarshal(data []byte) bool { | |||
} | |||
data = data[1:] | |||
if s.vers >= VersionTLS13 { | |||
if len(data) < 24 { | |||
return false | |||
} | |||
s.ticketCreationTime = time.Unix(0, int64(binary.BigEndian.Uint64(data))) | |||
data = data[8:] | |||
s.ticketExpiration = time.Unix(0, int64(binary.BigEndian.Uint64(data))) | |||
data = data[8:] | |||
s.ticketFlags = binary.BigEndian.Uint32(data) | |||
data = data[4:] | |||
s.ticketAgeAdd = binary.BigEndian.Uint32(data) | |||
data = data[4:] | |||
} | |||
if len(data) > 0 { | |||
return false | |||
} | |||