diff options
Diffstat (limited to 'src/pkg/crypto/tls/handshake_server.go')
-rw-r--r-- | src/pkg/crypto/tls/handshake_server.go | 534 |
1 files changed, 393 insertions, 141 deletions
diff --git a/src/pkg/crypto/tls/handshake_server.go b/src/pkg/crypto/tls/handshake_server.go index 76adc540c..730991016 100644 --- a/src/pkg/crypto/tls/handshake_server.go +++ b/src/pkg/crypto/tls/handshake_server.go @@ -13,31 +13,120 @@ import ( "io" ) +// serverHandshakeState contains details of a server handshake in progress. +// It's discarded once the handshake has completed. +type serverHandshakeState struct { + c *Conn + clientHello *clientHelloMsg + hello *serverHelloMsg + suite *cipherSuite + ellipticOk bool + sessionState *sessionState + finishedHash finishedHash + masterSecret []byte + certsFromClient [][]byte +} + +// serverHandshake performs a TLS handshake as a server. func (c *Conn) serverHandshake() error { config := c.config - msg, err := c.readHandshake() + + // If this is the first server handshake, we generate a random key to + // encrypt the tickets with. + config.serverInitOnce.Do(func() { + if config.SessionTicketsDisabled { + return + } + + // If the key has already been set then we have nothing to do. + for _, b := range config.SessionTicketKey { + if b != 0 { + return + } + } + + if _, err := io.ReadFull(config.rand(), config.SessionTicketKey[:]); err != nil { + config.SessionTicketsDisabled = true + } + }) + + hs := serverHandshakeState{ + c: c, + } + isResume, err := hs.readClientHello() if err != nil { return err } - clientHello, ok := msg.(*clientHelloMsg) + + // 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 err := hs.sendFinished(); err != nil { + return err + } + if err := hs.readFinished(); 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(); err != nil { + return err + } + if err := hs.sendSessionTicket(); err != nil { + return err + } + if err := hs.sendFinished(); err != nil { + return err + } + } + c.handshakeComplete = true + + return nil +} + +// readClientHello reads a ClientHello message from the client and decides +// whether we will perform session resumption. +func (hs *serverHandshakeState) readClientHello() (isResume bool, err error) { + config := hs.c.config + c := hs.c + + msg, err := c.readHandshake() + if err != nil { + return false, err + } + var ok bool + hs.clientHello, ok = msg.(*clientHelloMsg) if !ok { - return c.sendAlert(alertUnexpectedMessage) + return false, c.sendAlert(alertUnexpectedMessage) } - vers, ok := mutualVersion(clientHello.vers) + c.vers, ok = mutualVersion(hs.clientHello.vers) if !ok { - return c.sendAlert(alertProtocolVersion) + return false, c.sendAlert(alertProtocolVersion) } - c.vers = vers c.haveVers = true - finishedHash := newFinishedHash(vers) - finishedHash.Write(clientHello.marshal()) + hs.finishedHash = newFinishedHash(c.vers) + hs.finishedHash.Write(hs.clientHello.marshal()) - hello := new(serverHelloMsg) + hs.hello = new(serverHelloMsg) supportedCurve := false Curves: - for _, curve := range clientHello.supportedCurves { + for _, curve := range hs.clientHello.supportedCurves { switch curve { case curveP256, curveP384, curveP521: supportedCurve = true @@ -46,110 +135,182 @@ Curves: } supportedPointFormat := false - for _, pointFormat := range clientHello.supportedPoints { + for _, pointFormat := range hs.clientHello.supportedPoints { if pointFormat == pointFormatUncompressed { supportedPointFormat = true break } } - - ellipticOk := supportedCurve && supportedPointFormat - - var suite *cipherSuite -FindCipherSuite: - for _, id := range clientHello.cipherSuites { - for _, supported := range config.cipherSuites() { - if id == supported { - var candidate *cipherSuite - - for _, s := range cipherSuites { - if s.id == id { - candidate = s - break - } - } - if candidate == nil { - continue - } - // Don't select a ciphersuite which we can't - // support for this client. - if candidate.elliptic && !ellipticOk { - continue - } - suite = candidate - break FindCipherSuite - } - } - } + hs.ellipticOk = supportedCurve && supportedPointFormat foundCompression := false // We only support null compression, so check that the client offered it. - for _, compression := range clientHello.compressionMethods { + for _, compression := range hs.clientHello.compressionMethods { if compression == compressionNone { foundCompression = true break } } - if suite == nil || !foundCompression { - return c.sendAlert(alertHandshakeFailure) + if !foundCompression { + return false, c.sendAlert(alertHandshakeFailure) } - hello.vers = vers - hello.cipherSuite = suite.id + hs.hello.vers = c.vers t := uint32(config.time().Unix()) - hello.random = make([]byte, 32) - hello.random[0] = byte(t >> 24) - hello.random[1] = byte(t >> 16) - hello.random[2] = byte(t >> 8) - hello.random[3] = byte(t) - _, err = io.ReadFull(config.rand(), hello.random[4:]) + hs.hello.random = make([]byte, 32) + hs.hello.random[0] = byte(t >> 24) + hs.hello.random[1] = byte(t >> 16) + hs.hello.random[2] = byte(t >> 8) + hs.hello.random[3] = byte(t) + _, err = io.ReadFull(config.rand(), hs.hello.random[4:]) if err != nil { - return c.sendAlert(alertInternalError) + return false, c.sendAlert(alertInternalError) + } + hs.hello.compressionMethod = compressionNone + if len(hs.clientHello.serverName) > 0 { + c.serverName = hs.clientHello.serverName + } + if hs.clientHello.nextProtoNeg { + hs.hello.nextProtoNeg = true + hs.hello.nextProtos = config.NextProtos + } + + if hs.checkForResumption() { + return true, nil + } + + var preferenceList, supportedList []uint16 + if c.config.PreferServerCipherSuites { + preferenceList = c.config.cipherSuites() + supportedList = hs.clientHello.cipherSuites + } else { + preferenceList = hs.clientHello.cipherSuites + supportedList = c.config.cipherSuites() + } + + for _, id := range preferenceList { + if hs.suite = c.tryCipherSuite(id, supportedList, hs.ellipticOk); hs.suite != nil { + break + } + } + + if hs.suite == nil { + return false, c.sendAlert(alertHandshakeFailure) + } + + return false, nil +} + +// checkForResumption returns true if we should perform resumption on this connection. +func (hs *serverHandshakeState) checkForResumption() bool { + c := hs.c + + var ok bool + if hs.sessionState, ok = c.decryptTicket(hs.clientHello.sessionTicket); !ok { + return false } - hello.compressionMethod = compressionNone - if clientHello.nextProtoNeg { - hello.nextProtoNeg = true - hello.nextProtos = config.NextProtos + + if hs.sessionState.vers > hs.clientHello.vers { + return false + } + if vers, ok := mutualVersion(hs.sessionState.vers); !ok || vers != hs.sessionState.vers { + return false + } + + cipherSuiteOk := false + // Check that the client is still offering the ciphersuite in the session. + for _, id := range hs.clientHello.cipherSuites { + if id == hs.sessionState.cipherSuite { + cipherSuiteOk = true + break + } + } + if !cipherSuiteOk { + return false + } + + // Check that we also support the ciphersuite from the session. + hs.suite = c.tryCipherSuite(hs.sessionState.cipherSuite, c.config.cipherSuites(), hs.ellipticOk) + if hs.suite == nil { + return false + } + + sessionHasClientCerts := len(hs.sessionState.certificates) != 0 + needClientCerts := c.config.ClientAuth == RequireAnyClientCert || c.config.ClientAuth == RequireAndVerifyClientCert + if needClientCerts && !sessionHasClientCerts { + return false + } + if sessionHasClientCerts && c.config.ClientAuth == NoClientCert { + return false } + return true +} + +func (hs *serverHandshakeState) doResumeHandshake() error { + c := hs.c + + hs.hello.cipherSuite = hs.suite.id + // We echo the client's session ID in the ServerHello to let it know + // that we're doing a resumption. + hs.hello.sessionId = hs.clientHello.sessionId + hs.finishedHash.Write(hs.hello.marshal()) + c.writeRecord(recordTypeHandshake, hs.hello.marshal()) + + if len(hs.sessionState.certificates) > 0 { + if _, err := hs.processCertsFromClient(hs.sessionState.certificates); err != nil { + return err + } + } + + hs.masterSecret = hs.sessionState.masterSecret + + return nil +} + +func (hs *serverHandshakeState) doFullHandshake() error { + config := hs.c.config + c := hs.c + if len(config.Certificates) == 0 { return c.sendAlert(alertInternalError) } cert := &config.Certificates[0] - if len(clientHello.serverName) > 0 { - c.serverName = clientHello.serverName - cert = config.getCertificateForName(clientHello.serverName) + if len(hs.clientHello.serverName) > 0 { + cert = config.getCertificateForName(hs.clientHello.serverName) } - if clientHello.ocspStapling && len(cert.OCSPStaple) > 0 { - hello.ocspStapling = true + if hs.clientHello.ocspStapling && len(cert.OCSPStaple) > 0 { + hs.hello.ocspStapling = true } - finishedHash.Write(hello.marshal()) - c.writeRecord(recordTypeHandshake, hello.marshal()) + hs.hello.ticketSupported = hs.clientHello.ticketSupported && !config.SessionTicketsDisabled + hs.hello.cipherSuite = hs.suite.id + hs.finishedHash.Write(hs.hello.marshal()) + c.writeRecord(recordTypeHandshake, hs.hello.marshal()) certMsg := new(certificateMsg) certMsg.certificates = cert.Certificate - finishedHash.Write(certMsg.marshal()) + hs.finishedHash.Write(certMsg.marshal()) c.writeRecord(recordTypeHandshake, certMsg.marshal()) - if hello.ocspStapling { + if hs.hello.ocspStapling { certStatus := new(certificateStatusMsg) certStatus.statusType = statusTypeOCSP certStatus.response = cert.OCSPStaple - finishedHash.Write(certStatus.marshal()) + hs.finishedHash.Write(certStatus.marshal()) c.writeRecord(recordTypeHandshake, certStatus.marshal()) } - keyAgreement := suite.ka() - skx, err := keyAgreement.generateServerKeyExchange(config, cert, clientHello, hello) + keyAgreement := hs.suite.ka() + skx, err := keyAgreement.generateServerKeyExchange(config, cert, hs.clientHello, hs.hello) if err != nil { c.sendAlert(alertHandshakeFailure) return err } if skx != nil { - finishedHash.Write(skx.marshal()) + hs.finishedHash.Write(skx.marshal()) c.writeRecord(recordTypeHandshake, skx.marshal()) } @@ -166,28 +327,29 @@ FindCipherSuite: if config.ClientCAs != nil { certReq.certificateAuthorities = config.ClientCAs.Subjects() } - finishedHash.Write(certReq.marshal()) + hs.finishedHash.Write(certReq.marshal()) c.writeRecord(recordTypeHandshake, certReq.marshal()) } helloDone := new(serverHelloDoneMsg) - finishedHash.Write(helloDone.marshal()) + hs.finishedHash.Write(helloDone.marshal()) c.writeRecord(recordTypeHandshake, helloDone.marshal()) var pub *rsa.PublicKey // public key for client auth, if any - msg, err = c.readHandshake() + msg, err := c.readHandshake() if err != nil { return err } + var ok bool // If we requested a client certificate, then the client must send a // certificate message, even if it's empty. if config.ClientAuth >= RequestClientCert { if certMsg, ok = msg.(*certificateMsg); !ok { return c.sendAlert(alertHandshakeFailure) } - finishedHash.Write(certMsg.marshal()) + hs.finishedHash.Write(certMsg.marshal()) if len(certMsg.certificates) == 0 { // The client didn't actually send a certificate @@ -198,54 +360,9 @@ FindCipherSuite: } } - certs := make([]*x509.Certificate, len(certMsg.certificates)) - for i, asn1Data := range certMsg.certificates { - if certs[i], err = x509.ParseCertificate(asn1Data); err != nil { - c.sendAlert(alertBadCertificate) - return errors.New("tls: failed to parse client certificate: " + err.Error()) - } - } - - if c.config.ClientAuth >= VerifyClientCertIfGiven && len(certs) > 0 { - opts := x509.VerifyOptions{ - Roots: c.config.ClientCAs, - CurrentTime: c.config.time(), - Intermediates: x509.NewCertPool(), - } - - for i, cert := range certs { - if i == 0 { - continue - } - opts.Intermediates.AddCert(cert) - } - - chains, err := certs[0].Verify(opts) - if err != nil { - c.sendAlert(alertBadCertificate) - return errors.New("tls: failed to verify client's certificate: " + err.Error()) - } - - ok := false - for _, ku := range certs[0].ExtKeyUsage { - if ku == x509.ExtKeyUsageClientAuth { - ok = true - break - } - } - if !ok { - c.sendAlert(alertHandshakeFailure) - return errors.New("tls: client's certificate's extended key usage doesn't permit it to be used for client authentication") - } - - c.verifiedChains = chains - } - - if len(certs) > 0 { - if pub, ok = certs[0].PublicKey.(*rsa.PublicKey); !ok { - return c.sendAlert(alertUnsupportedCertificate) - } - c.peerCertificates = certs + pub, err = hs.processCertsFromClient(certMsg.certificates) + if err != nil { + return err } msg, err = c.readHandshake() @@ -259,7 +376,7 @@ FindCipherSuite: if !ok { return c.sendAlert(alertUnexpectedMessage) } - finishedHash.Write(ckx.marshal()) + hs.finishedHash.Write(ckx.marshal()) // If we received a client cert in response to our certificate request message, // the client will send us a certificateVerifyMsg immediately after the @@ -278,15 +395,15 @@ FindCipherSuite: } digest := make([]byte, 0, 36) - digest = finishedHash.serverMD5.Sum(digest) - digest = finishedHash.serverSHA1.Sum(digest) + digest = hs.finishedHash.serverMD5.Sum(digest) + digest = hs.finishedHash.serverSHA1.Sum(digest) err = rsa.VerifyPKCS1v15(pub, crypto.MD5SHA1, digest, certVerify.signature) if err != nil { c.sendAlert(alertBadCertificate) return errors.New("could not validate signature of connection nonces: " + err.Error()) } - finishedHash.Write(certVerify.marshal()) + hs.finishedHash.Write(certVerify.marshal()) } preMasterSecret, err := keyAgreement.processClientKeyExchange(config, cert, ckx, c.vers) @@ -294,20 +411,38 @@ FindCipherSuite: c.sendAlert(alertHandshakeFailure) return err } + hs.masterSecret = masterFromPreMasterSecret(c.vers, preMasterSecret, hs.clientHello.random, hs.hello.random) + + return nil +} + +func (hs *serverHandshakeState) establishKeys() error { + c := hs.c - masterSecret, clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV := - keysFromPreMasterSecret(c.vers, preMasterSecret, clientHello.random, hello.random, suite.macLen, suite.keyLen, suite.ivLen) + clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV := + keysFromMasterSecret(c.vers, hs.masterSecret, hs.clientHello.random, hs.hello.random, hs.suite.macLen, hs.suite.keyLen, hs.suite.ivLen) - clientCipher := suite.cipher(clientKey, clientIV, true /* for reading */) - clientHash := suite.mac(c.vers, clientMAC) + clientCipher := hs.suite.cipher(clientKey, clientIV, true /* for reading */) + clientHash := hs.suite.mac(c.vers, clientMAC) c.in.prepareCipherSpec(c.vers, clientCipher, clientHash) + + serverCipher := hs.suite.cipher(serverKey, serverIV, false /* not for reading */) + serverHash := hs.suite.mac(c.vers, serverMAC) + c.out.prepareCipherSpec(c.vers, serverCipher, serverHash) + + return nil +} + +func (hs *serverHandshakeState) readFinished() error { + c := hs.c + c.readRecord(recordTypeChangeCipherSpec) if err := c.error(); err != nil { return err } - if hello.nextProtoNeg { - msg, err = c.readHandshake() + if hs.hello.nextProtoNeg { + msg, err := c.readHandshake() if err != nil { return err } @@ -315,11 +450,11 @@ FindCipherSuite: if !ok { return c.sendAlert(alertUnexpectedMessage) } - finishedHash.Write(nextProto.marshal()) + hs.finishedHash.Write(nextProto.marshal()) c.clientProtocol = nextProto.proto } - msg, err = c.readHandshake() + msg, err := c.readHandshake() if err != nil { return err } @@ -328,25 +463,142 @@ FindCipherSuite: return c.sendAlert(alertUnexpectedMessage) } - verify := finishedHash.clientSum(masterSecret) + verify := hs.finishedHash.clientSum(hs.masterSecret) if len(verify) != len(clientFinished.verifyData) || subtle.ConstantTimeCompare(verify, clientFinished.verifyData) != 1 { return c.sendAlert(alertHandshakeFailure) } - finishedHash.Write(clientFinished.marshal()) + hs.finishedHash.Write(clientFinished.marshal()) + return nil +} + +func (hs *serverHandshakeState) sendSessionTicket() error { + if !hs.hello.ticketSupported { + return nil + } + + c := hs.c + m := new(newSessionTicketMsg) + + var err error + state := sessionState{ + vers: c.vers, + cipherSuite: hs.suite.id, + masterSecret: hs.masterSecret, + certificates: hs.certsFromClient, + } + m.ticket, err = c.encryptTicket(&state) + if err != nil { + return err + } + + hs.finishedHash.Write(m.marshal()) + c.writeRecord(recordTypeHandshake, m.marshal()) + + return nil +} + +func (hs *serverHandshakeState) sendFinished() error { + c := hs.c - serverCipher := suite.cipher(serverKey, serverIV, false /* not for reading */) - serverHash := suite.mac(c.vers, serverMAC) - c.out.prepareCipherSpec(c.vers, serverCipher, serverHash) c.writeRecord(recordTypeChangeCipherSpec, []byte{1}) finished := new(finishedMsg) - finished.verifyData = finishedHash.serverSum(masterSecret) + finished.verifyData = hs.finishedHash.serverSum(hs.masterSecret) + hs.finishedHash.Write(finished.marshal()) c.writeRecord(recordTypeHandshake, finished.marshal()) - c.handshakeComplete = true - c.cipherSuite = suite.id + c.cipherSuite = hs.suite.id + + return nil +} + +// processCertsFromClient takes a chain of client certificates either from a +// Certificates message or from a sessionState and verifies them. It returns +// the public key of the leaf certificate. +func (hs *serverHandshakeState) processCertsFromClient(certificates [][]byte) (*rsa.PublicKey, error) { + c := hs.c + + hs.certsFromClient = certificates + certs := make([]*x509.Certificate, len(certificates)) + var err error + for i, asn1Data := range certificates { + if certs[i], err = x509.ParseCertificate(asn1Data); err != nil { + c.sendAlert(alertBadCertificate) + return nil, errors.New("tls: failed to parse client certificate: " + err.Error()) + } + } + + if c.config.ClientAuth >= VerifyClientCertIfGiven && len(certs) > 0 { + opts := x509.VerifyOptions{ + Roots: c.config.ClientCAs, + CurrentTime: c.config.time(), + Intermediates: x509.NewCertPool(), + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + } + + for _, cert := range certs[1:] { + opts.Intermediates.AddCert(cert) + } + + chains, err := certs[0].Verify(opts) + if err != nil { + c.sendAlert(alertBadCertificate) + return nil, errors.New("tls: failed to verify client's certificate: " + err.Error()) + } + + ok := false + for _, ku := range certs[0].ExtKeyUsage { + if ku == x509.ExtKeyUsageClientAuth { + ok = true + break + } + } + if !ok { + c.sendAlert(alertHandshakeFailure) + return nil, errors.New("tls: client's certificate's extended key usage doesn't permit it to be used for client authentication") + } + + c.verifiedChains = chains + } + + if len(certs) > 0 { + pub, ok := certs[0].PublicKey.(*rsa.PublicKey) + if !ok { + return nil, c.sendAlert(alertUnsupportedCertificate) + } + c.peerCertificates = certs + return pub, nil + } + + return nil, nil +} + +// tryCipherSuite returns a cipherSuite with the given id if that cipher suite +// is acceptable to use. +func (c *Conn) tryCipherSuite(id uint16, supportedCipherSuites []uint16, ellipticOk bool) *cipherSuite { + for _, supported := range supportedCipherSuites { + if id == supported { + var candidate *cipherSuite + + for _, s := range cipherSuites { + if s.id == id { + candidate = s + break + } + } + if candidate == nil { + continue + } + // Don't select a ciphersuite which we can't + // support for this client. + if candidate.elliptic && !ellipticOk { + continue + } + return candidate + } + } return nil } |