| // Copyright 2009 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package runner |
| |
| import ( |
| "bytes" |
| "crypto" |
| "crypto/ecdsa" |
| "crypto/ed25519" |
| "crypto/elliptic" |
| "crypto/rsa" |
| "crypto/subtle" |
| "crypto/x509" |
| "errors" |
| "fmt" |
| "io" |
| "math/big" |
| "net" |
| "slices" |
| "time" |
| |
| "boringssl.googlesource.com/boringssl/ssl/test/runner/hpke" |
| "golang.org/x/crypto/cryptobyte" |
| ) |
| |
| const echBadPayloadByte = 0xff |
| |
| type clientHandshakeState struct { |
| c *Conn |
| serverHello *serverHelloMsg |
| hello *clientHelloMsg |
| innerHello *clientHelloMsg |
| echHPKEContext *hpke.Context |
| suite *cipherSuite |
| finishedHash finishedHash |
| keyShares map[CurveID]kemImplementation |
| masterSecret []byte |
| session *ClientSessionState |
| finishedBytes []byte |
| peerPublicKey crypto.PublicKey |
| } |
| |
| func mapClientHelloVersion(vers uint16, isDTLS bool) uint16 { |
| if !isDTLS { |
| return vers |
| } |
| |
| switch vers { |
| case VersionTLS12: |
| return VersionDTLS12 |
| case VersionTLS10: |
| return VersionDTLS10 |
| } |
| |
| panic("Unknown ClientHello version.") |
| } |
| |
| // replaceClientHello returns a new clientHelloMsg which serializes to |in|, but |
| // with key shares copied from |hello|. This allows sending an exact |
| // externally-specified ClientHello in tests. However, we use |hello|'s key |
| // shares. This ensures we have the private keys to complete the handshake. Note |
| // this function does not update internal handshake state, so the test must be |
| // configured compatibly with |in|. |
| func replaceClientHello(hello *clientHelloMsg, in []byte) (*clientHelloMsg, error) { |
| copied := slices.Clone(in) |
| newHello := new(clientHelloMsg) |
| if !newHello.unmarshal(copied) { |
| return nil, errors.New("tls: invalid ClientHello") |
| } |
| |
| // Replace |newHellos|'s key shares with those of |hello|. For simplicity, |
| // we require their lengths match, which is satisfied by matching the |
| // DefaultCurves setting to the selection in the replacement ClientHello. |
| bb := cryptobyte.NewBuilder(nil) |
| hello.marshalKeyShares(bb) |
| keyShares, err := bb.Bytes() |
| if err != nil { |
| return nil, err |
| } |
| if len(keyShares) != len(newHello.keySharesRaw) { |
| return nil, errors.New("tls: ClientHello key share length is inconsistent with DefaultCurves setting") |
| } |
| // |newHello.keySharesRaw| aliases |copied|. |
| copy(newHello.keySharesRaw, keyShares) |
| newHello.keyShares = hello.keyShares |
| |
| return newHello, nil |
| } |
| |
| func (c *Conn) clientHandshake() error { |
| if c.config == nil { |
| c.config = defaultConfig() |
| } |
| |
| if len(c.config.ServerName) == 0 && !c.config.InsecureSkipVerify { |
| return errors.New("tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config") |
| } |
| |
| c.sendHandshakeSeq = 0 |
| c.recvHandshakeSeq = 0 |
| |
| hs := &clientHandshakeState{ |
| c: c, |
| keyShares: make(map[CurveID]kemImplementation), |
| } |
| |
| // Pick a session to resume. |
| var session *ClientSessionState |
| var cacheKey string |
| sessionCache := c.config.ClientSessionCache |
| if sessionCache != nil { |
| // 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 |
| |
| // Check that the ciphersuite/version used for the |
| // previous session are still valid. |
| cipherSuiteOk := false |
| if candidateSession.vers <= VersionTLS12 { |
| for _, id := range c.config.cipherSuites() { |
| if id == candidateSession.cipherSuite.id { |
| cipherSuiteOk = true |
| break |
| } |
| } |
| } else { |
| // TLS 1.3 allows the cipher to change on |
| // resumption. |
| cipherSuiteOk = true |
| } |
| |
| _, versOk := c.config.isSupportedVersion(candidateSession.wireVersion, c.isDTLS) |
| if ticketOk && versOk && cipherSuiteOk { |
| session = candidateSession |
| hs.session = session |
| } |
| } |
| } |
| |
| // Set up ECH parameters. |
| var err error |
| var earlyHello *clientHelloMsg |
| if c.config.ClientECHConfig != nil { |
| if c.config.ClientECHConfig.KEM != hpke.X25519WithHKDFSHA256 { |
| return errors.New("tls: unsupported KEM type in ECHConfig") |
| } |
| |
| echCipherSuite, ok := chooseECHCipherSuite(c.config.ClientECHConfig, c.config) |
| if !ok { |
| return errors.New("tls: did not find compatible cipher suite in ECHConfig") |
| } |
| |
| info := []byte("tls ech\x00") |
| info = append(info, c.config.ClientECHConfig.Raw...) |
| |
| var echEnc []byte |
| hs.echHPKEContext, echEnc, err = hpke.SetupBaseSenderX25519(echCipherSuite.KDF, echCipherSuite.AEAD, c.config.ClientECHConfig.PublicKey, info, nil) |
| if err != nil { |
| return errors.New("tls: ech: failed to set up client's HPKE sender context") |
| } |
| |
| hs.innerHello, err = hs.createClientHello(nil, nil) |
| if err != nil { |
| return err |
| } |
| hs.hello, err = hs.createClientHello(hs.innerHello, echEnc) |
| if err != nil { |
| return err |
| } |
| earlyHello = hs.innerHello |
| } else { |
| hs.hello, err = hs.createClientHello(nil, nil) |
| if err != nil { |
| return err |
| } |
| earlyHello = hs.hello |
| } |
| |
| if len(earlyHello.pskIdentities) == 0 || c.config.Bugs.SendEarlyData == nil { |
| earlyHello = nil |
| } |
| |
| if c.config.Bugs.SendV2ClientHello { |
| hs.hello.isV2ClientHello = true |
| |
| // The V2ClientHello "challenge" field is variable-length and is |
| // left-padded or truncated to become the SSL3/TLS random. |
| challengeLength := c.config.Bugs.V2ClientHelloChallengeLength |
| if challengeLength == 0 { |
| challengeLength = len(hs.hello.random) |
| } |
| if challengeLength <= len(hs.hello.random) { |
| skip := len(hs.hello.random) - challengeLength |
| clear(hs.hello.random[:skip]) |
| hs.hello.v2Challenge = hs.hello.random[skip:] |
| } else { |
| hs.hello.v2Challenge = make([]byte, challengeLength) |
| copy(hs.hello.v2Challenge, hs.hello.random) |
| if _, err := io.ReadFull(c.config.rand(), hs.hello.v2Challenge[len(hs.hello.random):]); err != nil { |
| c.sendAlert(alertInternalError) |
| return fmt.Errorf("tls: short read from Rand: %s", err) |
| } |
| } |
| |
| c.writeV2Record(hs.hello.marshal()) |
| } else { |
| helloBytes := hs.hello.marshal() |
| var appendToHello byte |
| if c.config.Bugs.PartialClientFinishedWithClientHello { |
| appendToHello = typeFinished |
| } else if c.config.Bugs.PartialEndOfEarlyDataWithClientHello { |
| appendToHello = typeEndOfEarlyData |
| } else if c.config.Bugs.PartialSecondClientHelloAfterFirst { |
| appendToHello = typeClientHello |
| } else if c.config.Bugs.PartialClientKeyExchangeWithClientHello { |
| appendToHello = typeClientKeyExchange |
| } |
| if appendToHello != 0 { |
| c.writeRecord(recordTypeHandshake, append(helloBytes[:len(helloBytes):len(helloBytes)], appendToHello)) |
| } else { |
| c.writeRecord(recordTypeHandshake, helloBytes) |
| } |
| } |
| c.flushHandshake() |
| |
| if err := c.simulatePacketLoss(nil); err != nil { |
| return err |
| } |
| if c.config.Bugs.SendEarlyAlert { |
| c.sendAlert(alertHandshakeFailure) |
| } |
| if c.config.Bugs.SendFakeEarlyDataLength > 0 { |
| c.sendFakeEarlyData(c.config.Bugs.SendFakeEarlyDataLength) |
| } |
| |
| // Derive early write keys and set Conn state to allow early writes. |
| if earlyHello != nil { |
| finishedHash := newFinishedHash(session.wireVersion, c.isDTLS, session.cipherSuite) |
| finishedHash.addEntropy(session.secret) |
| finishedHash.Write(earlyHello.marshal()) |
| |
| if !c.config.Bugs.SkipChangeCipherSpec { |
| c.wireVersion = session.wireVersion |
| c.vers = VersionTLS13 |
| c.writeRecord(recordTypeChangeCipherSpec, []byte{1}) |
| c.wireVersion = 0 |
| c.vers = 0 |
| } |
| |
| earlyTrafficSecret := finishedHash.deriveSecret(earlyTrafficLabel) |
| c.earlyExporterSecret = finishedHash.deriveSecret(earlyExporterLabel) |
| |
| c.useOutTrafficSecret(uint16(encryptionEarlyData), session.wireVersion, session.cipherSuite, earlyTrafficSecret) |
| for _, earlyData := range c.config.Bugs.SendEarlyData { |
| if _, err := c.writeRecord(recordTypeApplicationData, earlyData); err != nil { |
| return err |
| } |
| } |
| } |
| |
| msg, err := c.readHandshake() |
| if err != nil { |
| return err |
| } |
| |
| if c.isDTLS { |
| helloVerifyRequest, ok := msg.(*helloVerifyRequestMsg) |
| if ok { |
| if helloVerifyRequest.vers != VersionDTLS10 { |
| // Per RFC 6347, the version field in |
| // HelloVerifyRequest SHOULD be always DTLS |
| // 1.0. Enforce this for testing purposes. |
| return errors.New("dtls: bad HelloVerifyRequest version") |
| } |
| |
| hs.hello.raw = nil |
| hs.hello.cookie = helloVerifyRequest.cookie |
| c.writeRecord(recordTypeHandshake, hs.hello.marshal()) |
| c.flushHandshake() |
| |
| if err := c.simulatePacketLoss(nil); err != nil { |
| return err |
| } |
| msg, err = c.readHandshake() |
| if err != nil { |
| return err |
| } |
| } |
| } |
| |
| // The first message is either ServerHello or HelloRetryRequest, either of |
| // which determines the version and cipher suite. |
| var serverWireVersion, suiteID uint16 |
| switch m := msg.(type) { |
| case *helloRetryRequestMsg: |
| serverWireVersion = m.vers |
| suiteID = m.cipherSuite |
| case *serverHelloMsg: |
| serverWireVersion = m.vers |
| suiteID = m.cipherSuite |
| default: |
| c.sendAlert(alertUnexpectedMessage) |
| return fmt.Errorf("tls: received unexpected message of type %T when waiting for HelloRetryRequest or ServerHello", msg) |
| } |
| |
| serverVersion, ok := c.config.isSupportedVersion(serverWireVersion, c.isDTLS) |
| if !ok { |
| c.sendAlert(alertProtocolVersion) |
| return fmt.Errorf("tls: server selected unsupported protocol version %x", c.vers) |
| } |
| c.wireVersion = serverWireVersion |
| c.vers = serverVersion |
| c.haveVers = true |
| |
| // We only implement enough of SSL 3.0 to test that the server doesn't: |
| // we can send a ClientHello and attempt to read a ServerHello. The server |
| // should respond with a protocol_version alert and not get this far. |
| if c.vers == VersionSSL30 { |
| return errors.New("tls: server selected SSL 3.0") |
| } |
| |
| cipherSuites := hs.hello.cipherSuites |
| if hs.innerHello != nil && c.config.Bugs.MinimalClientHelloOuter { |
| // hs.hello has a placeholder list of ciphers if testing with |
| // MinimalClientHelloOuter, so we use hs.innerHello instead. (We do not |
| // attempt to support actual different cipher suite preferences between |
| // the two.) |
| cipherSuites = hs.innerHello.cipherSuites |
| } |
| hs.suite = mutualCipherSuite(cipherSuites, suiteID) |
| if hs.suite == nil { |
| c.sendAlert(alertHandshakeFailure) |
| return fmt.Errorf("tls: server selected an unsupported cipher suite") |
| } |
| |
| hs.finishedHash = newFinishedHash(c.wireVersion, c.isDTLS, hs.suite) |
| hs.finishedHash.WriteHandshake(hs.hello.marshal(), hs.c.sendHandshakeSeq-1) |
| |
| if c.vers >= VersionTLS13 { |
| if err := hs.doTLS13Handshake(msg); err != nil { |
| return err |
| } |
| } else { |
| hs.serverHello, ok = msg.(*serverHelloMsg) |
| if !ok { |
| c.sendAlert(alertUnexpectedMessage) |
| return unexpectedMessageError(hs.serverHello, msg) |
| } |
| if isAllZero(hs.serverHello.random) { |
| // If the server forgets to fill in the server random, it will |
| // likely be all zero. |
| return errors.New("tls: ServerHello random was all zero") |
| } |
| |
| hs.writeServerHash(hs.serverHello.marshal()) |
| if c.config.Bugs.EarlyChangeCipherSpec > 0 { |
| hs.establishKeys() |
| c.writeRecord(recordTypeChangeCipherSpec, []byte{1}) |
| } |
| |
| if hs.serverHello.compressionMethod != compressionNone { |
| c.sendAlert(alertUnexpectedMessage) |
| return errors.New("tls: server selected unsupported compression format") |
| } |
| |
| err = hs.processServerExtensions(&hs.serverHello.extensions) |
| if err != nil { |
| return err |
| } |
| |
| isResume, err := hs.processServerHello() |
| if err != nil { |
| return err |
| } |
| |
| if isResume { |
| if c.config.Bugs.EarlyChangeCipherSpec == 0 { |
| if err := hs.establishKeys(); err != nil { |
| return err |
| } |
| } |
| if err := hs.readSessionTicket(); err != nil { |
| return err |
| } |
| if err := hs.readFinished(c.firstFinished[:]); err != nil { |
| return err |
| } |
| if err := hs.sendFinished(nil, isResume); err != nil { |
| return err |
| } |
| } else { |
| if err := hs.doFullHandshake(); err != nil { |
| return err |
| } |
| if err := hs.establishKeys(); err != nil { |
| return err |
| } |
| if err := hs.sendFinished(c.firstFinished[:], isResume); 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.sendHandshakeSeq-- |
| c.writeRecord(recordTypeHandshake, hs.finishedBytes) |
| c.flushHandshake() |
| }); err != nil { |
| return err |
| } |
| if err := hs.readSessionTicket(); err != nil { |
| return err |
| } |
| if err := hs.readFinished(nil); err != nil { |
| return err |
| } |
| } |
| |
| if sessionCache != nil && hs.session != nil && session != hs.session { |
| if c.config.Bugs.RequireSessionTickets && len(hs.session.sessionTicket) == 0 { |
| return errors.New("tls: new session used session IDs instead of tickets") |
| } |
| if c.config.Bugs.RequireSessionIDs && len(hs.session.sessionID) == 0 { |
| return errors.New("tls: new session used session tickets instead of IDs") |
| } |
| sessionCache.Put(cacheKey, hs.session) |
| } |
| |
| c.didResume = isResume |
| c.exporterSecret = hs.masterSecret |
| } |
| |
| c.handshakeComplete = true |
| c.cipherSuite = hs.suite |
| copy(c.clientRandom[:], hs.hello.random) |
| copy(c.serverRandom[:], hs.serverHello.random) |
| |
| return nil |
| } |
| |
| func chooseECHCipherSuite(echConfig *ECHConfig, config *Config) (HPKECipherSuite, bool) { |
| if echConfig.KEM != hpke.X25519WithHKDFSHA256 { |
| return HPKECipherSuite{}, false |
| } |
| |
| for _, suite := range config.echCipherSuitePreferences() { |
| if config.Bugs.IgnoreECHConfigCipherPreferences || slices.Contains(echConfig.CipherSuites, suite) { |
| return suite, true |
| } |
| } |
| return HPKECipherSuite{}, false |
| } |
| |
| // createClientHello creates a new ClientHello message. If |innerHello| is not |
| // nil, this is a ClientHelloOuter that should contain an encrypted |innerHello| |
| // with |echEnc| as the encapsulated public key. Otherwise, the ClientHello |
| // should reflect the connection's true preferences. |
| func (hs *clientHandshakeState) createClientHello(innerHello *clientHelloMsg, echEnc []byte) (*clientHelloMsg, error) { |
| c := hs.c |
| nextProtosLength := 0 |
| for _, proto := range c.config.NextProtos { |
| if l := len(proto); l > 255 { |
| return nil, errors.New("tls: invalid NextProtos value") |
| } else { |
| nextProtosLength += 1 + l |
| } |
| } |
| if nextProtosLength > 0xffff { |
| return nil, errors.New("tls: NextProtos values too large") |
| } |
| |
| quicTransportParams := c.config.QUICTransportParams |
| quicTransportParamsLegacy := c.config.QUICTransportParams |
| if !c.config.QUICTransportParamsUseLegacyCodepoint.IncludeStandard() { |
| quicTransportParams = nil |
| } |
| if !c.config.QUICTransportParamsUseLegacyCodepoint.IncludeLegacy() { |
| quicTransportParamsLegacy = nil |
| } |
| |
| isInner := innerHello == nil && hs.echHPKEContext != nil |
| |
| minVersion := c.config.minVersion(c.isDTLS) |
| maxVersion := c.config.maxVersion(c.isDTLS) |
| // The ClientHelloInner may not offer TLS 1.2 or below. |
| requireTLS13 := isInner && !c.config.Bugs.AllowTLS12InClientHelloInner |
| if requireTLS13 && minVersion < VersionTLS13 { |
| minVersion = VersionTLS13 |
| if minVersion > maxVersion { |
| return nil, errors.New("tls: ECH requires TLS 1.3") |
| } |
| } |
| |
| hello := &clientHelloMsg{ |
| isDTLS: c.isDTLS, |
| compressionMethods: []uint8{compressionNone}, |
| random: make([]byte, 32), |
| ocspStapling: !c.config.Bugs.NoOCSPStapling, |
| sctListSupported: !c.config.Bugs.NoSignedCertificateTimestamps, |
| supportedCurves: c.config.curvePreferences(), |
| supportedPoints: []uint8{pointFormatUncompressed}, |
| nextProtoNeg: len(c.config.NextProtos) > 0, |
| secureRenegotiation: []byte{}, |
| alpnProtocols: c.config.NextProtos, |
| quicTransportParams: quicTransportParams, |
| quicTransportParamsLegacy: quicTransportParamsLegacy, |
| duplicateExtension: c.config.Bugs.DuplicateExtension, |
| channelIDSupported: c.config.ChannelID != nil, |
| extendedMasterSecret: maxVersion >= VersionTLS10, |
| srtpProtectionProfiles: c.config.SRTPProtectionProfiles, |
| srtpMasterKeyIdentifier: c.config.Bugs.SRTPMasterKeyIdentifier, |
| customExtension: c.config.Bugs.CustomExtension, |
| omitExtensions: c.config.Bugs.OmitExtensions, |
| emptyExtensions: c.config.Bugs.EmptyExtensions, |
| delegatedCredential: c.config.DelegatedCredentialAlgorithms, |
| } |
| |
| // Translate the bugs that modify ClientHello extension order into a |
| // list of prefix extensions. The marshal function will try these |
| // extensions before any others, followed by any remaining extensions in |
| // the default order. |
| if c.config.Bugs.PSKBinderFirst && !c.config.Bugs.OnlyCorruptSecondPSKBinder { |
| hello.prefixExtensions = append(hello.prefixExtensions, extensionPreSharedKey) |
| } |
| if c.config.Bugs.SwapNPNAndALPN { |
| hello.prefixExtensions = append(hello.prefixExtensions, extensionALPN) |
| hello.prefixExtensions = append(hello.prefixExtensions, extensionNextProtoNeg) |
| } |
| |
| // Configure ech_outer_extensions. |
| if isInner { |
| hello.outerExtensions = c.config.ECHOuterExtensions |
| // If |OnlyCompressSecondClientHelloInner| is set, we still configure |
| // |hello.outerExtensions| for ordering, so that we do not introduce an |
| // unsolicited change across HelloRetryRequest. |
| hello.reorderOuterExtensionsWithoutCompressing = c.config.Bugs.OnlyCompressSecondClientHelloInner |
| } else { |
| // Compressed extensions must appear in the same relative order between |
| // ClientHelloInner and ClientHelloOuter. For simplicity, we default to |
| // forcing their order to match, but the caller can override this with |
| // either valid or invalid explicit orders. |
| if c.config.Bugs.ECHOuterExtensionOrder != nil { |
| hello.prefixExtensions = append(hello.prefixExtensions, c.config.Bugs.ECHOuterExtensionOrder...) |
| } else { |
| hello.prefixExtensions = append(hello.prefixExtensions, c.config.ECHOuterExtensions...) |
| } |
| } |
| |
| if maxVersion >= VersionTLS13 { |
| hello.vers = mapClientHelloVersion(VersionTLS12, c.isDTLS) |
| if !c.config.Bugs.OmitSupportedVersions { |
| hello.supportedVersions = c.config.supportedVersions(c.isDTLS, requireTLS13) |
| } |
| hello.pskKEModes = []byte{pskDHEKEMode} |
| } else { |
| hello.vers = mapClientHelloVersion(maxVersion, c.isDTLS) |
| } |
| |
| if c.config.Bugs.SendClientVersion != 0 { |
| hello.vers = c.config.Bugs.SendClientVersion |
| } |
| |
| if len(c.config.Bugs.SendSupportedVersions) > 0 { |
| hello.supportedVersions = c.config.Bugs.SendSupportedVersions |
| } |
| |
| if innerHello != nil { |
| hello.serverName = c.config.ClientECHConfig.PublicName |
| } else { |
| hello.serverName = c.config.ServerName |
| } |
| |
| if !isInner && c.config.Bugs.OmitPublicName { |
| hello.serverName = "" |
| } |
| |
| disableEMS := c.config.Bugs.NoExtendedMasterSecret |
| if c.cipherSuite != nil { |
| disableEMS = c.config.Bugs.NoExtendedMasterSecretOnRenegotiation |
| } |
| |
| if disableEMS { |
| hello.extendedMasterSecret = false |
| } |
| |
| if c.config.Bugs.NoSupportedCurves { |
| hello.supportedCurves = nil |
| } |
| |
| if c.config.Bugs.SendPSKKeyExchangeModes != nil { |
| hello.pskKEModes = c.config.Bugs.SendPSKKeyExchangeModes |
| } |
| |
| if c.config.Bugs.SendCompressionMethods != nil { |
| hello.compressionMethods = c.config.Bugs.SendCompressionMethods |
| } |
| |
| if c.config.Bugs.SendSupportedPointFormats != nil { |
| hello.supportedPoints = c.config.Bugs.SendSupportedPointFormats |
| } |
| |
| if len(c.clientVerify) > 0 && !c.config.Bugs.EmptyRenegotiationInfo { |
| if c.config.Bugs.BadRenegotiationInfo { |
| hello.secureRenegotiation = append(hello.secureRenegotiation, c.clientVerify...) |
| hello.secureRenegotiation[0] ^= 0x80 |
| } else { |
| hello.secureRenegotiation = c.clientVerify |
| } |
| } |
| |
| if c.config.Bugs.DuplicateCompressedCertAlgs { |
| hello.compressedCertAlgs = []uint16{1, 1} |
| } else if len(c.config.CertCompressionAlgs) > 0 { |
| hello.compressedCertAlgs = make([]uint16, 0, len(c.config.CertCompressionAlgs)) |
| for id := range c.config.CertCompressionAlgs { |
| hello.compressedCertAlgs = append(hello.compressedCertAlgs, uint16(id)) |
| } |
| } |
| |
| if c.noRenegotiationInfo() { |
| hello.secureRenegotiation = nil |
| } |
| |
| if c.config.ALPSUseNewCodepoint.IncludeNew() { |
| for protocol := range c.config.ApplicationSettings { |
| hello.alpsProtocols = append(hello.alpsProtocols, protocol) |
| } |
| } |
| if c.config.ALPSUseNewCodepoint.IncludeOld() { |
| for protocol := range c.config.ApplicationSettings { |
| hello.alpsProtocolsOld = append(hello.alpsProtocolsOld, protocol) |
| } |
| } |
| |
| if maxVersion >= VersionTLS13 { |
| // Use the same key shares between ClientHelloInner and ClientHelloOuter. |
| if innerHello != nil { |
| hello.hasKeyShares = innerHello.hasKeyShares |
| hello.keyShares = innerHello.keyShares |
| } else { |
| hello.hasKeyShares = true |
| hello.trailingKeyShareData = c.config.Bugs.TrailingKeyShareData |
| curvesToSend := c.config.defaultCurves() |
| for _, curveID := range hello.supportedCurves { |
| if !curvesToSend[curveID] { |
| continue |
| } |
| kem, ok := kemForCurveID(curveID, c.config) |
| if !ok { |
| continue |
| } |
| publicKey, err := kem.generate(c.config) |
| if err != nil { |
| return nil, err |
| } |
| |
| if c.config.Bugs.SendCurve != 0 { |
| curveID = c.config.Bugs.SendCurve |
| } |
| |
| hello.keyShares = append(hello.keyShares, keyShareEntry{ |
| group: curveID, |
| keyExchange: publicKey, |
| }) |
| hs.keyShares[curveID] = kem |
| |
| if c.config.Bugs.DuplicateKeyShares { |
| hello.keyShares = append(hello.keyShares, hello.keyShares[len(hello.keyShares)-1]) |
| } |
| } |
| |
| if c.config.Bugs.MissingKeyShare { |
| hello.hasKeyShares = false |
| } |
| } |
| } |
| |
| possibleCipherSuites := c.config.cipherSuites() |
| hello.cipherSuites = make([]uint16, 0, len(possibleCipherSuites)) |
| |
| for _, suiteID := range possibleCipherSuites { |
| suite := cipherSuiteFromID(suiteID) |
| if suite == nil { |
| continue |
| } |
| // Don't advertise TLS 1.2-only cipher suites unless |
| // we're attempting TLS 1.2. |
| if maxVersion < VersionTLS12 && suite.flags&suiteTLS12 != 0 { |
| continue |
| } |
| hello.cipherSuites = append(hello.cipherSuites, suiteID) |
| } |
| |
| if c.config.Bugs.AdvertiseAllConfiguredCiphers { |
| hello.cipherSuites = possibleCipherSuites |
| } |
| |
| if c.config.Bugs.SendRenegotiationSCSV { |
| hello.cipherSuites = append(hello.cipherSuites, renegotiationSCSV) |
| } |
| |
| if c.config.Bugs.SendFallbackSCSV { |
| hello.cipherSuites = append(hello.cipherSuites, fallbackSCSV) |
| } |
| |
| _, err := io.ReadFull(c.config.rand(), hello.random) |
| if err != nil { |
| c.sendAlert(alertInternalError) |
| return nil, errors.New("tls: short read from Rand: " + err.Error()) |
| } |
| |
| if maxVersion >= VersionTLS12 && !c.config.Bugs.NoSignatureAlgorithms { |
| hello.signatureAlgorithms = c.config.verifySignatureAlgorithms() |
| } |
| |
| if c.config.ClientSessionCache != nil { |
| hello.ticketSupported = !c.config.SessionTicketsDisabled |
| } |
| |
| session := hs.session |
| |
| // ClientHelloOuter cannot offer sessions. |
| if innerHello != nil && !c.config.Bugs.OfferSessionInClientHelloOuter { |
| session = nil |
| } |
| |
| if session != nil && c.config.time().Before(session.ticketExpiration) { |
| ticket := session.sessionTicket |
| if c.config.Bugs.FilterTicket != nil && len(ticket) > 0 { |
| // Copy the ticket so FilterTicket may act in-place. |
| ticket = make([]byte, len(session.sessionTicket)) |
| copy(ticket, session.sessionTicket) |
| |
| ticket, err = c.config.Bugs.FilterTicket(ticket) |
| if err != nil { |
| return nil, err |
| } |
| } |
| |
| if session.vers >= VersionTLS13 || c.config.Bugs.SendBothTickets { |
| // TODO(nharper): Support sending more |
| // than one PSK identity. |
| ticketAge := uint32(c.config.time().Sub(session.ticketCreationTime) / time.Millisecond) |
| if c.config.Bugs.SendTicketAge != 0 { |
| ticketAge = uint32(c.config.Bugs.SendTicketAge / time.Millisecond) |
| } |
| psk := pskIdentity{ |
| ticket: ticket, |
| obfuscatedTicketAge: session.ticketAgeAdd + ticketAge, |
| } |
| hello.pskIdentities = []pskIdentity{psk} |
| |
| if c.config.Bugs.ExtraPSKIdentity { |
| hello.pskIdentities = append(hello.pskIdentities, psk) |
| } |
| } |
| |
| if session.vers < VersionTLS13 || c.config.Bugs.SendBothTickets { |
| if ticket != nil { |
| hello.sessionTicket = ticket |
| // A random session ID is used to detect when the |
| // server accepted the ticket and is resuming a session |
| // (see RFC 5077). |
| sessionIDLen := 16 |
| if c.config.Bugs.TicketSessionIDLength != 0 { |
| sessionIDLen = c.config.Bugs.TicketSessionIDLength |
| } |
| if c.config.Bugs.EmptyTicketSessionID { |
| sessionIDLen = 0 |
| } |
| hello.sessionID = make([]byte, sessionIDLen) |
| if _, err := io.ReadFull(c.config.rand(), hello.sessionID); err != nil { |
| c.sendAlert(alertInternalError) |
| return nil, errors.New("tls: short read from Rand: " + err.Error()) |
| } |
| } else { |
| hello.sessionID = session.sessionID |
| } |
| } |
| } |
| |
| if innerHello == nil { |
| // Request compatibility mode from the client by sending a fake session |
| // ID. Although BoringSSL always enables compatibility mode, other |
| // implementations make it conditional on the ClientHello. We test |
| // BoringSSL's expected behavior with SendClientHelloSessionID. |
| if len(hello.sessionID) == 0 && maxVersion >= VersionTLS13 { |
| hello.sessionID = make([]byte, 32) |
| if _, err := io.ReadFull(c.config.rand(), hello.sessionID); err != nil { |
| c.sendAlert(alertInternalError) |
| return nil, errors.New("tls: short read from Rand: " + err.Error()) |
| } |
| } |
| if c.config.Bugs.MockQUICTransport != nil && !c.config.Bugs.CompatModeWithQUIC { |
| hello.sessionID = []byte{} |
| } |
| if c.config.Bugs.SendClientHelloSessionID != nil { |
| hello.sessionID = c.config.Bugs.SendClientHelloSessionID |
| } |
| } else { |
| // ClientHelloOuter's session ID is copied from ClientHelloINnner. |
| hello.sessionID = innerHello.sessionID |
| } |
| |
| if c.config.Bugs.SendCipherSuites != nil { |
| hello.cipherSuites = c.config.Bugs.SendCipherSuites |
| } |
| |
| if innerHello == nil { |
| if len(hello.pskIdentities) > 0 && c.config.Bugs.SendEarlyData != nil { |
| hello.hasEarlyData = true |
| } |
| if c.config.Bugs.SendFakeEarlyDataLength > 0 { |
| hello.hasEarlyData = true |
| } |
| if c.config.Bugs.OmitEarlyDataExtension { |
| hello.hasEarlyData = false |
| } |
| } else { |
| hello.hasEarlyData = innerHello.hasEarlyData |
| } |
| |
| if (isInner && !c.config.Bugs.OmitECHInner) || c.config.Bugs.AlwaysSendECHInner { |
| hello.echInner = true |
| hello.invalidECHInner = c.config.Bugs.SendInvalidECHInner |
| } |
| |
| if innerHello != nil { |
| if err := hs.encryptClientHello(hello, innerHello, c.config.ClientECHConfig.ConfigID, echEnc); err != nil { |
| return nil, err |
| } |
| if c.config.Bugs.CorruptEncryptedClientHello { |
| if c.config.Bugs.NullAllCiphers { |
| hello.echOuter.payload = []byte{echBadPayloadByte} |
| } else { |
| hello.echOuter.payload[0] ^= 1 |
| } |
| } |
| } |
| |
| // PSK binders and ECH both must be computed last because they incorporate |
| // the rest of the ClientHello and conflict. ECH resolves this by forbidding |
| // clients from offering PSKs on ClientHelloOuter, but we still need to test |
| // servers handle it correctly so they tolerate GREASE. In other cases, we |
| // expect the server to reject ECH, so we put PSK last. Note this renders |
| // ECH undecryptable. |
| if len(hello.pskIdentities) > 0 { |
| version := session.wireVersion |
| // We may have a pre-1.3 session if SendBothTickets is set. |
| if session.vers < VersionTLS13 { |
| version = VersionTLS13 |
| if c.isDTLS { |
| version = VersionDTLS125Experimental |
| } |
| } |
| generatePSKBinders(version, c.isDTLS, hello, session, nil, nil, c.config) |
| } |
| |
| if c.config.Bugs.SendClientHelloWithFixes != nil { |
| hello, err = replaceClientHello(hello, c.config.Bugs.SendClientHelloWithFixes) |
| if err != nil { |
| return nil, err |
| } |
| } |
| |
| return hello, nil |
| } |
| |
| // encryptClientHello encrypts |innerHello| using the specified HPKE context and |
| // adds the extension to |hello|. |
| func (hs *clientHandshakeState) encryptClientHello(hello, innerHello *clientHelloMsg, configID uint8, enc []byte) error { |
| c := hs.c |
| |
| if c.config.Bugs.MinimalClientHelloOuter { |
| *hello = clientHelloMsg{ |
| vers: VersionTLS12, |
| random: hello.random, |
| sessionID: hello.sessionID, |
| cipherSuites: []uint16{0x0a0a}, |
| compressionMethods: hello.compressionMethods, |
| } |
| } |
| |
| if c.config.Bugs.TruncateClientECHEnc { |
| enc = enc[:1] |
| } |
| |
| encodedInner := innerHello.marshalForEncodedInner() |
| padding := make([]byte, c.config.Bugs.ClientECHPadding) |
| if c.config.Bugs.BadClientECHPadding { |
| padding[0] = 1 |
| } |
| encodedInner = append(encodedInner, padding...) |
| |
| // Encode ClientHelloOuter with a placeholder payload string. |
| payloadLength := len(encodedInner) |
| if !c.config.Bugs.NullAllCiphers { |
| payloadLength += hs.echHPKEContext.Overhead() |
| } |
| hello.echOuter = &echClientOuter{ |
| kdfID: hs.echHPKEContext.KDF(), |
| aeadID: hs.echHPKEContext.AEAD(), |
| configID: configID, |
| enc: enc, |
| payload: make([]byte, payloadLength), |
| } |
| aad := hello.marshal()[4:] // Remove message header |
| |
| hello.raw = nil |
| hello.echOuter.payload = hs.echHPKEContext.Seal(encodedInner, aad) |
| if c.config.Bugs.NullAllCiphers { |
| hello.echOuter.payload = encodedInner |
| } |
| |
| if c.config.Bugs.RecordClientHelloInner != nil { |
| if err := c.config.Bugs.RecordClientHelloInner(encodedInner, hello.marshal()[4:]); err != nil { |
| return err |
| } |
| // ECH is normally the last extension added to |hello|, but, when |
| // OfferSessionInClientHelloOuter is enabled, we may modify it again. |
| hello.raw = nil |
| } |
| |
| return nil |
| } |
| |
| func (hs *clientHandshakeState) checkECHConfirmation(msg any, hello *clientHelloMsg, finishedHash *finishedHash) bool { |
| var offset int |
| var raw, label []byte |
| if hrr, ok := msg.(*helloRetryRequestMsg); ok { |
| if hrr.echConfirmationOffset == 0 { |
| return false |
| } |
| raw = hrr.raw |
| label = echAcceptConfirmationHRRLabel |
| offset = hrr.echConfirmationOffset |
| } else { |
| raw = msg.(*serverHelloMsg).raw |
| label = echAcceptConfirmationLabel |
| offset = 4 + 2 + 32 - echAcceptConfirmationLength |
| } |
| |
| withZeros := slices.Clone(raw) |
| clear(withZeros[offset : offset+echAcceptConfirmationLength]) |
| |
| confirmation := finishedHash.echAcceptConfirmation(hello.random, label, withZeros) |
| return bytes.Equal(confirmation, raw[offset:offset+echAcceptConfirmationLength]) |
| } |
| |
| func (hs *clientHandshakeState) doTLS13Handshake(msg any) error { |
| c := hs.c |
| |
| // The first message may be a ServerHello or HelloRetryRequest. |
| helloRetryRequest, haveHelloRetryRequest := msg.(*helloRetryRequestMsg) |
| if haveHelloRetryRequest { |
| hs.finishedHash.UpdateForHelloRetryRequest() |
| } |
| |
| // Determine whether the server accepted ECH and drop the unnecessary |
| // transcript. |
| if hs.innerHello != nil { |
| innerFinishedHash := newFinishedHash(c.wireVersion, c.isDTLS, hs.suite) |
| innerFinishedHash.WriteHandshake(hs.innerHello.marshal(), hs.c.sendHandshakeSeq-1) |
| if haveHelloRetryRequest { |
| innerFinishedHash.UpdateForHelloRetryRequest() |
| } |
| if hs.checkECHConfirmation(msg, hs.innerHello, &innerFinishedHash) { |
| c.echAccepted = true |
| // Replace the transcript. For now, leave hs.hello and hs.innerHello |
| // as-is. HelloRetryRequest requires both be available. |
| hs.finishedHash = innerFinishedHash |
| } |
| } else { |
| // When not offering ECH, test that the backend server does not (or does) |
| // send a confirmation as expected. |
| confirmed := hs.checkECHConfirmation(msg, hs.hello, &hs.finishedHash) |
| if hs.hello.echInner && !confirmed { |
| return fmt.Errorf("tls: server did not send ECH confirmation in %T when requested", msg) |
| } else if !hs.hello.echInner && confirmed { |
| return fmt.Errorf("tls: server sent ECH confirmation in %T when not requested", msg) |
| } |
| } |
| |
| // Once the PRF hash is known, TLS 1.3 does not require a handshake buffer. |
| hs.finishedHash.discardHandshakeBuffer() |
| |
| // The first server message must be followed by a ChangeCipherSpec. |
| c.expectTLS13ChangeCipherSpec = !c.isDTLS |
| |
| expectedSessionID := hs.hello.sessionID |
| if c.isDTLS { |
| expectedSessionID = nil |
| } |
| if haveHelloRetryRequest { |
| hs.writeServerHash(helloRetryRequest.marshal()) |
| |
| if !bytes.Equal(expectedSessionID, helloRetryRequest.sessionID) { |
| return errors.New("tls: ClientHello and HelloRetryRequest session IDs did not match.") |
| } |
| |
| if c.config.Bugs.FailIfHelloRetryRequested { |
| return errors.New("tls: unexpected HelloRetryRequest") |
| } |
| // Explicitly read the ChangeCipherSpec now; it should |
| // be attached to the first flight, not the second flight. |
| if err := c.readTLS13ChangeCipherSpec(); err != nil { |
| return err |
| } |
| |
| // Reset the encryption state, in case we sent 0-RTT data. |
| c.out.resetCipher() |
| |
| if c.echAccepted { |
| if err := hs.applyHelloRetryRequest(helloRetryRequest, hs.innerHello, hs.hello); err != nil { |
| return err |
| } |
| hs.writeClientHash(hs.innerHello.marshal()) |
| } else { |
| if err := hs.applyHelloRetryRequest(helloRetryRequest, hs.hello, nil); err != nil { |
| return err |
| } |
| hs.writeClientHash(hs.hello.marshal()) |
| } |
| toWrite := hs.hello.marshal() |
| |
| if c.config.Bugs.PartialSecondClientHelloAfterFirst { |
| // The first byte has already been sent. |
| toWrite = toWrite[1:] |
| } |
| |
| if c.config.Bugs.InterleaveEarlyData { |
| c.sendFakeEarlyData(4) |
| c.writeRecord(recordTypeHandshake, toWrite[:16]) |
| c.sendFakeEarlyData(4) |
| c.writeRecord(recordTypeHandshake, toWrite[16:]) |
| } else if c.config.Bugs.PartialClientFinishedWithSecondClientHello { |
| toWrite = append(make([]byte, 0, len(toWrite)+1), toWrite...) |
| toWrite = append(toWrite, typeFinished) |
| c.writeRecord(recordTypeHandshake, toWrite) |
| } else { |
| c.writeRecord(recordTypeHandshake, toWrite) |
| } |
| c.flushHandshake() |
| |
| if c.config.Bugs.SendEarlyDataOnSecondClientHello { |
| c.sendFakeEarlyData(4) |
| } |
| |
| var err error |
| msg, err = c.readHandshake() |
| if err != nil { |
| return err |
| } |
| } |
| |
| // We no longer need to retain two ClientHellos. |
| if c.echAccepted { |
| hs.hello = hs.innerHello |
| } |
| hs.innerHello = nil |
| |
| var ok bool |
| hs.serverHello, ok = msg.(*serverHelloMsg) |
| if !ok { |
| c.sendAlert(alertUnexpectedMessage) |
| return unexpectedMessageError(hs.serverHello, msg) |
| } |
| |
| if isAllZero(hs.serverHello.random) { |
| // If the server forgets to fill in the server random, it will |
| // likely be all zero. |
| return errors.New("tls: ServerHello random was all zero") |
| } |
| |
| if c.wireVersion != hs.serverHello.vers { |
| c.sendAlert(alertIllegalParameter) |
| return fmt.Errorf("tls: server sent non-matching version %x vs %x", c.wireVersion, hs.serverHello.vers) |
| } |
| |
| if hs.suite.id != hs.serverHello.cipherSuite { |
| c.sendAlert(alertIllegalParameter) |
| return fmt.Errorf("tls: server sent non-matching cipher suite %04x vs %04x", hs.suite.id, hs.serverHello.cipherSuite) |
| } |
| |
| if haveHelloRetryRequest { |
| if helloRetryRequest.hasSelectedGroup && helloRetryRequest.selectedGroup != hs.serverHello.keyShare.group { |
| c.sendAlert(alertHandshakeFailure) |
| return errors.New("tls: ServerHello parameters did not match HelloRetryRequest") |
| } |
| |
| // Both the ServerHello and HelloRetryRequest must have an ECH confirmation. |
| echConfirmed := hs.checkECHConfirmation(hs.serverHello, hs.hello, &hs.finishedHash) |
| if hs.hello.echInner && !echConfirmed { |
| return errors.New("tls: server did not send ECH confirmation in ServerHello when requested") |
| } else if !hs.hello.echInner && echConfirmed { |
| return errors.New("tls: server sent ECH confirmation in ServerHello when not requested") |
| } |
| } |
| |
| if !bytes.Equal(expectedSessionID, hs.serverHello.sessionID) { |
| return errors.New("tls: ClientHello and ServerHello session IDs did not match.") |
| } |
| |
| // Resolve PSK and compute the early secret. |
| zeroSecret := hs.finishedHash.zeroSecret() |
| pskSecret := zeroSecret |
| if hs.serverHello.hasPSKIdentity { |
| // 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 hs.session.cipherSuite.hash() != hs.suite.hash() { |
| c.sendAlert(alertHandshakeFailure) |
| return errors.New("tls: server resumed an invalid session for the cipher suite") |
| } |
| pskSecret = hs.session.secret |
| c.didResume = true |
| } |
| hs.finishedHash.addEntropy(pskSecret) |
| |
| if !hs.serverHello.hasKeyShare { |
| c.sendAlert(alertUnsupportedExtension) |
| return errors.New("tls: server omitted KeyShare on resumption.") |
| } |
| |
| // Resolve ECDHE and compute the handshake secret. |
| ecdheSecret := zeroSecret |
| if !c.config.Bugs.MissingKeyShare && !c.config.Bugs.SecondClientHelloMissingKeyShare { |
| kem, ok := hs.keyShares[hs.serverHello.keyShare.group] |
| if !ok { |
| c.sendAlert(alertHandshakeFailure) |
| return errors.New("tls: server selected an unsupported group") |
| } |
| c.curveID = hs.serverHello.keyShare.group |
| |
| var err error |
| ecdheSecret, err = kem.decap(c.config, hs.serverHello.keyShare.keyExchange) |
| if err != nil { |
| return err |
| } |
| } |
| hs.finishedHash.nextSecret() |
| hs.finishedHash.addEntropy(ecdheSecret) |
| hs.writeServerHash(hs.serverHello.marshal()) |
| |
| // Derive handshake traffic keys and switch read key to handshake |
| // traffic key. |
| clientHandshakeTrafficSecret := hs.finishedHash.deriveSecret(clientHandshakeTrafficLabel) |
| serverHandshakeTrafficSecret := hs.finishedHash.deriveSecret(serverHandshakeTrafficLabel) |
| if err := c.useInTrafficSecret(uint16(encryptionHandshake), c.wireVersion, hs.suite, serverHandshakeTrafficSecret); err != nil { |
| return err |
| } |
| |
| msg, err := c.readHandshake() |
| if err != nil { |
| return err |
| } |
| |
| encryptedExtensions, ok := msg.(*encryptedExtensionsMsg) |
| if !ok { |
| c.sendAlert(alertUnexpectedMessage) |
| return unexpectedMessageError(encryptedExtensions, msg) |
| } |
| hs.writeServerHash(encryptedExtensions.marshal()) |
| |
| if !bytes.Equal(encryptedExtensions.extensions.echRetryConfigs, c.config.Bugs.ExpectECHRetryConfigs) { |
| return errors.New("tls: server sent ECH retry_configs with unexpected contents") |
| } |
| |
| err = hs.processServerExtensions(&encryptedExtensions.extensions) |
| if err != nil { |
| return err |
| } |
| |
| var credential *Credential |
| var certReq *certificateRequestMsg |
| if c.didResume { |
| // Copy over authentication from the session. |
| c.peerCertificates = hs.session.serverCertificates |
| c.sctList = hs.session.sctList |
| c.ocspResponse = hs.session.ocspResponse |
| } else { |
| msg, err := c.readHandshake() |
| if err != nil { |
| return err |
| } |
| |
| var ok bool |
| certReq, ok = msg.(*certificateRequestMsg) |
| if ok { |
| if len(certReq.requestContext) != 0 { |
| return errors.New("tls: non-empty certificate request context sent in handshake") |
| } |
| |
| if c.config.Bugs.ExpectNoCertificateAuthoritiesExtension && certReq.hasCAExtension { |
| return errors.New("tls: expected no certificate_authorities extension") |
| } |
| |
| hs.writeServerHash(certReq.marshal()) |
| |
| credential = c.config.Credential |
| if credential != nil && c.config.Bugs.IgnorePeerSignatureAlgorithmPreferences { |
| certReq.signatureAlgorithms = credential.signatureAlgorithms() |
| } |
| |
| msg, err = c.readHandshake() |
| if err != nil { |
| return err |
| } |
| } |
| |
| var certMsg *certificateMsg |
| |
| if compressedCertMsg, ok := msg.(*compressedCertificateMsg); ok { |
| hs.writeServerHash(compressedCertMsg.marshal()) |
| |
| alg, ok := c.config.CertCompressionAlgs[compressedCertMsg.algID] |
| if !ok { |
| c.sendAlert(alertBadCertificate) |
| return fmt.Errorf("tls: received certificate compressed with unknown algorithm %x", compressedCertMsg.algID) |
| } |
| |
| decompressed := make([]byte, 4+int(compressedCertMsg.uncompressedLength)) |
| if !alg.Decompress(decompressed[4:], compressedCertMsg.compressed) { |
| c.sendAlert(alertBadCertificate) |
| return fmt.Errorf("tls: failed to decompress certificate with algorithm %x", compressedCertMsg.algID) |
| } |
| |
| certMsg = &certificateMsg{ |
| hasRequestContext: true, |
| } |
| |
| if !certMsg.unmarshal(decompressed) { |
| c.sendAlert(alertBadCertificate) |
| return errors.New("tls: failed to parse decompressed certificate") |
| } |
| |
| if expected := c.config.Bugs.ExpectedCompressedCert; expected != 0 && expected != compressedCertMsg.algID { |
| return fmt.Errorf("tls: expected certificate compressed with algorithm %x, but message used %x", expected, compressedCertMsg.algID) |
| } |
| |
| if c.config.Bugs.ExpectUncompressedCert { |
| return errors.New("tls: compressed certificate received") |
| } |
| } else { |
| if certMsg, ok = msg.(*certificateMsg); !ok { |
| c.sendAlert(alertUnexpectedMessage) |
| return unexpectedMessageError(certMsg, msg) |
| } |
| hs.writeServerHash(certMsg.marshal()) |
| |
| if c.config.Bugs.ExpectedCompressedCert != 0 { |
| return errors.New("tls: uncompressed certificate received") |
| } |
| } |
| |
| // Check for unsolicited extensions. |
| for i, cert := range certMsg.certificates { |
| if c.config.Bugs.NoOCSPStapling && cert.ocspResponse != nil { |
| c.sendAlert(alertUnsupportedExtension) |
| return errors.New("tls: unexpected OCSP response in the server certificate") |
| } |
| if c.config.Bugs.NoSignedCertificateTimestamps && cert.sctList != nil { |
| c.sendAlert(alertUnsupportedExtension) |
| return errors.New("tls: unexpected SCT list in the server certificate") |
| } |
| if i > 0 && c.config.Bugs.ExpectNoExtensionsOnIntermediate && (cert.ocspResponse != nil || cert.sctList != nil) { |
| c.sendAlert(alertUnsupportedExtension) |
| return errors.New("tls: unexpected extensions in the server certificate") |
| } |
| } |
| |
| if err := hs.verifyCertificates(certMsg); err != nil { |
| return err |
| } |
| c.ocspResponse = certMsg.certificates[0].ocspResponse |
| c.sctList = certMsg.certificates[0].sctList |
| |
| msg, err = c.readHandshake() |
| if err != nil { |
| return err |
| } |
| certVerifyMsg, ok := msg.(*certificateVerifyMsg) |
| if !ok { |
| c.sendAlert(alertUnexpectedMessage) |
| return unexpectedMessageError(certVerifyMsg, msg) |
| } |
| |
| c.peerSignatureAlgorithm = certVerifyMsg.signatureAlgorithm |
| input := hs.finishedHash.certificateVerifyInput(serverCertificateVerifyContextTLS13) |
| if c.peerDelegatedCredential != nil { |
| err = verifyMessageDC(c.isClient, c.vers, hs.peerPublicKey, c.config, certVerifyMsg.signatureAlgorithm, input, certVerifyMsg.signature) |
| } else { |
| err = verifyMessage(c.isClient, c.vers, hs.peerPublicKey, c.config, certVerifyMsg.signatureAlgorithm, input, certVerifyMsg.signature) |
| } |
| if err != nil { |
| return err |
| } |
| |
| hs.writeServerHash(certVerifyMsg.marshal()) |
| } |
| |
| msg, err = c.readHandshake() |
| if err != nil { |
| return err |
| } |
| serverFinished, ok := msg.(*finishedMsg) |
| if !ok { |
| c.sendAlert(alertUnexpectedMessage) |
| return unexpectedMessageError(serverFinished, msg) |
| } |
| |
| verify := hs.finishedHash.serverSum(serverHandshakeTrafficSecret) |
| if len(verify) != len(serverFinished.verifyData) || |
| subtle.ConstantTimeCompare(verify, serverFinished.verifyData) != 1 { |
| c.sendAlert(alertHandshakeFailure) |
| return errors.New("tls: server's Finished message was incorrect") |
| } |
| |
| hs.writeServerHash(serverFinished.marshal()) |
| |
| // The various secrets do not incorporate the client's final leg, so |
| // derive them now before updating the handshake context. |
| hs.finishedHash.nextSecret() |
| hs.finishedHash.addEntropy(zeroSecret) |
| |
| clientTrafficSecret := hs.finishedHash.deriveSecret(clientApplicationTrafficLabel) |
| serverTrafficSecret := hs.finishedHash.deriveSecret(serverApplicationTrafficLabel) |
| c.exporterSecret = hs.finishedHash.deriveSecret(exporterLabel) |
| |
| // Switch to application data keys on read. In particular, any alerts |
| // from the client certificate are read over these keys. |
| if err := c.useInTrafficSecret(uint16(encryptionApplication), c.wireVersion, hs.suite, serverTrafficSecret); err != nil { |
| return err |
| } |
| |
| // If we're expecting 0.5-RTT messages from the server, read them now. |
| var deferredTickets []*newSessionTicketMsg |
| if encryptedExtensions.extensions.hasEarlyData { |
| // BoringSSL will always send two tickets half-RTT when |
| // negotiating 0-RTT. |
| for i := 0; i < shimConfig.HalfRTTTickets; i++ { |
| msg, err := c.readHandshake() |
| if err != nil { |
| return fmt.Errorf("tls: error reading half-RTT ticket: %s", err) |
| } |
| newSessionTicket, ok := msg.(*newSessionTicketMsg) |
| if !ok { |
| return errors.New("tls: expected half-RTT ticket") |
| } |
| // Defer processing until the resumption secret is computed. |
| deferredTickets = append(deferredTickets, newSessionTicket) |
| } |
| for _, expectedMsg := range c.config.Bugs.ExpectHalfRTTData { |
| if err := c.readRecord(recordTypeApplicationData); err != nil { |
| return err |
| } |
| if !bytes.Equal(c.input.Bytes(), expectedMsg) { |
| return fmt.Errorf("tls: got half-RTT data record %x, wanted %x", c.input.Bytes(), expectedMsg) |
| } |
| c.input.Reset() |
| } |
| } |
| |
| // Send EndOfEarlyData and then switch write key to handshake |
| // traffic key. |
| if encryptedExtensions.extensions.hasEarlyData && !c.config.Bugs.SkipEndOfEarlyData && c.usesEndOfEarlyData() { |
| if c.config.Bugs.SendStrayEarlyHandshake { |
| helloRequest := new(helloRequestMsg) |
| c.writeRecord(recordTypeHandshake, helloRequest.marshal()) |
| } |
| endOfEarlyData := new(endOfEarlyDataMsg) |
| endOfEarlyData.nonEmpty = c.config.Bugs.NonEmptyEndOfEarlyData |
| hs.writeClientHash(endOfEarlyData.marshal()) |
| if c.config.Bugs.PartialEndOfEarlyDataWithClientHello { |
| // The first byte has already been sent. |
| c.writeRecord(recordTypeHandshake, endOfEarlyData.marshal()[1:]) |
| } else { |
| c.writeRecord(recordTypeHandshake, endOfEarlyData.marshal()) |
| } |
| } |
| |
| if !c.config.Bugs.SkipChangeCipherSpec && !hs.hello.hasEarlyData && !c.isDTLS { |
| c.writeRecord(recordTypeChangeCipherSpec, []byte{1}) |
| } |
| |
| for i := 0; i < c.config.Bugs.SendExtraChangeCipherSpec; i++ { |
| c.writeRecord(recordTypeChangeCipherSpec, []byte{1}) |
| } |
| |
| c.useOutTrafficSecret(uint16(encryptionHandshake), c.wireVersion, hs.suite, clientHandshakeTrafficSecret) |
| |
| // The client EncryptedExtensions message is sent if some extension uses it. |
| // (Currently only ALPS does.) |
| hasEncryptedExtensions := c.config.Bugs.AlwaysSendClientEncryptedExtensions |
| clientEncryptedExtensions := new(clientEncryptedExtensionsMsg) |
| if encryptedExtensions.extensions.hasApplicationSettings || (c.config.Bugs.SendApplicationSettingsWithEarlyData && c.hasApplicationSettings) { |
| hasEncryptedExtensions = true |
| if !c.config.Bugs.OmitClientApplicationSettings { |
| clientEncryptedExtensions.hasApplicationSettings = true |
| clientEncryptedExtensions.applicationSettings = c.localApplicationSettings |
| } |
| } |
| if encryptedExtensions.extensions.hasApplicationSettingsOld || (c.config.Bugs.SendApplicationSettingsWithEarlyData && c.hasApplicationSettingsOld) { |
| hasEncryptedExtensions = true |
| if !c.config.Bugs.OmitClientApplicationSettings { |
| clientEncryptedExtensions.hasApplicationSettingsOld = true |
| clientEncryptedExtensions.applicationSettingsOld = c.localApplicationSettingsOld |
| } |
| } |
| if c.config.Bugs.SendExtraClientEncryptedExtension { |
| hasEncryptedExtensions = true |
| clientEncryptedExtensions.customExtension = []byte{0} |
| } |
| if hasEncryptedExtensions && !c.config.Bugs.OmitClientEncryptedExtensions { |
| hs.writeClientHash(clientEncryptedExtensions.marshal()) |
| c.writeRecord(recordTypeHandshake, clientEncryptedExtensions.marshal()) |
| } |
| |
| if certReq != nil && !c.config.Bugs.SkipClientCertificate { |
| certMsg := &certificateMsg{ |
| hasRequestContext: true, |
| requestContext: certReq.requestContext, |
| } |
| if credential != nil { |
| for _, certData := range credential.Certificate { |
| certMsg.certificates = append(certMsg.certificates, certificateEntry{ |
| data: certData, |
| extraExtension: c.config.Bugs.SendExtensionOnCertificate, |
| }) |
| } |
| } |
| hs.writeClientHash(certMsg.marshal()) |
| c.writeRecord(recordTypeHandshake, certMsg.marshal()) |
| |
| if credential != nil { |
| certVerify := &certificateVerifyMsg{ |
| hasSignatureAlgorithm: true, |
| } |
| |
| // Determine the hash to sign. |
| var err error |
| certVerify.signatureAlgorithm, err = selectSignatureAlgorithm(c.isClient, c.vers, credential, c.config, certReq.signatureAlgorithms) |
| if err != nil { |
| c.sendAlert(alertInternalError) |
| return err |
| } |
| |
| privKey := credential.PrivateKey |
| input := hs.finishedHash.certificateVerifyInput(clientCertificateVerifyContextTLS13) |
| certVerify.signature, err = signMessage(c.isClient, c.vers, privKey, c.config, certVerify.signatureAlgorithm, input) |
| if err != nil { |
| c.sendAlert(alertInternalError) |
| return err |
| } |
| if c.config.Bugs.SendSignatureAlgorithm != 0 { |
| certVerify.signatureAlgorithm = c.config.Bugs.SendSignatureAlgorithm |
| } |
| |
| if !c.config.Bugs.SkipCertificateVerify { |
| hs.writeClientHash(certVerify.marshal()) |
| c.writeRecord(recordTypeHandshake, certVerify.marshal()) |
| } |
| } |
| } |
| |
| if encryptedExtensions.extensions.channelIDRequested { |
| channelIDHash := crypto.SHA256.New() |
| channelIDHash.Write(hs.finishedHash.certificateVerifyInput(channelIDContextTLS13)) |
| channelIDMsgBytes, err := hs.writeChannelIDMessage(channelIDHash.Sum(nil)) |
| if err != nil { |
| return err |
| } |
| hs.writeClientHash(channelIDMsgBytes) |
| c.writeRecord(recordTypeHandshake, channelIDMsgBytes) |
| } |
| |
| // Send a client Finished message. |
| finished := new(finishedMsg) |
| finished.verifyData = hs.finishedHash.clientSum(clientHandshakeTrafficSecret) |
| if c.config.Bugs.BadFinished { |
| finished.verifyData[0]++ |
| } |
| hs.writeClientHash(finished.marshal()) |
| if c.config.Bugs.PartialClientFinishedWithClientHello { |
| // The first byte has already been sent. |
| c.writeRecord(recordTypeHandshake, finished.marshal()[1:]) |
| } else if c.config.Bugs.InterleaveEarlyData { |
| finishedBytes := finished.marshal() |
| c.sendFakeEarlyData(4) |
| c.writeRecord(recordTypeHandshake, finishedBytes[:1]) |
| c.sendFakeEarlyData(4) |
| c.writeRecord(recordTypeHandshake, finishedBytes[1:]) |
| } else { |
| c.writeRecord(recordTypeHandshake, finished.marshal()) |
| } |
| if c.config.Bugs.SendExtraFinished { |
| c.writeRecord(recordTypeHandshake, finished.marshal()) |
| } |
| c.flushHandshake() |
| |
| if data := c.config.Bugs.AppDataBeforeTLS13KeyChange; data != nil { |
| c.writeRecord(recordTypeApplicationData, data) |
| } |
| |
| // Switch to application data keys. |
| c.useOutTrafficSecret(uint16(encryptionApplication), c.wireVersion, hs.suite, clientTrafficSecret) |
| c.resumptionSecret = hs.finishedHash.deriveSecret(resumptionLabel) |
| for _, ticket := range deferredTickets { |
| if err := c.processTLS13NewSessionTicket(ticket, hs.suite); err != nil { |
| return err |
| } |
| } |
| |
| return nil |
| } |
| |
| // applyHelloRetryRequest updates |hello| in-place based on |helloRetryRequest|. |
| // If |outerHello| is not nil, |outerHello| will be updated to contain an |
| // encrypted copy of |hello|. |
| func (hs *clientHandshakeState) applyHelloRetryRequest(helloRetryRequest *helloRetryRequestMsg, hello, outerHello *clientHelloMsg) error { |
| c := hs.c |
| firstHelloBytes := hello.marshal() |
| if len(helloRetryRequest.cookie) > 0 { |
| hello.tls13Cookie = helloRetryRequest.cookie |
| } |
| |
| if c.config.Bugs.MisinterpretHelloRetryRequestCurve != 0 { |
| helloRetryRequest.hasSelectedGroup = true |
| helloRetryRequest.selectedGroup = c.config.Bugs.MisinterpretHelloRetryRequestCurve |
| } |
| if helloRetryRequest.hasSelectedGroup { |
| group := helloRetryRequest.selectedGroup |
| if !slices.Contains(hello.supportedCurves, group) || hs.keyShares[group] != nil { |
| c.sendAlert(alertHandshakeFailure) |
| return errors.New("tls: received invalid HelloRetryRequest") |
| } |
| kem, ok := kemForCurveID(group, c.config) |
| if !ok { |
| return errors.New("tls: Unable to get curve requested in HelloRetryRequest") |
| } |
| publicKey, err := kem.generate(c.config) |
| if err != nil { |
| return err |
| } |
| hs.keyShares[group] = kem |
| hello.keyShares = []keyShareEntry{{ |
| group: group, |
| keyExchange: publicKey, |
| }} |
| } |
| |
| if c.config.Bugs.SecondClientHelloMissingKeyShare { |
| hello.hasKeyShares = false |
| } |
| |
| if c.config.Bugs.OmitSecondECHInner { |
| hello.echInner = false |
| } |
| |
| hello.hasEarlyData = c.config.Bugs.SendEarlyDataOnSecondClientHello |
| // The first ClientHello may have skipped this due to OnlyCorruptSecondPSKBinder. |
| if c.config.Bugs.PSKBinderFirst && c.config.Bugs.OnlyCorruptSecondPSKBinder { |
| hello.prefixExtensions = append(hello.prefixExtensions, extensionPreSharedKey) |
| } |
| // The first ClientHello may have set this due to OnlyCompressSecondClientHelloInner. |
| hello.reorderOuterExtensionsWithoutCompressing = false |
| if c.config.Bugs.OmitPSKsOnSecondClientHello { |
| hello.pskIdentities = nil |
| hello.pskBinders = nil |
| } |
| hello.raw = nil |
| |
| if len(hello.pskIdentities) > 0 { |
| generatePSKBinders(c.wireVersion, c.isDTLS, hello, hs.session, firstHelloBytes, helloRetryRequest.marshal(), c.config) |
| } |
| |
| if outerHello != nil { |
| outerHello.raw = nil |
| // We know the server has accepted ECH, so the ClientHelloOuter's fields |
| // are irrelevant. In the general case, the HelloRetryRequest may not |
| // even be valid for ClientHelloOuter. However, we copy the key shares |
| // from ClientHelloInner so they remain eligible for compression. |
| if !c.config.Bugs.MinimalClientHelloOuter { |
| outerHello.keyShares = hello.keyShares |
| } |
| |
| if c.config.Bugs.OmitSecondEncryptedClientHello { |
| outerHello.echOuter = nil |
| } else { |
| configID := c.config.ClientECHConfig.ConfigID |
| if c.config.Bugs.CorruptSecondEncryptedClientHelloConfigID { |
| configID ^= 1 |
| } |
| if err := hs.encryptClientHello(outerHello, hello, configID, nil); err != nil { |
| return err |
| } |
| if c.config.Bugs.CorruptSecondEncryptedClientHello { |
| if c.config.Bugs.NullAllCiphers { |
| outerHello.echOuter.payload = []byte{echBadPayloadByte} |
| } else { |
| outerHello.echOuter.payload[0] ^= 1 |
| } |
| } |
| } |
| } |
| |
| return nil |
| } |
| |
| func (hs *clientHandshakeState) doFullHandshake() error { |
| c := hs.c |
| |
| var leaf *x509.Certificate |
| if hs.suite.flags&suitePSK == 0 { |
| msg, err := c.readHandshake() |
| if err != nil { |
| return err |
| } |
| |
| certMsg, ok := msg.(*certificateMsg) |
| if !ok { |
| c.sendAlert(alertUnexpectedMessage) |
| return unexpectedMessageError(certMsg, msg) |
| } |
| hs.writeServerHash(certMsg.marshal()) |
| |
| if err := hs.verifyCertificates(certMsg); err != nil { |
| return err |
| } |
| leaf = c.peerCertificates[0] |
| } |
| |
| if hs.serverHello.extensions.ocspStapling { |
| msg, err := c.readHandshake() |
| if err != nil { |
| return err |
| } |
| cs, ok := msg.(*certificateStatusMsg) |
| if !ok { |
| c.sendAlert(alertUnexpectedMessage) |
| return unexpectedMessageError(cs, msg) |
| } |
| hs.writeServerHash(cs.marshal()) |
| |
| if cs.statusType == statusTypeOCSP { |
| c.ocspResponse = cs.response |
| } |
| } |
| |
| msg, err := c.readHandshake() |
| if err != nil { |
| return err |
| } |
| |
| keyAgreement := hs.suite.ka(c.vers) |
| |
| skx, ok := msg.(*serverKeyExchangeMsg) |
| if ok { |
| hs.writeServerHash(skx.marshal()) |
| err = keyAgreement.processServerKeyExchange(c.config, hs.hello, hs.serverHello, hs.peerPublicKey, skx) |
| if err != nil { |
| c.sendAlert(alertUnexpectedMessage) |
| return err |
| } |
| if ecdhe, ok := keyAgreement.(*ecdheKeyAgreement); ok { |
| c.curveID = ecdhe.curveID |
| } |
| |
| c.peerSignatureAlgorithm = keyAgreement.peerSignatureAlgorithm() |
| |
| msg, err = c.readHandshake() |
| if err != nil { |
| return err |
| } |
| } |
| |
| var credential *Credential |
| var certRequested bool |
| certReq, ok := msg.(*certificateRequestMsg) |
| if ok { |
| certRequested = true |
| hs.writeServerHash(certReq.marshal()) |
| |
| credential = c.config.Credential |
| if credential != nil && c.config.Bugs.IgnorePeerSignatureAlgorithmPreferences { |
| certReq.signatureAlgorithms = credential.signatureAlgorithms() |
| } |
| |
| msg, err = c.readHandshake() |
| if err != nil { |
| return err |
| } |
| } |
| |
| shd, ok := msg.(*serverHelloDoneMsg) |
| if !ok { |
| c.sendAlert(alertUnexpectedMessage) |
| return unexpectedMessageError(shd, msg) |
| } |
| hs.writeServerHash(shd.marshal()) |
| |
| // If the server requested a certificate then we have to send a |
| // Certificate message in TLS, even if it's empty because we don't have |
| // a certificate to send. |
| if certRequested && !c.config.Bugs.SkipClientCertificate { |
| certMsg := new(certificateMsg) |
| if credential != nil { |
| for _, certData := range credential.Certificate { |
| certMsg.certificates = append(certMsg.certificates, certificateEntry{ |
| data: certData, |
| }) |
| } |
| } |
| hs.writeClientHash(certMsg.marshal()) |
| c.writeRecord(recordTypeHandshake, certMsg.marshal()) |
| } |
| |
| preMasterSecret, ckx, err := keyAgreement.generateClientKeyExchange(c.config, hs.hello, leaf) |
| if err != nil { |
| c.sendAlert(alertInternalError) |
| return err |
| } |
| if ckx != nil { |
| if c.config.Bugs.EarlyChangeCipherSpec < 2 { |
| hs.writeClientHash(ckx.marshal()) |
| } |
| if c.config.Bugs.PartialClientKeyExchangeWithClientHello { |
| // The first byte was already written. |
| c.writeRecord(recordTypeHandshake, ckx.marshal()[1:]) |
| } else { |
| c.writeRecord(recordTypeHandshake, ckx.marshal()) |
| } |
| } |
| |
| if hs.serverHello.extensions.extendedMasterSecret { |
| hs.masterSecret = extendedMasterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.finishedHash) |
| c.extendedMasterSecret = true |
| } else { |
| if c.config.Bugs.RequireExtendedMasterSecret { |
| return errors.New("tls: extended master secret required but not supported by peer") |
| } |
| hs.masterSecret = masterFromPreMasterSecret(c.vers, hs.suite, preMasterSecret, hs.hello.random, hs.serverHello.random) |
| } |
| |
| if credential != nil { |
| certVerify := &certificateVerifyMsg{ |
| hasSignatureAlgorithm: c.vers >= VersionTLS12, |
| } |
| |
| // Determine the hash to sign. |
| if certVerify.hasSignatureAlgorithm { |
| certVerify.signatureAlgorithm, err = selectSignatureAlgorithm(c.isClient, c.vers, credential, c.config, certReq.signatureAlgorithms) |
| if err != nil { |
| c.sendAlert(alertInternalError) |
| return err |
| } |
| } |
| |
| privKey := c.config.Credential.PrivateKey |
| certVerify.signature, err = signMessage(c.isClient, c.vers, privKey, c.config, certVerify.signatureAlgorithm, hs.finishedHash.buffer) |
| if err == nil && c.config.Bugs.SendSignatureAlgorithm != 0 { |
| certVerify.signatureAlgorithm = c.config.Bugs.SendSignatureAlgorithm |
| } |
| if err != nil { |
| c.sendAlert(alertInternalError) |
| return errors.New("tls: failed to sign handshake with client certificate: " + err.Error()) |
| } |
| |
| if !c.config.Bugs.SkipCertificateVerify { |
| hs.writeClientHash(certVerify.marshal()) |
| c.writeRecord(recordTypeHandshake, certVerify.marshal()) |
| } |
| } |
| // flushHandshake will be called in sendFinished. |
| |
| hs.finishedHash.discardHandshakeBuffer() |
| |
| return nil |
| } |
| |
| // delegatedCredentialSignedMessage returns the bytes that are signed in order |
| // to authenticate a delegated credential. |
| func delegatedCredentialSignedMessage(credBytes []byte, algorithm signatureAlgorithm, leafDER []byte) []byte { |
| // https://www.rfc-editor.org/rfc/rfc9345.html#section-4 |
| ret := make([]byte, 64, 128) |
| for i := range ret { |
| ret[i] = 0x20 |
| } |
| |
| ret = append(ret, []byte("TLS, server delegated credentials\x00")...) |
| ret = append(ret, leafDER...) |
| ret = append(ret, byte(algorithm>>8), byte(algorithm)) |
| ret = append(ret, credBytes...) |
| |
| return ret |
| } |
| |
| func (hs *clientHandshakeState) verifyCertificates(certMsg *certificateMsg) error { |
| c := hs.c |
| |
| if len(certMsg.certificates) == 0 { |
| c.sendAlert(alertIllegalParameter) |
| return errors.New("tls: no certificates sent") |
| } |
| |
| var dc *delegatedCredential |
| certs := make([]*x509.Certificate, len(certMsg.certificates)) |
| for i, certEntry := range certMsg.certificates { |
| cert, err := x509.ParseCertificate(certEntry.data) |
| if err != nil { |
| c.sendAlert(alertBadCertificate) |
| return errors.New("tls: failed to parse certificate from server: " + err.Error()) |
| } |
| certs[i] = cert |
| |
| if certEntry.delegatedCredential != nil { |
| if i != 0 { |
| c.sendAlert(alertIllegalParameter) |
| return errors.New("tls: non-leaf certificate has a delegated credential") |
| } |
| if len(c.config.DelegatedCredentialAlgorithms) == 0 { |
| c.sendAlert(alertIllegalParameter) |
| return errors.New("tls: server sent delegated credential without it being requested") |
| } |
| dc = certEntry.delegatedCredential |
| } |
| } |
| |
| if !c.config.InsecureSkipVerify { |
| opts := x509.VerifyOptions{ |
| Roots: c.config.RootCAs, |
| CurrentTime: c.config.time(), |
| DNSName: c.config.ServerName, |
| Intermediates: x509.NewCertPool(), |
| } |
| |
| for i, cert := range certs { |
| if i == 0 { |
| continue |
| } |
| opts.Intermediates.AddCert(cert) |
| } |
| var err error |
| c.verifiedChains, err = certs[0].Verify(opts) |
| if err != nil { |
| c.sendAlert(alertBadCertificate) |
| return err |
| } |
| } |
| |
| leafPublicKey := certs[0].PublicKey |
| switch leafPublicKey.(type) { |
| case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey: |
| break |
| default: |
| c.sendAlert(alertUnsupportedCertificate) |
| return fmt.Errorf("tls: server's certificate contains an unsupported type of public key: %T", leafPublicKey) |
| } |
| |
| c.peerCertificates = certs |
| |
| if dc != nil { |
| // Note that this doesn't check a) the delegated credential temporal |
| // validity nor b) that the certificate has the special OID asserted. |
| var err error |
| if hs.peerPublicKey, err = x509.ParsePKIXPublicKey(dc.pkixPublicKey); err != nil { |
| c.sendAlert(alertBadCertificate) |
| return errors.New("tls: failed to parse public key from delegated credential: " + err.Error()) |
| } |
| |
| signedMsg := delegatedCredentialSignedMessage(dc.signedBytes, dc.algorithm, certs[0].Raw) |
| if err := verifyMessage(c.isClient, c.vers, leafPublicKey, c.config, dc.algorithm, signedMsg, dc.signature); err != nil { |
| c.sendAlert(alertBadCertificate) |
| return errors.New("tls: failed to verify delegated credential: " + err.Error()) |
| } |
| c.peerDelegatedCredential = dc.raw |
| } else { |
| hs.peerPublicKey = leafPublicKey |
| } |
| |
| return nil |
| } |
| |
| func (hs *clientHandshakeState) establishKeys() error { |
| c := hs.c |
| |
| clientMAC, serverMAC, clientKey, serverKey, clientIV, serverIV := |
| keysFromMasterSecret(c.vers, hs.suite, hs.masterSecret, hs.hello.random, hs.serverHello.random, hs.suite.macLen, hs.suite.keyLen, hs.suite.ivLen(c.vers)) |
| var clientCipher, serverCipher any |
| var clientHash, serverHash macFunction |
| if hs.suite.cipher != nil { |
| clientCipher = hs.suite.cipher(clientKey, clientIV, false /* not for reading */) |
| clientHash = hs.suite.mac(c.vers, clientMAC) |
| serverCipher = hs.suite.cipher(serverKey, serverIV, true /* for reading */) |
| serverHash = hs.suite.mac(c.vers, serverMAC) |
| } else { |
| clientCipher = hs.suite.aead(c.vers, clientKey, clientIV) |
| serverCipher = hs.suite.aead(c.vers, serverKey, serverIV) |
| } |
| |
| c.in.prepareCipherSpec(c.wireVersion, serverCipher, serverHash) |
| c.out.prepareCipherSpec(c.wireVersion, clientCipher, clientHash) |
| return nil |
| } |
| |
| func (hs *clientHandshakeState) processServerExtensions(serverExtensions *serverExtensions) error { |
| c := hs.c |
| |
| if c.vers < VersionTLS13 { |
| if c.config.Bugs.RequireRenegotiationInfo && serverExtensions.secureRenegotiation == nil { |
| return errors.New("tls: renegotiation extension missing") |
| } |
| |
| if len(c.clientVerify) > 0 && !c.noRenegotiationInfo() { |
| var expectedRenegInfo []byte |
| expectedRenegInfo = append(expectedRenegInfo, c.clientVerify...) |
| expectedRenegInfo = append(expectedRenegInfo, c.serverVerify...) |
| if !bytes.Equal(serverExtensions.secureRenegotiation, expectedRenegInfo) { |
| c.sendAlert(alertHandshakeFailure) |
| return fmt.Errorf("tls: renegotiation mismatch") |
| } |
| } |
| } else if serverExtensions.secureRenegotiation != nil { |
| return errors.New("tls: renegotiation info sent in TLS 1.3") |
| } |
| |
| if expected := c.config.Bugs.ExpectedCustomExtension; expected != nil { |
| if serverExtensions.customExtension != *expected { |
| return fmt.Errorf("tls: bad custom extension contents %q", serverExtensions.customExtension) |
| } |
| } |
| |
| clientDidNPN := hs.hello.nextProtoNeg |
| clientDidALPN := len(hs.hello.alpnProtocols) > 0 |
| serverHasNPN := serverExtensions.nextProtoNeg |
| serverHasALPN := len(serverExtensions.alpnProtocol) > 0 |
| |
| if !clientDidNPN && serverHasNPN { |
| c.sendAlert(alertHandshakeFailure) |
| return errors.New("server advertised unrequested NPN extension") |
| } |
| |
| if !clientDidALPN && serverHasALPN { |
| c.sendAlert(alertHandshakeFailure) |
| return errors.New("server advertised unrequested ALPN extension") |
| } |
| |
| if serverHasNPN && serverHasALPN { |
| c.sendAlert(alertHandshakeFailure) |
| return errors.New("server advertised both NPN and ALPN extensions") |
| } |
| |
| if serverHasALPN { |
| c.clientProtocol = serverExtensions.alpnProtocol |
| c.clientProtocolFallback = false |
| c.usedALPN = true |
| } |
| |
| if serverHasNPN && c.vers >= VersionTLS13 { |
| c.sendAlert(alertHandshakeFailure) |
| return errors.New("server advertised NPN over TLS 1.3") |
| } |
| |
| if !hs.hello.channelIDSupported && serverExtensions.channelIDRequested { |
| c.sendAlert(alertHandshakeFailure) |
| return errors.New("server advertised unrequested Channel ID extension") |
| } |
| |
| if serverExtensions.extendedMasterSecret && c.vers >= VersionTLS13 { |
| return errors.New("tls: server advertised extended master secret over TLS 1.3") |
| } |
| |
| if serverExtensions.ticketSupported && c.vers >= VersionTLS13 { |
| return errors.New("tls: server advertised ticket extension over TLS 1.3") |
| } |
| |
| if serverExtensions.ocspStapling && c.vers >= VersionTLS13 { |
| return errors.New("tls: server advertised OCSP in ServerHello over TLS 1.3") |
| } |
| |
| if serverExtensions.ocspStapling && c.config.Bugs.NoOCSPStapling { |
| return errors.New("tls: server advertised unrequested OCSP extension") |
| } |
| |
| if len(serverExtensions.sctList) > 0 && c.vers >= VersionTLS13 { |
| return errors.New("tls: server advertised SCTs in ServerHello over TLS 1.3") |
| } |
| |
| if len(serverExtensions.sctList) > 0 && c.config.Bugs.NoSignedCertificateTimestamps { |
| return errors.New("tls: server advertised unrequested SCTs") |
| } |
| |
| if serverExtensions.srtpProtectionProfile != 0 { |
| if serverExtensions.srtpMasterKeyIdentifier != "" { |
| return errors.New("tls: server selected SRTP MKI value") |
| } |
| |
| found := false |
| for _, p := range c.config.SRTPProtectionProfiles { |
| if p == serverExtensions.srtpProtectionProfile { |
| found = true |
| break |
| } |
| } |
| if !found { |
| return errors.New("tls: server advertised unsupported SRTP profile") |
| } |
| |
| c.srtpProtectionProfile = serverExtensions.srtpProtectionProfile |
| } |
| |
| if c.vers >= VersionTLS13 && c.didResume { |
| if c.config.Bugs.ExpectEarlyDataAccepted && !serverExtensions.hasEarlyData { |
| c.sendAlert(alertHandshakeFailure) |
| return errors.New("tls: server did not accept early data when expected") |
| } |
| |
| if !c.config.Bugs.ExpectEarlyDataAccepted && serverExtensions.hasEarlyData { |
| c.sendAlert(alertHandshakeFailure) |
| return errors.New("tls: server accepted early data when not expected") |
| } |
| } else if serverExtensions.hasEarlyData { |
| return errors.New("tls: server accepted early data when not resuming") |
| } |
| |
| if len(serverExtensions.quicTransportParams) > 0 { |
| if c.vers < VersionTLS13 { |
| c.sendAlert(alertHandshakeFailure) |
| return errors.New("tls: server sent QUIC transport params for TLS version less than 1.3") |
| } |
| c.quicTransportParams = serverExtensions.quicTransportParams |
| } |
| |
| if len(serverExtensions.quicTransportParamsLegacy) > 0 { |
| if c.vers < VersionTLS13 { |
| c.sendAlert(alertHandshakeFailure) |
| return errors.New("tls: server sent QUIC transport params for TLS version less than 1.3") |
| } |
| c.quicTransportParamsLegacy = serverExtensions.quicTransportParamsLegacy |
| } |
| |
| if serverExtensions.hasApplicationSettings && serverExtensions.hasApplicationSettingsOld { |
| return errors.New("tls: server negotiated both old and new application settings together") |
| } |
| |
| if serverExtensions.hasApplicationSettings || serverExtensions.hasApplicationSettingsOld { |
| if c.vers < VersionTLS13 { |
| return errors.New("tls: server sent application settings at invalid version") |
| } |
| if serverExtensions.hasEarlyData { |
| return errors.New("tls: server sent application settings with 0-RTT") |
| } |
| if !serverHasALPN { |
| return errors.New("tls: server sent application settings without ALPN") |
| } |
| settings, ok := c.config.ApplicationSettings[serverExtensions.alpnProtocol] |
| if !ok { |
| return errors.New("tls: server sent application settings for invalid protocol") |
| } |
| |
| if serverExtensions.hasApplicationSettings { |
| c.hasApplicationSettings = true |
| c.localApplicationSettings = settings |
| c.peerApplicationSettings = serverExtensions.applicationSettings |
| } |
| |
| if serverExtensions.hasApplicationSettingsOld { |
| c.hasApplicationSettingsOld = true |
| c.localApplicationSettingsOld = settings |
| c.peerApplicationSettingsOld = serverExtensions.applicationSettingsOld |
| } |
| } else if serverExtensions.hasEarlyData { |
| // 0-RTT connections inherit application settings from the session. |
| c.hasApplicationSettings = hs.session.hasApplicationSettings |
| c.localApplicationSettings = hs.session.localApplicationSettings |
| c.peerApplicationSettings = hs.session.peerApplicationSettings |
| c.hasApplicationSettingsOld = hs.session.hasApplicationSettingsOld |
| c.localApplicationSettingsOld = hs.session.localApplicationSettingsOld |
| c.peerApplicationSettingsOld = hs.session.peerApplicationSettingsOld |
| } |
| |
| return nil |
| } |
| |
| func (hs *clientHandshakeState) serverResumedSession() bool { |
| // If the server responded with the same sessionID then it means the |
| // sessionTicket is being used to resume a TLS session. |
| // |
| // Note that, if hs.hello.sessionID is a non-nil empty array, this will |
| // accept an empty session ID from the server as resumption. See |
| // EmptyTicketSessionID. |
| return hs.session != nil && hs.hello.sessionID != nil && |
| bytes.Equal(hs.serverHello.sessionID, hs.hello.sessionID) |
| } |
| |
| func (hs *clientHandshakeState) processServerHello() (bool, error) { |
| c := hs.c |
| |
| // Check for downgrade signals in the server random, per RFC 8446, section 4.1.3. |
| gotDowngrade := hs.serverHello.random[len(hs.serverHello.random)-8:] |
| if !c.config.Bugs.IgnoreTLS13DowngradeRandom { |
| if c.config.maxVersion(c.isDTLS) >= VersionTLS13 { |
| if bytes.Equal(gotDowngrade, downgradeTLS13) { |
| c.sendAlert(alertProtocolVersion) |
| return false, errors.New("tls: downgrade from TLS 1.3 detected") |
| } |
| } |
| if c.vers <= VersionTLS11 && c.config.maxVersion(c.isDTLS) >= VersionTLS12 { |
| if bytes.Equal(gotDowngrade, downgradeTLS12) { |
| c.sendAlert(alertProtocolVersion) |
| return false, errors.New("tls: downgrade from TLS 1.2 detected") |
| } |
| } |
| } |
| |
| if bytes.Equal(gotDowngrade, downgradeJDK11) != c.config.Bugs.ExpectJDK11DowngradeRandom { |
| c.sendAlert(alertProtocolVersion) |
| if c.config.Bugs.ExpectJDK11DowngradeRandom { |
| return false, errors.New("tls: server did not send a JDK 11 downgrade signal") |
| } |
| return false, errors.New("tls: server sent an unexpected JDK 11 downgrade signal") |
| } |
| |
| if c.config.Bugs.ExpectOmitExtensions && !hs.serverHello.omitExtensions { |
| return false, errors.New("tls: ServerHello did not omit extensions") |
| } |
| |
| if hs.serverResumedSession() { |
| // For test purposes, assert that the server never accepts the |
| // resumption offer on renegotiation. |
| if c.cipherSuite != nil && c.config.Bugs.FailIfResumeOnRenego { |
| return false, errors.New("tls: server resumed session on renegotiation") |
| } |
| |
| if hs.serverHello.extensions.sctList != nil { |
| return false, errors.New("tls: server sent SCT extension on session resumption") |
| } |
| |
| if hs.serverHello.extensions.ocspStapling { |
| return false, errors.New("tls: server sent OCSP extension on session resumption") |
| } |
| |
| // Restore masterSecret and peerCerts from previous state |
| hs.masterSecret = hs.session.secret |
| c.peerCertificates = hs.session.serverCertificates |
| c.peerDelegatedCredential = hs.session.serverDelegatedCredential |
| c.extendedMasterSecret = hs.session.extendedMasterSecret |
| c.sctList = hs.session.sctList |
| c.ocspResponse = hs.session.ocspResponse |
| hs.finishedHash.discardHandshakeBuffer() |
| return true, nil |
| } |
| |
| if hs.serverHello.extensions.sctList != nil { |
| c.sctList = hs.serverHello.extensions.sctList |
| } |
| |
| return false, nil |
| } |
| |
| func (hs *clientHandshakeState) readFinished(out []byte) error { |
| c := hs.c |
| |
| c.readRecord(recordTypeChangeCipherSpec) |
| if err := c.in.error(); err != nil { |
| return err |
| } |
| |
| msg, err := c.readHandshake() |
| if err != nil { |
| return err |
| } |
| serverFinished, ok := msg.(*finishedMsg) |
| if !ok { |
| c.sendAlert(alertUnexpectedMessage) |
| return unexpectedMessageError(serverFinished, msg) |
| } |
| |
| if c.config.Bugs.EarlyChangeCipherSpec == 0 { |
| verify := hs.finishedHash.serverSum(hs.masterSecret) |
| if len(verify) != len(serverFinished.verifyData) || |
| subtle.ConstantTimeCompare(verify, serverFinished.verifyData) != 1 { |
| c.sendAlert(alertHandshakeFailure) |
| return errors.New("tls: server's Finished message was incorrect") |
| } |
| } |
| c.serverVerify = append(c.serverVerify[:0], serverFinished.verifyData...) |
| copy(out, serverFinished.verifyData) |
| hs.writeServerHash(serverFinished.marshal()) |
| return nil |
| } |
| |
| func (hs *clientHandshakeState) readSessionTicket() error { |
| c := hs.c |
| |
| // Create a session with no server identifier. Either a |
| // session ID or session ticket will be attached. |
| session := &ClientSessionState{ |
| vers: c.vers, |
| wireVersion: c.wireVersion, |
| cipherSuite: hs.suite, |
| secret: hs.masterSecret, |
| handshakeHash: hs.finishedHash.Sum(), |
| serverCertificates: c.peerCertificates, |
| serverDelegatedCredential: c.peerDelegatedCredential, |
| sctList: c.sctList, |
| ocspResponse: c.ocspResponse, |
| ticketExpiration: c.config.time().Add(time.Duration(7 * 24 * time.Hour)), |
| } |
| |
| if !hs.serverHello.extensions.ticketSupported { |
| if c.config.Bugs.ExpectNewTicket { |
| return errors.New("tls: expected new ticket") |
| } |
| if hs.session == nil && len(hs.serverHello.sessionID) > 0 { |
| session.sessionID = hs.serverHello.sessionID |
| hs.session = session |
| } |
| return nil |
| } |
| |
| if c.config.Bugs.ExpectNoNewSessionTicket { |
| return errors.New("tls: received unexpected NewSessionTicket") |
| } |
| |
| msg, err := c.readHandshake() |
| if err != nil { |
| return err |
| } |
| sessionTicketMsg, ok := msg.(*newSessionTicketMsg) |
| if !ok { |
| c.sendAlert(alertUnexpectedMessage) |
| return unexpectedMessageError(sessionTicketMsg, msg) |
| } |
| |
| session.sessionTicket = sessionTicketMsg.ticket |
| hs.session = session |
| |
| hs.writeServerHash(sessionTicketMsg.marshal()) |
| |
| return nil |
| } |
| |
| func (hs *clientHandshakeState) sendFinished(out []byte, isResume bool) error { |
| c := hs.c |
| |
| var postCCSMsgs [][]byte |
| seqno := hs.c.sendHandshakeSeq |
| if hs.serverHello.extensions.nextProtoNeg { |
| nextProto := new(nextProtoMsg) |
| proto, fallback := mutualProtocol(c.config.NextProtos, hs.serverHello.extensions.nextProtos) |
| if fallback && c.config.NoFallbackNextProto { |
| proto = "" |
| fallback = false |
| } |
| nextProto.proto = proto |
| c.clientProtocol = proto |
| c.clientProtocolFallback = fallback |
| |
| nextProtoBytes := nextProto.marshal() |
| hs.finishedHash.WriteHandshake(nextProtoBytes, seqno) |
| seqno++ |
| postCCSMsgs = append(postCCSMsgs, nextProtoBytes) |
| } |
| |
| if hs.serverHello.extensions.channelIDRequested { |
| var resumeHash []byte |
| if isResume { |
| resumeHash = hs.session.handshakeHash |
| } |
| channelIDMsgBytes, err := hs.writeChannelIDMessage(hs.finishedHash.hashForChannelID(resumeHash)) |
| if err != nil { |
| return err |
| } |
| hs.finishedHash.WriteHandshake(channelIDMsgBytes, seqno) |
| seqno++ |
| postCCSMsgs = append(postCCSMsgs, channelIDMsgBytes) |
| } |
| |
| finished := new(finishedMsg) |
| if c.config.Bugs.EarlyChangeCipherSpec == 2 { |
| finished.verifyData = hs.finishedHash.clientSum(nil) |
| } else { |
| finished.verifyData = hs.finishedHash.clientSum(hs.masterSecret) |
| } |
| copy(out, finished.verifyData) |
| if c.config.Bugs.BadFinished { |
| finished.verifyData[0]++ |
| } |
| c.clientVerify = append(c.clientVerify[:0], finished.verifyData...) |
| hs.finishedBytes = finished.marshal() |
| hs.finishedHash.WriteHandshake(hs.finishedBytes, seqno) |
| if c.config.Bugs.PartialClientFinishedWithClientHello { |
| // The first byte has already been written. |
| postCCSMsgs = append(postCCSMsgs, hs.finishedBytes[1:]) |
| } else { |
| postCCSMsgs = append(postCCSMsgs, hs.finishedBytes) |
| } |
| |
| if c.config.Bugs.FragmentAcrossChangeCipherSpec { |
| c.writeRecord(recordTypeHandshake, postCCSMsgs[0][:5]) |
| postCCSMsgs[0] = postCCSMsgs[0][5:] |
| } else if c.config.Bugs.SendUnencryptedFinished { |
| c.writeRecord(recordTypeHandshake, postCCSMsgs[0]) |
| postCCSMsgs = postCCSMsgs[1:] |
| } |
| |
| if !c.config.Bugs.SkipChangeCipherSpec && |
| c.config.Bugs.EarlyChangeCipherSpec == 0 { |
| ccs := []byte{1} |
| if c.config.Bugs.BadChangeCipherSpec != nil { |
| ccs = c.config.Bugs.BadChangeCipherSpec |
| } |
| c.writeRecord(recordTypeChangeCipherSpec, ccs) |
| } |
| |
| if c.config.Bugs.AppDataAfterChangeCipherSpec != nil { |
| c.writeRecord(recordTypeApplicationData, c.config.Bugs.AppDataAfterChangeCipherSpec) |
| } |
| if c.config.Bugs.AlertAfterChangeCipherSpec != 0 { |
| c.sendAlert(c.config.Bugs.AlertAfterChangeCipherSpec) |
| return errors.New("tls: simulating post-CCS alert") |
| } |
| |
| if !c.config.Bugs.SkipFinished { |
| for _, msg := range postCCSMsgs { |
| c.writeRecord(recordTypeHandshake, msg) |
| } |
| |
| if c.config.Bugs.SendExtraFinished { |
| c.writeRecord(recordTypeHandshake, finished.marshal()) |
| } |
| } |
| |
| if !isResume || !c.config.Bugs.PackAppDataWithHandshake { |
| c.flushHandshake() |
| } |
| return nil |
| } |
| |
| func (hs *clientHandshakeState) writeChannelIDMessage(channelIDHash []byte) ([]byte, error) { |
| c := hs.c |
| channelIDMsg := new(channelIDMsg) |
| if c.config.ChannelID.Curve != elliptic.P256() { |
| return nil, fmt.Errorf("tls: Channel ID is not on P-256.") |
| } |
| r, s, err := ecdsa.Sign(c.config.rand(), c.config.ChannelID, channelIDHash) |
| if err != nil { |
| return nil, err |
| } |
| channelID := make([]byte, 128) |
| writeIntPadded(channelID[0:32], c.config.ChannelID.X) |
| writeIntPadded(channelID[32:64], c.config.ChannelID.Y) |
| writeIntPadded(channelID[64:96], r) |
| writeIntPadded(channelID[96:128], s) |
| if c.config.Bugs.InvalidChannelIDSignature { |
| channelID[64] ^= 1 |
| } |
| channelIDMsg.channelID = channelID |
| |
| c.channelID = &c.config.ChannelID.PublicKey |
| |
| return channelIDMsg.marshal(), nil |
| } |
| |
| func (hs *clientHandshakeState) writeClientHash(msg []byte) { |
| // writeClientHash is called before writeRecord. |
| hs.finishedHash.WriteHandshake(msg, hs.c.sendHandshakeSeq) |
| } |
| |
| func (hs *clientHandshakeState) writeServerHash(msg []byte) { |
| // writeServerHash is called after readHandshake. |
| hs.finishedHash.WriteHandshake(msg, hs.c.recvHandshakeSeq-1) |
| } |
| |
| // clientSessionCacheKey returns a key used to cache sessionTickets that could |
| // be used to resume previously negotiated TLS sessions with a server. |
| func clientSessionCacheKey(serverAddr net.Addr, config *Config) string { |
| if len(config.ServerName) > 0 { |
| return config.ServerName |
| } |
| return serverAddr.String() |
| } |
| |
| // mutualProtocol finds the mutual Next Protocol Negotiation or ALPN protocol |
| // given list of possible protocols and a list of the preference order. The |
| // first list must not be empty. It returns the resulting protocol and flag |
| // indicating if the fallback case was reached. |
| func mutualProtocol(protos, preferenceProtos []string) (string, bool) { |
| for _, s := range preferenceProtos { |
| if slices.Contains(protos, s) { |
| return s, false |
| } |
| } |
| |
| return protos[0], true |
| } |
| |
| // writeIntPadded writes x into b, padded up with leading zeros as |
| // needed. |
| func writeIntPadded(b []byte, x *big.Int) { |
| clear(b) |
| xb := x.Bytes() |
| copy(b[len(b)-len(xb):], xb) |
| } |
| |
| func generatePSKBinders(version uint16, isDTLS bool, hello *clientHelloMsg, session *ClientSessionState, firstClientHello, helloRetryRequest []byte, config *Config) { |
| maybeCorruptBinder := !config.Bugs.OnlyCorruptSecondPSKBinder || len(firstClientHello) > 0 |
| binderLen := session.cipherSuite.hash().Size() |
| numBinders := 1 |
| if maybeCorruptBinder { |
| if config.Bugs.SendNoPSKBinder { |
| // The binders may have been set from the previous |
| // ClientHello. |
| hello.pskBinders = nil |
| return |
| } |
| |
| if config.Bugs.SendShortPSKBinder { |
| binderLen-- |
| } |
| |
| if config.Bugs.SendExtraPSKBinder { |
| numBinders++ |
| } |
| } |
| |
| // Fill hello.pskBinders with appropriate length arrays of zeros so the |
| // length prefixes are correct when computing the binder over the truncated |
| // ClientHello message. |
| hello.pskBinders = make([][]byte, numBinders) |
| for i := range hello.pskBinders { |
| hello.pskBinders[i] = make([]byte, binderLen) |
| } |
| |
| helloBytes := hello.marshal() |
| binderSize := len(hello.pskBinders)*(binderLen+1) + 2 |
| truncatedHello := helloBytes[:len(helloBytes)-binderSize] |
| binder := computePSKBinder(session.secret, version, isDTLS, resumptionPSKBinderLabel, session.cipherSuite, firstClientHello, helloRetryRequest, truncatedHello) |
| if maybeCorruptBinder { |
| if config.Bugs.SendShortPSKBinder { |
| binder = binder[:binderLen] |
| } |
| if config.Bugs.SendInvalidPSKBinder { |
| binder[0] ^= 1 |
| } |
| } |
| |
| for i := range hello.pskBinders { |
| hello.pskBinders[i] = binder |
| } |
| |
| hello.raw = nil |
| } |