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>
This commit is contained in:
Nick Harper 2016-07-07 17:36:52 -07:00 committed by David Benjamin
parent 1f61f0d7c3
commit 728eed8277

View File

@ -52,67 +52,74 @@ func (c *Conn) serverHandshake() error {
if err := hs.readClientHello(); err != nil { if err := hs.readClientHello(); err != nil {
return err 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 c.vers >= VersionTLS13 && enableTLS13Handshake {
if isResume { if err := hs.doTLS13Handshake(); err != nil {
// The client has included a session ticket and so we do an abbreviated handshake.
if err := hs.doResumeHandshake(); err != nil {
return err return err
} }
if err := hs.establishKeys(); err != nil { } else {
isResume, err := hs.processClientHello()
if err != nil {
return err 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 { if err := hs.sendSessionTicket(); err != nil {
return err return err
} }
} if err := hs.sendFinished(nil); err != nil {
if err := hs.sendFinished(c.firstFinished[:]); err != nil { return err
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(nil); err != nil {
return err
}
} }
c.handshakeComplete = true c.handshakeComplete = true
copy(c.clientRandom[:], hs.clientHello.random) copy(c.clientRandom[:], hs.clientHello.random)
@ -249,6 +256,234 @@ func (hs *serverHandshakeState) readClientHello() error {
return nil 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 // processClientHello processes the ClientHello message from the client and
// decides whether we will perform session resumption. // decides whether we will perform session resumption.
func (hs *serverHandshakeState) processClientHello() (isResume bool, err error) { func (hs *serverHandshakeState) processClientHello() (isResume bool, err error) {
@ -341,7 +576,7 @@ Curves:
} }
for _, id := range preferenceList { 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 break
} }
} }
@ -360,23 +595,25 @@ func (hs *serverHandshakeState) processClientExtensions(serverExtensions *server
config := hs.c.config config := hs.c.config
c := hs.c c := hs.c
if !bytes.Equal(c.clientVerify, hs.clientHello.secureRenegotiation) { if c.vers < VersionTLS13 || !enableTLS13Handshake {
c.sendAlert(alertHandshakeFailure) if !bytes.Equal(c.clientVerify, hs.clientHello.secureRenegotiation) {
return errors.New("tls: renegotiation mismatch") 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
} }
} else {
serverExtensions.secureRenegotiation = hs.clientHello.secureRenegotiation
}
if c.noRenegotiationInfo() { if len(c.clientVerify) > 0 && !c.config.Bugs.EmptyRenegotiationInfo {
serverExtensions.secureRenegotiation = nil 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 serverExtensions.duplicateExtension = c.config.Bugs.DuplicateExtension
@ -408,22 +645,25 @@ func (hs *serverHandshakeState) processClientExtensions(serverExtensions *server
c.usedALPN = true c.usedALPN = true
} }
} }
if len(hs.clientHello.alpnProtocols) == 0 || c.config.Bugs.NegotiateALPNAndNPN {
// Although sending an empty NPN extension is reasonable, Firefox has if c.vers < VersionTLS13 || !enableTLS13Handshake {
// had a bug around this. Best to send nothing at all if if len(hs.clientHello.alpnProtocols) == 0 || c.config.Bugs.NegotiateALPNAndNPN {
// config.NextProtos is empty. See // Although sending an empty NPN extension is reasonable, Firefox has
// https://code.google.com/p/go/issues/detail?id=5445. // had a bug around this. Best to send nothing at all if
if hs.clientHello.nextProtoNeg && len(config.NextProtos) > 0 { // config.NextProtos is empty. See
serverExtensions.nextProtoNeg = true // https://code.google.com/p/go/issues/detail?id=5445.
serverExtensions.nextProtos = config.NextProtos if hs.clientHello.nextProtoNeg && len(config.NextProtos) > 0 {
serverExtensions.npnLast = config.Bugs.SwapNPNAndALPN 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 { if hs.clientHello.channelIDSupported && config.RequestChannelID {
serverExtensions.channelIDRequested = true serverExtensions.channelIDRequested = true
}
} }
if hs.clientHello.srtpProtectionProfiles != nil { if hs.clientHello.srtpProtectionProfiles != nil {
@ -496,7 +736,7 @@ func (hs *serverHandshakeState) checkForResumption() bool {
} }
// Check that we also support the ciphersuite from the session. // 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 { if hs.suite == nil {
return false return false
} }
@ -1048,7 +1288,7 @@ func (hs *serverHandshakeState) writeHash(msg []byte, seqno uint16) {
// tryCipherSuite returns a cipherSuite with the given id if that cipher suite // tryCipherSuite returns a cipherSuite with the given id if that cipher suite
// is acceptable to use. // 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 { for _, supported := range supportedCipherSuites {
if id == supported { if id == supported {
var candidate *cipherSuite var candidate *cipherSuite
@ -1065,6 +1305,9 @@ func (c *Conn) tryCipherSuite(id uint16, supportedCipherSuites []uint16, version
// Don't select a ciphersuite which we can't // Don't select a ciphersuite which we can't
// support for this client. // support for this client.
if !c.config.Bugs.EnableAllCiphers { if !c.config.Bugs.EnableAllCiphers {
if (candidate.flags&suitePSK != 0) && !pskOk {
continue
}
if (candidate.flags&suiteECDHE != 0) && !ellipticOk { if (candidate.flags&suiteECDHE != 0) && !ellipticOk {
continue continue
} }
@ -1074,6 +1317,9 @@ func (c *Conn) tryCipherSuite(id uint16, supportedCipherSuites []uint16, version
if version < VersionTLS12 && candidate.flags&suiteTLS12 != 0 { if version < VersionTLS12 && candidate.flags&suiteTLS12 != 0 {
continue continue
} }
if version >= VersionTLS13 && candidate.flags&suiteTLS13 == 0 {
continue
}
if c.isDTLS && candidate.flags&suiteNoDTLS != 0 { if c.isDTLS && candidate.flags&suiteNoDTLS != 0 {
continue continue
} }