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 }