Implement basic TLS 1.3 server handshake in Go.
[Originally written by nharper, revised by davidben.]
Change-Id: If1d45c33994476f4bc9cd69831b6bbed40f792d0
Reviewed-on: https://boringssl-review.googlesource.com/8599
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index c8ddf91..fba1927 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -52,67 +52,74 @@
if err := hs.readClientHello(); err != nil {
return err
}
- isResume, err := hs.processClientHello()
- if err != nil {
- return err
- }
- // For an overview of TLS handshaking, see https://tools.ietf.org/html/rfc5246#section-7.3
- if isResume {
- // The client has included a session ticket and so we do an abbreviated handshake.
- if err := hs.doResumeHandshake(); err != nil {
+ if c.vers >= VersionTLS13 && enableTLS13Handshake {
+ if err := hs.doTLS13Handshake(); err != nil {
return err
}
- if err := hs.establishKeys(); err != nil {
+ } else {
+ isResume, err := hs.processClientHello()
+ if err != nil {
return err
}
- if c.config.Bugs.RenewTicketOnResume {
+
+ // For an overview of TLS handshaking, see https://tools.ietf.org/html/rfc5246#section-7.3
+ if isResume {
+ // The client has included a session ticket and so we do an abbreviated handshake.
+ if err := hs.doResumeHandshake(); err != nil {
+ return err
+ }
+ if err := hs.establishKeys(); err != nil {
+ return err
+ }
+ if c.config.Bugs.RenewTicketOnResume {
+ if err := hs.sendSessionTicket(); err != nil {
+ return err
+ }
+ }
+ if err := hs.sendFinished(c.firstFinished[:]); err != nil {
+ return err
+ }
+ // Most retransmits are triggered by a timeout, but the final
+ // leg of the handshake is retransmited upon re-receiving a
+ // Finished.
+ if err := c.simulatePacketLoss(func() {
+ c.writeRecord(recordTypeHandshake, hs.finishedBytes)
+ c.flushHandshake()
+ }); err != nil {
+ return err
+ }
+ if err := hs.readFinished(nil, isResume); err != nil {
+ return err
+ }
+ c.didResume = true
+ } else {
+ // The client didn't include a session ticket, or it wasn't
+ // valid so we do a full handshake.
+ if err := hs.doFullHandshake(); err != nil {
+ return err
+ }
+ if err := hs.establishKeys(); err != nil {
+ return err
+ }
+ if err := hs.readFinished(c.firstFinished[:], isResume); err != nil {
+ return err
+ }
+ if c.config.Bugs.AlertBeforeFalseStartTest != 0 {
+ c.sendAlert(c.config.Bugs.AlertBeforeFalseStartTest)
+ }
+ if c.config.Bugs.ExpectFalseStart {
+ if err := c.readRecord(recordTypeApplicationData); err != nil {
+ return fmt.Errorf("tls: peer did not false start: %s", err)
+ }
+ }
if err := hs.sendSessionTicket(); err != nil {
return err
}
- }
- if err := hs.sendFinished(c.firstFinished[:]); err != nil {
- return err
- }
- // Most retransmits are triggered by a timeout, but the final
- // leg of the handshake is retransmited upon re-receiving a
- // Finished.
- if err := c.simulatePacketLoss(func() {
- c.writeRecord(recordTypeHandshake, hs.finishedBytes)
- c.flushHandshake()
- }); err != nil {
- return err
- }
- if err := hs.readFinished(nil, isResume); err != nil {
- return err
- }
- c.didResume = true
- } else {
- // The client didn't include a session ticket, or it wasn't
- // valid so we do a full handshake.
- if err := hs.doFullHandshake(); err != nil {
- return err
- }
- if err := hs.establishKeys(); err != nil {
- return err
- }
- if err := hs.readFinished(c.firstFinished[:], isResume); err != nil {
- return err
- }
- if c.config.Bugs.AlertBeforeFalseStartTest != 0 {
- c.sendAlert(c.config.Bugs.AlertBeforeFalseStartTest)
- }
- if c.config.Bugs.ExpectFalseStart {
- if err := c.readRecord(recordTypeApplicationData); err != nil {
- return fmt.Errorf("tls: peer did not false start: %s", err)
+ if err := hs.sendFinished(nil); err != nil {
+ return err
}
}
- if err := hs.sendSessionTicket(); err != nil {
- return err
- }
- if err := hs.sendFinished(nil); err != nil {
- return err
- }
}
c.handshakeComplete = true
copy(c.clientRandom[:], hs.clientHello.random)
@@ -249,6 +256,234 @@
return nil
}
+func (hs *serverHandshakeState) doTLS13Handshake() error {
+ c := hs.c
+ config := c.config
+
+ hs.hello = &serverHelloMsg{
+ isDTLS: c.isDTLS,
+ vers: c.vers,
+ }
+
+ hs.hello.random = make([]byte, 32)
+ if _, err := io.ReadFull(config.rand(), hs.hello.random); err != nil {
+ c.sendAlert(alertInternalError)
+ return err
+ }
+
+ // TLS 1.3 forbids clients from advertising any non-null compression.
+ if len(hs.clientHello.compressionMethods) != 1 || hs.clientHello.compressionMethods[0] != compressionNone {
+ return errors.New("tls: client sent compression method other than null for TLS 1.3")
+ }
+
+ // Prepare an EncryptedExtensions message, but do not send it yet.
+ encryptedExtensions := new(encryptedExtensionsMsg)
+ if err := hs.processClientExtensions(&encryptedExtensions.extensions); err != nil {
+ return err
+ }
+
+ supportedCurve := false
+ var selectedCurve CurveID
+ preferredCurves := config.curvePreferences()
+Curves:
+ for _, curve := range hs.clientHello.supportedCurves {
+ for _, supported := range preferredCurves {
+ if supported == curve {
+ supportedCurve = true
+ selectedCurve = curve
+ break 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 _, id := range preferenceList {
+ if hs.suite = c.tryCipherSuite(id, supportedList, c.vers, supportedCurve, ecdsaOk, pskOk); hs.suite != nil {
+ break
+ }
+ }
+
+ if hs.suite == nil {
+ c.sendAlert(alertHandshakeFailure)
+ return errors.New("tls: no cipher suite supported by both client and server")
+ }
+
+ hs.hello.cipherSuite = hs.suite.id
+ hs.finishedHash = newFinishedHash(c.vers, hs.suite)
+ hs.finishedHash.discardHandshakeBuffer()
+ hs.writeClientHash(hs.clientHello.marshal())
+
+ // Resolve PSK and compute the early secret.
+ var psk []byte
+ if hs.suite.flags&suitePSK != 0 {
+ return errors.New("tls: PSK ciphers not implemented for TLS 1.3")
+ } else {
+ psk = hs.finishedHash.zeroSecret()
+ hs.finishedHash.setResumptionContext(hs.finishedHash.zeroSecret())
+ }
+
+ earlySecret := hs.finishedHash.extractKey(hs.finishedHash.zeroSecret(), psk)
+
+ // Resolve ECDHE and compute the handshake secret.
+ var ecdheSecret []byte
+ if hs.suite.flags&suiteECDHE != 0 {
+ // Look for the key share corresponding to our selected curve.
+ var selectedKeyShare *keyShareEntry
+ for i := range hs.clientHello.keyShares {
+ if hs.clientHello.keyShares[i].group == selectedCurve {
+ selectedKeyShare = &hs.clientHello.keyShares[i]
+ break
+ }
+ }
+
+ if selectedKeyShare == nil {
+ // TODO(davidben,nharper): Implement HelloRetryRequest.
+ return errors.New("tls: HelloRetryRequest not implemented")
+ }
+
+ // Once a curve has been selected and a key share identified,
+ // the server needs to generate a public value and send it in
+ // the ServerHello.
+ curve, ok := curveForCurveID(selectedKeyShare.group)
+ if !ok {
+ panic("tls: server failed to look up curve ID")
+ }
+ var publicKey []byte
+ var err error
+ publicKey, ecdheSecret, err = curve.accept(config.rand(), selectedKeyShare.keyExchange)
+ if err != nil {
+ c.sendAlert(alertHandshakeFailure)
+ return err
+ }
+ hs.hello.hasKeyShare = true
+ hs.hello.keyShare = keyShareEntry{
+ group: selectedKeyShare.group,
+ keyExchange: publicKey,
+ }
+ } else {
+ ecdheSecret = hs.finishedHash.zeroSecret()
+ }
+
+ // Send unencrypted ServerHello.
+ hs.writeServerHash(hs.hello.marshal())
+ c.writeRecord(recordTypeHandshake, hs.hello.marshal())
+ c.flushHandshake()
+
+ // Compute the handshake secret.
+ handshakeSecret := hs.finishedHash.extractKey(earlySecret, ecdheSecret)
+
+ // Switch to handshake traffic keys.
+ handshakeTrafficSecret := hs.finishedHash.deriveSecret(handshakeSecret, handshakeTrafficLabel)
+ c.out.updateKeys(deriveTrafficAEAD(c.vers, hs.suite, handshakeTrafficSecret, handshakePhase, serverWrite), c.vers)
+ c.in.updateKeys(deriveTrafficAEAD(c.vers, hs.suite, handshakeTrafficSecret, handshakePhase, clientWrite), c.vers)
+
+ // Send EncryptedExtensions.
+ hs.writeServerHash(encryptedExtensions.marshal())
+ c.writeRecord(recordTypeHandshake, encryptedExtensions.marshal())
+
+ if hs.suite.flags&suitePSK == 0 {
+ if config.ClientAuth >= RequestClientCert {
+ // TODO(davidben): Implement client auth.
+ return errors.New("tls: client auth not implemented")
+ }
+
+ certMsg := &certificateMsg{
+ hasRequestContext: true,
+ }
+ if !config.Bugs.EmptyCertificateList {
+ certMsg.certificates = hs.cert.Certificate
+ }
+ hs.writeServerHash(certMsg.marshal())
+ c.writeRecord(recordTypeHandshake, certMsg.marshal())
+
+ certVerify := &certificateVerifyMsg{
+ hasSignatureAlgorithm: true,
+ }
+
+ // Determine the hash to sign.
+ privKey := hs.cert.PrivateKey
+
+ var err error
+ certVerify.signatureAlgorithm, err = selectSignatureAlgorithm(c.vers, privKey, config, hs.clientHello.signatureAlgorithms)
+ if err != nil {
+ c.sendAlert(alertInternalError)
+ return err
+ }
+
+ input := hs.finishedHash.certificateVerifyInput(serverCertificateVerifyContextTLS13)
+ certVerify.signature, err = signMessage(c.vers, privKey, c.config, certVerify.signatureAlgorithm, input)
+ if err != nil {
+ c.sendAlert(alertInternalError)
+ return err
+ }
+
+ hs.writeServerHash(certVerify.marshal())
+ c.writeRecord(recordTypeHandshake, certVerify.marshal())
+ }
+
+ finished := new(finishedMsg)
+ finished.verifyData = hs.finishedHash.serverSum(handshakeTrafficSecret)
+ if config.Bugs.BadFinished {
+ finished.verifyData[0]++
+ }
+ hs.writeServerHash(finished.marshal())
+ c.writeRecord(recordTypeHandshake, finished.marshal())
+ c.flushHandshake()
+
+ // The various secrets do not incorporate the client's final leg, so
+ // derive them now before updating the handshake context.
+ masterSecret := hs.finishedHash.extractKey(handshakeSecret, hs.finishedHash.zeroSecret())
+ trafficSecret := hs.finishedHash.deriveSecret(masterSecret, applicationTrafficLabel)
+
+ // If we requested a client certificate, then the client must send a
+ // certificate message, even if it's empty.
+ if config.ClientAuth >= RequestClientCert {
+ return errors.New("tls: client certificates not implemented")
+ }
+
+ // Read the client Finished message.
+ msg, err := c.readHandshake()
+ if err != nil {
+ return err
+ }
+ clientFinished, ok := msg.(*finishedMsg)
+ if !ok {
+ c.sendAlert(alertUnexpectedMessage)
+ return unexpectedMessageError(clientFinished, msg)
+ }
+
+ verify := hs.finishedHash.clientSum(handshakeTrafficSecret)
+ if len(verify) != len(clientFinished.verifyData) ||
+ subtle.ConstantTimeCompare(verify, clientFinished.verifyData) != 1 {
+ c.sendAlert(alertHandshakeFailure)
+ return errors.New("tls: client's Finished message was incorrect")
+ }
+
+ // Switch to application data keys.
+ c.out.updateKeys(deriveTrafficAEAD(c.vers, hs.suite, trafficSecret, applicationPhase, serverWrite), c.vers)
+ c.in.updateKeys(deriveTrafficAEAD(c.vers, hs.suite, trafficSecret, applicationPhase, clientWrite), c.vers)
+
+ // TODO(davidben): Derive and save the exporter master secret for key exporters. Swap out the masterSecret field.
+ // TODO(davidben): Derive and save the resumption master secret for receiving tickets.
+ // TODO(davidben): Save the traffic secret for KeyUpdate.
+ c.cipherSuite = hs.suite
+ return nil
+}
+
// processClientHello processes the ClientHello message from the client and
// decides whether we will perform session resumption.
func (hs *serverHandshakeState) processClientHello() (isResume bool, err error) {
@@ -341,7 +576,7 @@
}
for _, id := range preferenceList {
- if hs.suite = c.tryCipherSuite(id, supportedList, c.vers, hs.ellipticOk, hs.ecdsaOk); hs.suite != nil {
+ if hs.suite = c.tryCipherSuite(id, supportedList, c.vers, hs.ellipticOk, hs.ecdsaOk, true); hs.suite != nil {
break
}
}
@@ -360,23 +595,25 @@
config := hs.c.config
c := hs.c
- if !bytes.Equal(c.clientVerify, hs.clientHello.secureRenegotiation) {
- c.sendAlert(alertHandshakeFailure)
- return errors.New("tls: renegotiation mismatch")
- }
-
- if len(c.clientVerify) > 0 && !c.config.Bugs.EmptyRenegotiationInfo {
- serverExtensions.secureRenegotiation = append(serverExtensions.secureRenegotiation, c.clientVerify...)
- serverExtensions.secureRenegotiation = append(serverExtensions.secureRenegotiation, c.serverVerify...)
- if c.config.Bugs.BadRenegotiationInfo {
- serverExtensions.secureRenegotiation[0] ^= 0x80
+ if c.vers < VersionTLS13 || !enableTLS13Handshake {
+ if !bytes.Equal(c.clientVerify, hs.clientHello.secureRenegotiation) {
+ c.sendAlert(alertHandshakeFailure)
+ return errors.New("tls: renegotiation mismatch")
}
- } else {
- serverExtensions.secureRenegotiation = hs.clientHello.secureRenegotiation
- }
- if c.noRenegotiationInfo() {
- serverExtensions.secureRenegotiation = nil
+ if len(c.clientVerify) > 0 && !c.config.Bugs.EmptyRenegotiationInfo {
+ serverExtensions.secureRenegotiation = append(serverExtensions.secureRenegotiation, c.clientVerify...)
+ serverExtensions.secureRenegotiation = append(serverExtensions.secureRenegotiation, c.serverVerify...)
+ if c.config.Bugs.BadRenegotiationInfo {
+ serverExtensions.secureRenegotiation[0] ^= 0x80
+ }
+ } else {
+ serverExtensions.secureRenegotiation = hs.clientHello.secureRenegotiation
+ }
+
+ if c.noRenegotiationInfo() {
+ serverExtensions.secureRenegotiation = nil
+ }
}
serverExtensions.duplicateExtension = c.config.Bugs.DuplicateExtension
@@ -408,22 +645,25 @@
c.usedALPN = true
}
}
- if len(hs.clientHello.alpnProtocols) == 0 || c.config.Bugs.NegotiateALPNAndNPN {
- // Although sending an empty NPN extension is reasonable, Firefox has
- // had a bug around this. Best to send nothing at all if
- // config.NextProtos is empty. See
- // https://code.google.com/p/go/issues/detail?id=5445.
- if hs.clientHello.nextProtoNeg && len(config.NextProtos) > 0 {
- serverExtensions.nextProtoNeg = true
- serverExtensions.nextProtos = config.NextProtos
- serverExtensions.npnLast = config.Bugs.SwapNPNAndALPN
+
+ if c.vers < VersionTLS13 || !enableTLS13Handshake {
+ if len(hs.clientHello.alpnProtocols) == 0 || c.config.Bugs.NegotiateALPNAndNPN {
+ // Although sending an empty NPN extension is reasonable, Firefox has
+ // had a bug around this. Best to send nothing at all if
+ // config.NextProtos is empty. See
+ // https://code.google.com/p/go/issues/detail?id=5445.
+ if hs.clientHello.nextProtoNeg && len(config.NextProtos) > 0 {
+ serverExtensions.nextProtoNeg = true
+ serverExtensions.nextProtos = config.NextProtos
+ serverExtensions.npnLast = config.Bugs.SwapNPNAndALPN
+ }
}
- }
- serverExtensions.extendedMasterSecret = c.vers >= VersionTLS10 && hs.clientHello.extendedMasterSecret && !c.config.Bugs.NoExtendedMasterSecret
+ serverExtensions.extendedMasterSecret = c.vers >= VersionTLS10 && hs.clientHello.extendedMasterSecret && !c.config.Bugs.NoExtendedMasterSecret
- if hs.clientHello.channelIDSupported && config.RequestChannelID {
- serverExtensions.channelIDRequested = true
+ if hs.clientHello.channelIDSupported && config.RequestChannelID {
+ serverExtensions.channelIDRequested = true
+ }
}
if hs.clientHello.srtpProtectionProfiles != nil {
@@ -496,7 +736,7 @@
}
// Check that we also support the ciphersuite from the session.
- hs.suite = c.tryCipherSuite(hs.sessionState.cipherSuite, c.config.cipherSuites(), hs.sessionState.vers, hs.ellipticOk, hs.ecdsaOk)
+ hs.suite = c.tryCipherSuite(hs.sessionState.cipherSuite, c.config.cipherSuites(), hs.sessionState.vers, hs.ellipticOk, hs.ecdsaOk, true)
if hs.suite == nil {
return false
}
@@ -1048,7 +1288,7 @@
// tryCipherSuite returns a cipherSuite with the given id if that cipher suite
// is acceptable to use.
-func (c *Conn) tryCipherSuite(id uint16, supportedCipherSuites []uint16, version uint16, ellipticOk, ecdsaOk bool) *cipherSuite {
+func (c *Conn) tryCipherSuite(id uint16, supportedCipherSuites []uint16, version uint16, ellipticOk, ecdsaOk, pskOk bool) *cipherSuite {
for _, supported := range supportedCipherSuites {
if id == supported {
var candidate *cipherSuite
@@ -1065,6 +1305,9 @@
// Don't select a ciphersuite which we can't
// support for this client.
if !c.config.Bugs.EnableAllCiphers {
+ if (candidate.flags&suitePSK != 0) && !pskOk {
+ continue
+ }
if (candidate.flags&suiteECDHE != 0) && !ellipticOk {
continue
}
@@ -1074,6 +1317,9 @@
if version < VersionTLS12 && candidate.flags&suiteTLS12 != 0 {
continue
}
+ if version >= VersionTLS13 && candidate.flags&suiteTLS13 == 0 {
+ continue
+ }
if c.isDTLS && candidate.flags&suiteNoDTLS != 0 {
continue
}