|
|
|
// 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"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"boringssl.googlesource.com/boringssl/ssl/test/runner/hpke"
|
|
|
|
)
|
|
|
|
|
|
|
|
const echBadPayloadByte = 0xff
|
|
|
|
|
|
|
|
type clientHandshakeState struct {
|
Update to draft-ietf-tls-esni-13.
Later CLs will clean up the ClientHello construction a bit (draft-12
avoids computing ClientHelloOuter twice). I suspect the transcript
handling on the client can also be simpler, but I'll see what's
convenient after I've changed how ClientHelloOuter is constructed.
Changes of note between draft-10 and draft-13:
- There is now an ECH confirmation signal in both HRR and SH. We don't
actually make much use of this in our client right now, but it
resolves a bunch of weird issues around HRR, including edge cases if
HRR applies to one ClientHello but not the other.
- The confirmation signal no longer depends on key_share and PSK, so we
don't have to work around a weird ordering issue.
- ech_is_inner is now folded into the main encrypted_client_hello code
point. This works better with some stuff around HRR.
- Padding is moved from the padding extension, computed with
ClientHelloInner, to something we fill in afterwards. This makes it
easier to pad up the whole thing to a multiple of 32. I've accordingly
updated to the latest recommended padding construction, and updated
the GREASE logic to match.
- ech_outer_extensions is much easier to process because the order is
required to be consistent. We were doing that anyway, and now a simple
linear scan works.
- ClientHelloOuterAAD now uses an all zero placeholder payload of the
same length. This lets us simplify the server code, but, for now, I've
kept the client code the same. I'll follow this up with a CL to avoid
computing ClientHelloOuter twice.
- ClientHelloOuterAAD is allowed to contain a placeholder PSK. I haven't
filled that in and will do it in a follow-up CL.
Bug: 275
Change-Id: I7464345125c53968b2fe692f9268e392120fc2eb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48912
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
4 years ago
|
|
|
c *Conn
|
|
|
|
serverHello *serverHelloMsg
|
|
|
|
hello *clientHelloMsg
|
|
|
|
innerHello *clientHelloMsg
|
|
|
|
echHPKEContext *hpke.Context
|
|
|
|
suite *cipherSuite
|
|
|
|
finishedHash finishedHash
|
|
|
|
keyShares map[CurveID]ecdhCurve
|
|
|
|
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 := append([]byte{}, 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 := newByteBuilder()
|
|
|
|
hello.marshalKeyShares(bb)
|
|
|
|
keyShares := bb.finish()
|
|
|
|
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]ecdhCurve),
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
for i := 0; i < skip; i++ {
|
|
|
|
hs.hello.random[i] = 0
|
|
|
|
}
|
|
|
|
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(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 _, wantSuite := range config.echCipherSuitePreferences() {
|
|
|
|
if config.Bugs.IgnoreECHConfigCipherPreferences {
|
|
|
|
return wantSuite, true
|
|
|
|
}
|
|
|
|
for _, cipherSuite := range echConfig.CipherSuites {
|
|
|
|
if cipherSuite == wantSuite {
|
|
|
|
return cipherSuite, 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.SRTPMasterKeyIdentifer,
|
|
|
|
customExtension: c.config.Bugs.CustomExtension,
|
|
|
|
omitExtensions: c.config.Bugs.OmitExtensions,
|
|
|
|
emptyExtensions: c.config.Bugs.EmptyExtensions,
|
|
|
|
delegatedCredentials: !c.config.Bugs.DisableDelegatedCredentials,
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
}
|
Update to draft-ietf-tls-esni-13.
Later CLs will clean up the ClientHello construction a bit (draft-12
avoids computing ClientHelloOuter twice). I suspect the transcript
handling on the client can also be simpler, but I'll see what's
convenient after I've changed how ClientHelloOuter is constructed.
Changes of note between draft-10 and draft-13:
- There is now an ECH confirmation signal in both HRR and SH. We don't
actually make much use of this in our client right now, but it
resolves a bunch of weird issues around HRR, including edge cases if
HRR applies to one ClientHello but not the other.
- The confirmation signal no longer depends on key_share and PSK, so we
don't have to work around a weird ordering issue.
- ech_is_inner is now folded into the main encrypted_client_hello code
point. This works better with some stuff around HRR.
- Padding is moved from the padding extension, computed with
ClientHelloInner, to something we fill in afterwards. This makes it
easier to pad up the whole thing to a multiple of 32. I've accordingly
updated to the latest recommended padding construction, and updated
the GREASE logic to match.
- ech_outer_extensions is much easier to process because the order is
required to be consistent. We were doing that anyway, and now a simple
linear scan works.
- ClientHelloOuterAAD now uses an all zero placeholder payload of the
same length. This lets us simplify the server code, but, for now, I've
kept the client code the same. I'll follow this up with a CL to avoid
computing ClientHelloOuter twice.
- ClientHelloOuterAAD is allowed to contain a placeholder PSK. I haven't
filled that in and will do it in a follow-up CL.
Bug: 275
Change-Id: I7464345125c53968b2fe692f9268e392120fc2eb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48912
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
4 years ago
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
for protocol := range c.config.ApplicationSettings {
|
|
|
|
hello.alpsProtocols = append(hello.alpsProtocols, 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
|
|
|
|
}
|
|
|
|
curve, ok := curveForCurveID(curveID, c.config)
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
publicKey, err := curve.offer(c.config.rand())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.config.Bugs.SendCurve != 0 {
|
|
|
|
curveID = c.config.Bugs.SendCurve
|
|
|
|
}
|
|
|
|
if c.config.Bugs.InvalidECDHPoint {
|
|
|
|
publicKey[0] ^= 0xff
|
|
|
|
}
|
|
|
|
|
|
|
|
hello.keyShares = append(hello.keyShares, keyShareEntry{
|
|
|
|
group: curveID,
|
|
|
|
keyExchange: publicKey,
|
|
|
|
})
|
|
|
|
hs.keyShares[curveID] = curve
|
|
|
|
|
|
|
|
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))
|
|
|
|
|
|
|
|
NextCipherSuite:
|
|
|
|
for _, suiteID := range possibleCipherSuites {
|
|
|
|
for _, suite := range cipherSuites {
|
|
|
|
if suite.id != suiteID {
|
|
|
|
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)
|
|
|
|
continue NextCipherSuite
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
Update to draft-ietf-tls-esni-13.
Later CLs will clean up the ClientHello construction a bit (draft-12
avoids computing ClientHelloOuter twice). I suspect the transcript
handling on the client can also be simpler, but I'll see what's
convenient after I've changed how ClientHelloOuter is constructed.
Changes of note between draft-10 and draft-13:
- There is now an ECH confirmation signal in both HRR and SH. We don't
actually make much use of this in our client right now, but it
resolves a bunch of weird issues around HRR, including edge cases if
HRR applies to one ClientHello but not the other.
- The confirmation signal no longer depends on key_share and PSK, so we
don't have to work around a weird ordering issue.
- ech_is_inner is now folded into the main encrypted_client_hello code
point. This works better with some stuff around HRR.
- Padding is moved from the padding extension, computed with
ClientHelloInner, to something we fill in afterwards. This makes it
easier to pad up the whole thing to a multiple of 32. I've accordingly
updated to the latest recommended padding construction, and updated
the GREASE logic to match.
- ech_outer_extensions is much easier to process because the order is
required to be consistent. We were doing that anyway, and now a simple
linear scan works.
- ClientHelloOuterAAD now uses an all zero placeholder payload of the
same length. This lets us simplify the server code, but, for now, I've
kept the client code the same. I'll follow this up with a CL to avoid
computing ClientHelloOuter twice.
- ClientHelloOuterAAD is allowed to contain a placeholder PSK. I haven't
filled that in and will do it in a follow-up CL.
Bug: 275
Change-Id: I7464345125c53968b2fe692f9268e392120fc2eb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48912
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
4 years ago
|
|
|
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 {
|
Update to draft-ietf-tls-esni-13.
Later CLs will clean up the ClientHello construction a bit (draft-12
avoids computing ClientHelloOuter twice). I suspect the transcript
handling on the client can also be simpler, but I'll see what's
convenient after I've changed how ClientHelloOuter is constructed.
Changes of note between draft-10 and draft-13:
- There is now an ECH confirmation signal in both HRR and SH. We don't
actually make much use of this in our client right now, but it
resolves a bunch of weird issues around HRR, including edge cases if
HRR applies to one ClientHello but not the other.
- The confirmation signal no longer depends on key_share and PSK, so we
don't have to work around a weird ordering issue.
- ech_is_inner is now folded into the main encrypted_client_hello code
point. This works better with some stuff around HRR.
- Padding is moved from the padding extension, computed with
ClientHelloInner, to something we fill in afterwards. This makes it
easier to pad up the whole thing to a multiple of 32. I've accordingly
updated to the latest recommended padding construction, and updated
the GREASE logic to match.
- ech_outer_extensions is much easier to process because the order is
required to be consistent. We were doing that anyway, and now a simple
linear scan works.
- ClientHelloOuterAAD now uses an all zero placeholder payload of the
same length. This lets us simplify the server code, but, for now, I've
kept the client code the same. I'll follow this up with a CL to avoid
computing ClientHelloOuter twice.
- ClientHelloOuterAAD is allowed to contain a placeholder PSK. I haven't
filled that in and will do it in a follow-up CL.
Bug: 275
Change-Id: I7464345125c53968b2fe692f9268e392120fc2eb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48912
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
4 years ago
|
|
|
hello.echOuter.payload = []byte{echBadPayloadByte}
|
|
|
|
} else {
|
Update to draft-ietf-tls-esni-13.
Later CLs will clean up the ClientHello construction a bit (draft-12
avoids computing ClientHelloOuter twice). I suspect the transcript
handling on the client can also be simpler, but I'll see what's
convenient after I've changed how ClientHelloOuter is constructed.
Changes of note between draft-10 and draft-13:
- There is now an ECH confirmation signal in both HRR and SH. We don't
actually make much use of this in our client right now, but it
resolves a bunch of weird issues around HRR, including edge cases if
HRR applies to one ClientHello but not the other.
- The confirmation signal no longer depends on key_share and PSK, so we
don't have to work around a weird ordering issue.
- ech_is_inner is now folded into the main encrypted_client_hello code
point. This works better with some stuff around HRR.
- Padding is moved from the padding extension, computed with
ClientHelloInner, to something we fill in afterwards. This makes it
easier to pad up the whole thing to a multiple of 32. I've accordingly
updated to the latest recommended padding construction, and updated
the GREASE logic to match.
- ech_outer_extensions is much easier to process because the order is
required to be consistent. We were doing that anyway, and now a simple
linear scan works.
- ClientHelloOuterAAD now uses an all zero placeholder payload of the
same length. This lets us simplify the server code, but, for now, I've
kept the client code the same. I'll follow this up with a CL to avoid
computing ClientHelloOuter twice.
- ClientHelloOuterAAD is allowed to contain a placeholder PSK. I haven't
filled that in and will do it in a follow-up CL.
Bug: 275
Change-Id: I7464345125c53968b2fe692f9268e392120fc2eb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48912
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
4 years ago
|
|
|
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
|
|
|
|
}
|
|
|
|
generatePSKBinders(version, 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()
|
Update to draft-ietf-tls-esni-13.
Later CLs will clean up the ClientHello construction a bit (draft-12
avoids computing ClientHelloOuter twice). I suspect the transcript
handling on the client can also be simpler, but I'll see what's
convenient after I've changed how ClientHelloOuter is constructed.
Changes of note between draft-10 and draft-13:
- There is now an ECH confirmation signal in both HRR and SH. We don't
actually make much use of this in our client right now, but it
resolves a bunch of weird issues around HRR, including edge cases if
HRR applies to one ClientHello but not the other.
- The confirmation signal no longer depends on key_share and PSK, so we
don't have to work around a weird ordering issue.
- ech_is_inner is now folded into the main encrypted_client_hello code
point. This works better with some stuff around HRR.
- Padding is moved from the padding extension, computed with
ClientHelloInner, to something we fill in afterwards. This makes it
easier to pad up the whole thing to a multiple of 32. I've accordingly
updated to the latest recommended padding construction, and updated
the GREASE logic to match.
- ech_outer_extensions is much easier to process because the order is
required to be consistent. We were doing that anyway, and now a simple
linear scan works.
- ClientHelloOuterAAD now uses an all zero placeholder payload of the
same length. This lets us simplify the server code, but, for now, I've
kept the client code the same. I'll follow this up with a CL to avoid
computing ClientHelloOuter twice.
- ClientHelloOuterAAD is allowed to contain a placeholder PSK. I haven't
filled that in and will do it in a follow-up CL.
Bug: 275
Change-Id: I7464345125c53968b2fe692f9268e392120fc2eb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48912
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
4 years ago
|
|
|
padding := make([]byte, c.config.Bugs.ClientECHPadding)
|
|
|
|
if c.config.Bugs.BadClientECHPadding {
|
|
|
|
padding[0] = 1
|
|
|
|
}
|
Update to draft-ietf-tls-esni-13.
Later CLs will clean up the ClientHello construction a bit (draft-12
avoids computing ClientHelloOuter twice). I suspect the transcript
handling on the client can also be simpler, but I'll see what's
convenient after I've changed how ClientHelloOuter is constructed.
Changes of note between draft-10 and draft-13:
- There is now an ECH confirmation signal in both HRR and SH. We don't
actually make much use of this in our client right now, but it
resolves a bunch of weird issues around HRR, including edge cases if
HRR applies to one ClientHello but not the other.
- The confirmation signal no longer depends on key_share and PSK, so we
don't have to work around a weird ordering issue.
- ech_is_inner is now folded into the main encrypted_client_hello code
point. This works better with some stuff around HRR.
- Padding is moved from the padding extension, computed with
ClientHelloInner, to something we fill in afterwards. This makes it
easier to pad up the whole thing to a multiple of 32. I've accordingly
updated to the latest recommended padding construction, and updated
the GREASE logic to match.
- ech_outer_extensions is much easier to process because the order is
required to be consistent. We were doing that anyway, and now a simple
linear scan works.
- ClientHelloOuterAAD now uses an all zero placeholder payload of the
same length. This lets us simplify the server code, but, for now, I've
kept the client code the same. I'll follow this up with a CL to avoid
computing ClientHelloOuter twice.
- ClientHelloOuterAAD is allowed to contain a placeholder PSK. I haven't
filled that in and will do it in a follow-up CL.
Bug: 275
Change-Id: I7464345125c53968b2fe692f9268e392120fc2eb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48912
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
4 years ago
|
|
|
encodedInner = append(encodedInner, padding...)
|
|
|
|
|
Update to draft-ietf-tls-esni-13.
Later CLs will clean up the ClientHello construction a bit (draft-12
avoids computing ClientHelloOuter twice). I suspect the transcript
handling on the client can also be simpler, but I'll see what's
convenient after I've changed how ClientHelloOuter is constructed.
Changes of note between draft-10 and draft-13:
- There is now an ECH confirmation signal in both HRR and SH. We don't
actually make much use of this in our client right now, but it
resolves a bunch of weird issues around HRR, including edge cases if
HRR applies to one ClientHello but not the other.
- The confirmation signal no longer depends on key_share and PSK, so we
don't have to work around a weird ordering issue.
- ech_is_inner is now folded into the main encrypted_client_hello code
point. This works better with some stuff around HRR.
- Padding is moved from the padding extension, computed with
ClientHelloInner, to something we fill in afterwards. This makes it
easier to pad up the whole thing to a multiple of 32. I've accordingly
updated to the latest recommended padding construction, and updated
the GREASE logic to match.
- ech_outer_extensions is much easier to process because the order is
required to be consistent. We were doing that anyway, and now a simple
linear scan works.
- ClientHelloOuterAAD now uses an all zero placeholder payload of the
same length. This lets us simplify the server code, but, for now, I've
kept the client code the same. I'll follow this up with a CL to avoid
computing ClientHelloOuter twice.
- ClientHelloOuterAAD is allowed to contain a placeholder PSK. I haven't
filled that in and will do it in a follow-up CL.
Bug: 275
Change-Id: I7464345125c53968b2fe692f9268e392120fc2eb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48912
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
4 years ago
|
|
|
// 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,
|
Update to draft-ietf-tls-esni-13.
Later CLs will clean up the ClientHello construction a bit (draft-12
avoids computing ClientHelloOuter twice). I suspect the transcript
handling on the client can also be simpler, but I'll see what's
convenient after I've changed how ClientHelloOuter is constructed.
Changes of note between draft-10 and draft-13:
- There is now an ECH confirmation signal in both HRR and SH. We don't
actually make much use of this in our client right now, but it
resolves a bunch of weird issues around HRR, including edge cases if
HRR applies to one ClientHello but not the other.
- The confirmation signal no longer depends on key_share and PSK, so we
don't have to work around a weird ordering issue.
- ech_is_inner is now folded into the main encrypted_client_hello code
point. This works better with some stuff around HRR.
- Padding is moved from the padding extension, computed with
ClientHelloInner, to something we fill in afterwards. This makes it
easier to pad up the whole thing to a multiple of 32. I've accordingly
updated to the latest recommended padding construction, and updated
the GREASE logic to match.
- ech_outer_extensions is much easier to process because the order is
required to be consistent. We were doing that anyway, and now a simple
linear scan works.
- ClientHelloOuterAAD now uses an all zero placeholder payload of the
same length. This lets us simplify the server code, but, for now, I've
kept the client code the same. I'll follow this up with a CL to avoid
computing ClientHelloOuter twice.
- ClientHelloOuterAAD is allowed to contain a placeholder PSK. I haven't
filled that in and will do it in a follow-up CL.
Bug: 275
Change-Id: I7464345125c53968b2fe692f9268e392120fc2eb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48912
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
4 years ago
|
|
|
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
|
|
|
|
}
|
|
|
|
|
Update to draft-ietf-tls-esni-13.
Later CLs will clean up the ClientHello construction a bit (draft-12
avoids computing ClientHelloOuter twice). I suspect the transcript
handling on the client can also be simpler, but I'll see what's
convenient after I've changed how ClientHelloOuter is constructed.
Changes of note between draft-10 and draft-13:
- There is now an ECH confirmation signal in both HRR and SH. We don't
actually make much use of this in our client right now, but it
resolves a bunch of weird issues around HRR, including edge cases if
HRR applies to one ClientHello but not the other.
- The confirmation signal no longer depends on key_share and PSK, so we
don't have to work around a weird ordering issue.
- ech_is_inner is now folded into the main encrypted_client_hello code
point. This works better with some stuff around HRR.
- Padding is moved from the padding extension, computed with
ClientHelloInner, to something we fill in afterwards. This makes it
easier to pad up the whole thing to a multiple of 32. I've accordingly
updated to the latest recommended padding construction, and updated
the GREASE logic to match.
- ech_outer_extensions is much easier to process because the order is
required to be consistent. We were doing that anyway, and now a simple
linear scan works.
- ClientHelloOuterAAD now uses an all zero placeholder payload of the
same length. This lets us simplify the server code, but, for now, I've
kept the client code the same. I'll follow this up with a CL to avoid
computing ClientHelloOuter twice.
- ClientHelloOuterAAD is allowed to contain a placeholder PSK. I haven't
filled that in and will do it in a follow-up CL.
Bug: 275
Change-Id: I7464345125c53968b2fe692f9268e392120fc2eb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48912
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
4 years ago
|
|
|
func (hs *clientHandshakeState) checkECHConfirmation(msg interface{}, 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 := append(make([]byte, 0, len(raw)), raw...)
|
|
|
|
for i := 0; i < echAcceptConfirmationLength; i++ {
|
|
|
|
withZeros[i+offset] = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
confirmation := finishedHash.echAcceptConfirmation(hello.random, label, withZeros)
|
|
|
|
return bytes.Equal(confirmation, raw[offset:offset+echAcceptConfirmationLength])
|
|
|
|
}
|
|
|
|
|
|
|
|
func (hs *clientHandshakeState) doTLS13Handshake(msg interface{}) error {
|
|
|
|
c := hs.c
|
|
|
|
|
Update to draft-ietf-tls-esni-13.
Later CLs will clean up the ClientHello construction a bit (draft-12
avoids computing ClientHelloOuter twice). I suspect the transcript
handling on the client can also be simpler, but I'll see what's
convenient after I've changed how ClientHelloOuter is constructed.
Changes of note between draft-10 and draft-13:
- There is now an ECH confirmation signal in both HRR and SH. We don't
actually make much use of this in our client right now, but it
resolves a bunch of weird issues around HRR, including edge cases if
HRR applies to one ClientHello but not the other.
- The confirmation signal no longer depends on key_share and PSK, so we
don't have to work around a weird ordering issue.
- ech_is_inner is now folded into the main encrypted_client_hello code
point. This works better with some stuff around HRR.
- Padding is moved from the padding extension, computed with
ClientHelloInner, to something we fill in afterwards. This makes it
easier to pad up the whole thing to a multiple of 32. I've accordingly
updated to the latest recommended padding construction, and updated
the GREASE logic to match.
- ech_outer_extensions is much easier to process because the order is
required to be consistent. We were doing that anyway, and now a simple
linear scan works.
- ClientHelloOuterAAD now uses an all zero placeholder payload of the
same length. This lets us simplify the server code, but, for now, I've
kept the client code the same. I'll follow this up with a CL to avoid
computing ClientHelloOuter twice.
- ClientHelloOuterAAD is allowed to contain a placeholder PSK. I haven't
filled that in and will do it in a follow-up CL.
Bug: 275
Change-Id: I7464345125c53968b2fe692f9268e392120fc2eb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48912
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
4 years ago
|
|
|
// 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 = true
|
|
|
|
|
|
|
|
if haveHelloRetryRequest {
|
|
|
|
hs.writeServerHash(helloRetryRequest.marshal())
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
Update to draft-ietf-tls-esni-13.
Later CLs will clean up the ClientHello construction a bit (draft-12
avoids computing ClientHelloOuter twice). I suspect the transcript
handling on the client can also be simpler, but I'll see what's
convenient after I've changed how ClientHelloOuter is constructed.
Changes of note between draft-10 and draft-13:
- There is now an ECH confirmation signal in both HRR and SH. We don't
actually make much use of this in our client right now, but it
resolves a bunch of weird issues around HRR, including edge cases if
HRR applies to one ClientHello but not the other.
- The confirmation signal no longer depends on key_share and PSK, so we
don't have to work around a weird ordering issue.
- ech_is_inner is now folded into the main encrypted_client_hello code
point. This works better with some stuff around HRR.
- Padding is moved from the padding extension, computed with
ClientHelloInner, to something we fill in afterwards. This makes it
easier to pad up the whole thing to a multiple of 32. I've accordingly
updated to the latest recommended padding construction, and updated
the GREASE logic to match.
- ech_outer_extensions is much easier to process because the order is
required to be consistent. We were doing that anyway, and now a simple
linear scan works.
- ClientHelloOuterAAD now uses an all zero placeholder payload of the
same length. This lets us simplify the server code, but, for now, I've
kept the client code the same. I'll follow this up with a CL to avoid
computing ClientHelloOuter twice.
- ClientHelloOuterAAD is allowed to contain a placeholder PSK. I haven't
filled that in and will do it in a follow-up CL.
Bug: 275
Change-Id: I7464345125c53968b2fe692f9268e392120fc2eb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48912
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
4 years ago
|
|
|
if c.echAccepted {
|
|
|
|
if err := hs.applyHelloRetryRequest(helloRetryRequest, hs.innerHello, hs.hello); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
Update to draft-ietf-tls-esni-13.
Later CLs will clean up the ClientHello construction a bit (draft-12
avoids computing ClientHelloOuter twice). I suspect the transcript
handling on the client can also be simpler, but I'll see what's
convenient after I've changed how ClientHelloOuter is constructed.
Changes of note between draft-10 and draft-13:
- There is now an ECH confirmation signal in both HRR and SH. We don't
actually make much use of this in our client right now, but it
resolves a bunch of weird issues around HRR, including edge cases if
HRR applies to one ClientHello but not the other.
- The confirmation signal no longer depends on key_share and PSK, so we
don't have to work around a weird ordering issue.
- ech_is_inner is now folded into the main encrypted_client_hello code
point. This works better with some stuff around HRR.
- Padding is moved from the padding extension, computed with
ClientHelloInner, to something we fill in afterwards. This makes it
easier to pad up the whole thing to a multiple of 32. I've accordingly
updated to the latest recommended padding construction, and updated
the GREASE logic to match.
- ech_outer_extensions is much easier to process because the order is
required to be consistent. We were doing that anyway, and now a simple
linear scan works.
- ClientHelloOuterAAD now uses an all zero placeholder payload of the
same length. This lets us simplify the server code, but, for now, I've
kept the client code the same. I'll follow this up with a CL to avoid
computing ClientHelloOuter twice.
- ClientHelloOuterAAD is allowed to contain a placeholder PSK. I haven't
filled that in and will do it in a follow-up CL.
Bug: 275
Change-Id: I7464345125c53968b2fe692f9268e392120fc2eb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48912
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
4 years ago
|
|
|
hs.writeClientHash(hs.innerHello.marshal())
|
|
|
|
} else {
|
|
|
|
if err := hs.applyHelloRetryRequest(helloRetryRequest, hs.hello, nil); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
Update to draft-ietf-tls-esni-13.
Later CLs will clean up the ClientHello construction a bit (draft-12
avoids computing ClientHelloOuter twice). I suspect the transcript
handling on the client can also be simpler, but I'll see what's
convenient after I've changed how ClientHelloOuter is constructed.
Changes of note between draft-10 and draft-13:
- There is now an ECH confirmation signal in both HRR and SH. We don't
actually make much use of this in our client right now, but it
resolves a bunch of weird issues around HRR, including edge cases if
HRR applies to one ClientHello but not the other.
- The confirmation signal no longer depends on key_share and PSK, so we
don't have to work around a weird ordering issue.
- ech_is_inner is now folded into the main encrypted_client_hello code
point. This works better with some stuff around HRR.
- Padding is moved from the padding extension, computed with
ClientHelloInner, to something we fill in afterwards. This makes it
easier to pad up the whole thing to a multiple of 32. I've accordingly
updated to the latest recommended padding construction, and updated
the GREASE logic to match.
- ech_outer_extensions is much easier to process because the order is
required to be consistent. We were doing that anyway, and now a simple
linear scan works.
- ClientHelloOuterAAD now uses an all zero placeholder payload of the
same length. This lets us simplify the server code, but, for now, I've
kept the client code the same. I'll follow this up with a CL to avoid
computing ClientHelloOuter twice.
- ClientHelloOuterAAD is allowed to contain a placeholder PSK. I haven't
filled that in and will do it in a follow-up CL.
Bug: 275
Change-Id: I7464345125c53968b2fe692f9268e392120fc2eb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48912
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
4 years ago
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Update to draft-ietf-tls-esni-13.
Later CLs will clean up the ClientHello construction a bit (draft-12
avoids computing ClientHelloOuter twice). I suspect the transcript
handling on the client can also be simpler, but I'll see what's
convenient after I've changed how ClientHelloOuter is constructed.
Changes of note between draft-10 and draft-13:
- There is now an ECH confirmation signal in both HRR and SH. We don't
actually make much use of this in our client right now, but it
resolves a bunch of weird issues around HRR, including edge cases if
HRR applies to one ClientHello but not the other.
- The confirmation signal no longer depends on key_share and PSK, so we
don't have to work around a weird ordering issue.
- ech_is_inner is now folded into the main encrypted_client_hello code
point. This works better with some stuff around HRR.
- Padding is moved from the padding extension, computed with
ClientHelloInner, to something we fill in afterwards. This makes it
easier to pad up the whole thing to a multiple of 32. I've accordingly
updated to the latest recommended padding construction, and updated
the GREASE logic to match.
- ech_outer_extensions is much easier to process because the order is
required to be consistent. We were doing that anyway, and now a simple
linear scan works.
- ClientHelloOuterAAD now uses an all zero placeholder payload of the
same length. This lets us simplify the server code, but, for now, I've
kept the client code the same. I'll follow this up with a CL to avoid
computing ClientHelloOuter twice.
- ClientHelloOuterAAD is allowed to contain a placeholder PSK. I haven't
filled that in and will do it in a follow-up CL.
Bug: 275
Change-Id: I7464345125c53968b2fe692f9268e392120fc2eb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48912
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
4 years ago
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
Update to draft-ietf-tls-esni-13.
Later CLs will clean up the ClientHello construction a bit (draft-12
avoids computing ClientHelloOuter twice). I suspect the transcript
handling on the client can also be simpler, but I'll see what's
convenient after I've changed how ClientHelloOuter is constructed.
Changes of note between draft-10 and draft-13:
- There is now an ECH confirmation signal in both HRR and SH. We don't
actually make much use of this in our client right now, but it
resolves a bunch of weird issues around HRR, including edge cases if
HRR applies to one ClientHello but not the other.
- The confirmation signal no longer depends on key_share and PSK, so we
don't have to work around a weird ordering issue.
- ech_is_inner is now folded into the main encrypted_client_hello code
point. This works better with some stuff around HRR.
- Padding is moved from the padding extension, computed with
ClientHelloInner, to something we fill in afterwards. This makes it
easier to pad up the whole thing to a multiple of 32. I've accordingly
updated to the latest recommended padding construction, and updated
the GREASE logic to match.
- ech_outer_extensions is much easier to process because the order is
required to be consistent. We were doing that anyway, and now a simple
linear scan works.
- ClientHelloOuterAAD now uses an all zero placeholder payload of the
same length. This lets us simplify the server code, but, for now, I've
kept the client code the same. I'll follow this up with a CL to avoid
computing ClientHelloOuter twice.
- ClientHelloOuterAAD is allowed to contain a placeholder PSK. I haven't
filled that in and will do it in a follow-up CL.
Bug: 275
Change-Id: I7464345125c53968b2fe692f9268e392120fc2eb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48912
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
4 years ago
|
|
|
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(hs.hello.sessionID, hs.serverHello.sessionID) {
|
|
|
|
return errors.New("tls: 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 {
|
|
|
|
curve, 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 = curve.finish(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(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 chainToSend *Certificate
|
|
|
|
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")
|
|
|
|
}
|
|
|
|
|
|
|
|
if c.config.Bugs.IgnorePeerSignatureAlgorithmPreferences {
|
|
|
|
certReq.signatureAlgorithms = c.config.signSignatureAlgorithms()
|
|
|
|
}
|
|
|
|
|
|
|
|
hs.writeServerHash(certReq.marshal())
|
|
|
|
|
|
|
|
chainToSend, err = selectClientCertificate(c, certReq)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
err = verifyMessage(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(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.data[c.input.off:], expectedMsg) {
|
|
|
|
return errors.New("ExpectHalfRTTData: did not get expected message")
|
|
|
|
}
|
|
|
|
c.in.freeBlock(c.input)
|
|
|
|
c.input = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send EndOfEarlyData and then switch write key to handshake
|
|
|
|
// traffic key.
|
|
|
|
if encryptedExtensions.extensions.hasEarlyData && !c.config.Bugs.SkipEndOfEarlyData && c.config.Bugs.MockQUICTransport == nil {
|
|
|
|
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.writeRecord(recordTypeChangeCipherSpec, []byte{1})
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < c.config.Bugs.SendExtraChangeCipherSpec; i++ {
|
|
|
|
c.writeRecord(recordTypeChangeCipherSpec, []byte{1})
|
|
|
|
}
|
|
|
|
|
|
|
|
c.useOutTrafficSecret(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 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 chainToSend != nil {
|
|
|
|
for _, certData := range chainToSend.Certificate {
|
|
|
|
certMsg.certificates = append(certMsg.certificates, certificateEntry{
|
|
|
|
data: certData,
|
|
|
|
extraExtension: c.config.Bugs.SendExtensionOnCertificate,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
hs.writeClientHash(certMsg.marshal())
|
|
|
|
c.writeRecord(recordTypeHandshake, certMsg.marshal())
|
|
|
|
|
|
|
|
if chainToSend != nil {
|
|
|
|
certVerify := &certificateVerifyMsg{
|
|
|
|
hasSignatureAlgorithm: true,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine the hash to sign.
|
|
|
|
privKey := chainToSend.PrivateKey
|
|
|
|
|
|
|
|
var err error
|
|
|
|
certVerify.signatureAlgorithm, err = selectSignatureAlgorithm(c.vers, privKey, c.config, certReq.signatureAlgorithms)
|
|
|
|
if err != nil {
|
|
|
|
c.sendAlert(alertInternalError)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
input := hs.finishedHash.certificateVerifyInput(clientCertificateVerifyContextTLS13)
|
|
|
|
certVerify.signature, err = signMessage(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()
|
|
|
|
|
|
|
|
// Switch to application data keys.
|
|
|
|
c.useOutTrafficSecret(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|.
|
Update to draft-ietf-tls-esni-13.
Later CLs will clean up the ClientHello construction a bit (draft-12
avoids computing ClientHelloOuter twice). I suspect the transcript
handling on the client can also be simpler, but I'll see what's
convenient after I've changed how ClientHelloOuter is constructed.
Changes of note between draft-10 and draft-13:
- There is now an ECH confirmation signal in both HRR and SH. We don't
actually make much use of this in our client right now, but it
resolves a bunch of weird issues around HRR, including edge cases if
HRR applies to one ClientHello but not the other.
- The confirmation signal no longer depends on key_share and PSK, so we
don't have to work around a weird ordering issue.
- ech_is_inner is now folded into the main encrypted_client_hello code
point. This works better with some stuff around HRR.
- Padding is moved from the padding extension, computed with
ClientHelloInner, to something we fill in afterwards. This makes it
easier to pad up the whole thing to a multiple of 32. I've accordingly
updated to the latest recommended padding construction, and updated
the GREASE logic to match.
- ech_outer_extensions is much easier to process because the order is
required to be consistent. We were doing that anyway, and now a simple
linear scan works.
- ClientHelloOuterAAD now uses an all zero placeholder payload of the
same length. This lets us simplify the server code, but, for now, I've
kept the client code the same. I'll follow this up with a CL to avoid
computing ClientHelloOuter twice.
- ClientHelloOuterAAD is allowed to contain a placeholder PSK. I haven't
filled that in and will do it in a follow-up CL.
Bug: 275
Change-Id: I7464345125c53968b2fe692f9268e392120fc2eb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48912
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
4 years ago
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
Update to draft-ietf-tls-esni-13.
Later CLs will clean up the ClientHello construction a bit (draft-12
avoids computing ClientHelloOuter twice). I suspect the transcript
handling on the client can also be simpler, but I'll see what's
convenient after I've changed how ClientHelloOuter is constructed.
Changes of note between draft-10 and draft-13:
- There is now an ECH confirmation signal in both HRR and SH. We don't
actually make much use of this in our client right now, but it
resolves a bunch of weird issues around HRR, including edge cases if
HRR applies to one ClientHello but not the other.
- The confirmation signal no longer depends on key_share and PSK, so we
don't have to work around a weird ordering issue.
- ech_is_inner is now folded into the main encrypted_client_hello code
point. This works better with some stuff around HRR.
- Padding is moved from the padding extension, computed with
ClientHelloInner, to something we fill in afterwards. This makes it
easier to pad up the whole thing to a multiple of 32. I've accordingly
updated to the latest recommended padding construction, and updated
the GREASE logic to match.
- ech_outer_extensions is much easier to process because the order is
required to be consistent. We were doing that anyway, and now a simple
linear scan works.
- ClientHelloOuterAAD now uses an all zero placeholder payload of the
same length. This lets us simplify the server code, but, for now, I've
kept the client code the same. I'll follow this up with a CL to avoid
computing ClientHelloOuter twice.
- ClientHelloOuterAAD is allowed to contain a placeholder PSK. I haven't
filled that in and will do it in a follow-up CL.
Bug: 275
Change-Id: I7464345125c53968b2fe692f9268e392120fc2eb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48912
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
4 years ago
|
|
|
if c.config.Bugs.MisinterpretHelloRetryRequestCurve != 0 {
|
|
|
|
helloRetryRequest.hasSelectedGroup = true
|
|
|
|
helloRetryRequest.selectedGroup = c.config.Bugs.MisinterpretHelloRetryRequestCurve
|
|
|
|
}
|
|
|
|
if helloRetryRequest.hasSelectedGroup {
|
|
|
|
var hrrCurveFound bool
|
|
|
|
group := helloRetryRequest.selectedGroup
|
|
|
|
for _, curveID := range hello.supportedCurves {
|
|
|
|
if group == curveID {
|
|
|
|
hrrCurveFound = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
Update to draft-ietf-tls-esni-13.
Later CLs will clean up the ClientHello construction a bit (draft-12
avoids computing ClientHelloOuter twice). I suspect the transcript
handling on the client can also be simpler, but I'll see what's
convenient after I've changed how ClientHelloOuter is constructed.
Changes of note between draft-10 and draft-13:
- There is now an ECH confirmation signal in both HRR and SH. We don't
actually make much use of this in our client right now, but it
resolves a bunch of weird issues around HRR, including edge cases if
HRR applies to one ClientHello but not the other.
- The confirmation signal no longer depends on key_share and PSK, so we
don't have to work around a weird ordering issue.
- ech_is_inner is now folded into the main encrypted_client_hello code
point. This works better with some stuff around HRR.
- Padding is moved from the padding extension, computed with
ClientHelloInner, to something we fill in afterwards. This makes it
easier to pad up the whole thing to a multiple of 32. I've accordingly
updated to the latest recommended padding construction, and updated
the GREASE logic to match.
- ech_outer_extensions is much easier to process because the order is
required to be consistent. We were doing that anyway, and now a simple
linear scan works.
- ClientHelloOuterAAD now uses an all zero placeholder payload of the
same length. This lets us simplify the server code, but, for now, I've
kept the client code the same. I'll follow this up with a CL to avoid
computing ClientHelloOuter twice.
- ClientHelloOuterAAD is allowed to contain a placeholder PSK. I haven't
filled that in and will do it in a follow-up CL.
Bug: 275
Change-Id: I7464345125c53968b2fe692f9268e392120fc2eb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48912
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
4 years ago
|
|
|
if !hrrCurveFound || hs.keyShares[group] != nil {
|
|
|
|
c.sendAlert(alertHandshakeFailure)
|
|
|
|
return errors.New("tls: received invalid HelloRetryRequest")
|
|
|
|
}
|
|
|
|
curve, ok := curveForCurveID(group, c.config)
|
|
|
|
if !ok {
|
|
|
|
return errors.New("tls: Unable to get curve requested in HelloRetryRequest")
|
|
|
|
}
|
Update to draft-ietf-tls-esni-13.
Later CLs will clean up the ClientHello construction a bit (draft-12
avoids computing ClientHelloOuter twice). I suspect the transcript
handling on the client can also be simpler, but I'll see what's
convenient after I've changed how ClientHelloOuter is constructed.
Changes of note between draft-10 and draft-13:
- There is now an ECH confirmation signal in both HRR and SH. We don't
actually make much use of this in our client right now, but it
resolves a bunch of weird issues around HRR, including edge cases if
HRR applies to one ClientHello but not the other.
- The confirmation signal no longer depends on key_share and PSK, so we
don't have to work around a weird ordering issue.
- ech_is_inner is now folded into the main encrypted_client_hello code
point. This works better with some stuff around HRR.
- Padding is moved from the padding extension, computed with
ClientHelloInner, to something we fill in afterwards. This makes it
easier to pad up the whole thing to a multiple of 32. I've accordingly
updated to the latest recommended padding construction, and updated
the GREASE logic to match.
- ech_outer_extensions is much easier to process because the order is
required to be consistent. We were doing that anyway, and now a simple
linear scan works.
- ClientHelloOuterAAD now uses an all zero placeholder payload of the
same length. This lets us simplify the server code, but, for now, I've
kept the client code the same. I'll follow this up with a CL to avoid
computing ClientHelloOuter twice.
- ClientHelloOuterAAD is allowed to contain a placeholder PSK. I haven't
filled that in and will do it in a follow-up CL.
Bug: 275
Change-Id: I7464345125c53968b2fe692f9268e392120fc2eb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48912
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
4 years ago
|
|
|
publicKey, err := curve.offer(c.config.rand())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
hs.keyShares[group] = curve
|
|
|
|
hello.keyShares = []keyShareEntry{{
|
|
|
|
group: group,
|
|
|
|
keyExchange: publicKey,
|
|
|
|
}}
|
|
|
|
}
|
|
|
|
|
Update to draft-ietf-tls-esni-13.
Later CLs will clean up the ClientHello construction a bit (draft-12
avoids computing ClientHelloOuter twice). I suspect the transcript
handling on the client can also be simpler, but I'll see what's
convenient after I've changed how ClientHelloOuter is constructed.
Changes of note between draft-10 and draft-13:
- There is now an ECH confirmation signal in both HRR and SH. We don't
actually make much use of this in our client right now, but it
resolves a bunch of weird issues around HRR, including edge cases if
HRR applies to one ClientHello but not the other.
- The confirmation signal no longer depends on key_share and PSK, so we
don't have to work around a weird ordering issue.
- ech_is_inner is now folded into the main encrypted_client_hello code
point. This works better with some stuff around HRR.
- Padding is moved from the padding extension, computed with
ClientHelloInner, to something we fill in afterwards. This makes it
easier to pad up the whole thing to a multiple of 32. I've accordingly
updated to the latest recommended padding construction, and updated
the GREASE logic to match.
- ech_outer_extensions is much easier to process because the order is
required to be consistent. We were doing that anyway, and now a simple
linear scan works.
- ClientHelloOuterAAD now uses an all zero placeholder payload of the
same length. This lets us simplify the server code, but, for now, I've
kept the client code the same. I'll follow this up with a CL to avoid
computing ClientHelloOuter twice.
- ClientHelloOuterAAD is allowed to contain a placeholder PSK. I haven't
filled that in and will do it in a follow-up CL.
Bug: 275
Change-Id: I7464345125c53968b2fe692f9268e392120fc2eb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48912
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
4 years ago
|
|
|
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)
|
|
|
|
}
|
Update to draft-ietf-tls-esni-13.
Later CLs will clean up the ClientHello construction a bit (draft-12
avoids computing ClientHelloOuter twice). I suspect the transcript
handling on the client can also be simpler, but I'll see what's
convenient after I've changed how ClientHelloOuter is constructed.
Changes of note between draft-10 and draft-13:
- There is now an ECH confirmation signal in both HRR and SH. We don't
actually make much use of this in our client right now, but it
resolves a bunch of weird issues around HRR, including edge cases if
HRR applies to one ClientHello but not the other.
- The confirmation signal no longer depends on key_share and PSK, so we
don't have to work around a weird ordering issue.
- ech_is_inner is now folded into the main encrypted_client_hello code
point. This works better with some stuff around HRR.
- Padding is moved from the padding extension, computed with
ClientHelloInner, to something we fill in afterwards. This makes it
easier to pad up the whole thing to a multiple of 32. I've accordingly
updated to the latest recommended padding construction, and updated
the GREASE logic to match.
- ech_outer_extensions is much easier to process because the order is
required to be consistent. We were doing that anyway, and now a simple
linear scan works.
- ClientHelloOuterAAD now uses an all zero placeholder payload of the
same length. This lets us simplify the server code, but, for now, I've
kept the client code the same. I'll follow this up with a CL to avoid
computing ClientHelloOuter twice.
- ClientHelloOuterAAD is allowed to contain a placeholder PSK. I haven't
filled that in and will do it in a follow-up CL.
Bug: 275
Change-Id: I7464345125c53968b2fe692f9268e392120fc2eb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48912
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
4 years ago
|
|
|
// 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
|
|
|
|
|
Update to draft-ietf-tls-esni-13.
Later CLs will clean up the ClientHello construction a bit (draft-12
avoids computing ClientHelloOuter twice). I suspect the transcript
handling on the client can also be simpler, but I'll see what's
convenient after I've changed how ClientHelloOuter is constructed.
Changes of note between draft-10 and draft-13:
- There is now an ECH confirmation signal in both HRR and SH. We don't
actually make much use of this in our client right now, but it
resolves a bunch of weird issues around HRR, including edge cases if
HRR applies to one ClientHello but not the other.
- The confirmation signal no longer depends on key_share and PSK, so we
don't have to work around a weird ordering issue.
- ech_is_inner is now folded into the main encrypted_client_hello code
point. This works better with some stuff around HRR.
- Padding is moved from the padding extension, computed with
ClientHelloInner, to something we fill in afterwards. This makes it
easier to pad up the whole thing to a multiple of 32. I've accordingly
updated to the latest recommended padding construction, and updated
the GREASE logic to match.
- ech_outer_extensions is much easier to process because the order is
required to be consistent. We were doing that anyway, and now a simple
linear scan works.
- ClientHelloOuterAAD now uses an all zero placeholder payload of the
same length. This lets us simplify the server code, but, for now, I've
kept the client code the same. I'll follow this up with a CL to avoid
computing ClientHelloOuter twice.
- ClientHelloOuterAAD is allowed to contain a placeholder PSK. I haven't
filled that in and will do it in a follow-up CL.
Bug: 275
Change-Id: I7464345125c53968b2fe692f9268e392120fc2eb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48912
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
4 years ago
|
|
|
if len(hello.pskIdentities) > 0 {
|
|
|
|
generatePSKBinders(c.wireVersion, 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 {
|
Update to draft-ietf-tls-esni-13.
Later CLs will clean up the ClientHello construction a bit (draft-12
avoids computing ClientHelloOuter twice). I suspect the transcript
handling on the client can also be simpler, but I'll see what's
convenient after I've changed how ClientHelloOuter is constructed.
Changes of note between draft-10 and draft-13:
- There is now an ECH confirmation signal in both HRR and SH. We don't
actually make much use of this in our client right now, but it
resolves a bunch of weird issues around HRR, including edge cases if
HRR applies to one ClientHello but not the other.
- The confirmation signal no longer depends on key_share and PSK, so we
don't have to work around a weird ordering issue.
- ech_is_inner is now folded into the main encrypted_client_hello code
point. This works better with some stuff around HRR.
- Padding is moved from the padding extension, computed with
ClientHelloInner, to something we fill in afterwards. This makes it
easier to pad up the whole thing to a multiple of 32. I've accordingly
updated to the latest recommended padding construction, and updated
the GREASE logic to match.
- ech_outer_extensions is much easier to process because the order is
required to be consistent. We were doing that anyway, and now a simple
linear scan works.
- ClientHelloOuterAAD now uses an all zero placeholder payload of the
same length. This lets us simplify the server code, but, for now, I've
kept the client code the same. I'll follow this up with a CL to avoid
computing ClientHelloOuter twice.
- ClientHelloOuterAAD is allowed to contain a placeholder PSK. I haven't
filled that in and will do it in a follow-up CL.
Bug: 275
Change-Id: I7464345125c53968b2fe692f9268e392120fc2eb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48912
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
4 years ago
|
|
|
outerHello.echOuter = nil
|
|
|
|
} else {
|
|
|
|
configID := c.config.ClientECHConfig.ConfigID
|
|
|
|
if c.config.Bugs.CorruptSecondEncryptedClientHelloConfigID {
|
|
|
|
configID ^= 1
|
|
|
|
}
|
Update to draft-ietf-tls-esni-13.
Later CLs will clean up the ClientHello construction a bit (draft-12
avoids computing ClientHelloOuter twice). I suspect the transcript
handling on the client can also be simpler, but I'll see what's
convenient after I've changed how ClientHelloOuter is constructed.
Changes of note between draft-10 and draft-13:
- There is now an ECH confirmation signal in both HRR and SH. We don't
actually make much use of this in our client right now, but it
resolves a bunch of weird issues around HRR, including edge cases if
HRR applies to one ClientHello but not the other.
- The confirmation signal no longer depends on key_share and PSK, so we
don't have to work around a weird ordering issue.
- ech_is_inner is now folded into the main encrypted_client_hello code
point. This works better with some stuff around HRR.
- Padding is moved from the padding extension, computed with
ClientHelloInner, to something we fill in afterwards. This makes it
easier to pad up the whole thing to a multiple of 32. I've accordingly
updated to the latest recommended padding construction, and updated
the GREASE logic to match.
- ech_outer_extensions is much easier to process because the order is
required to be consistent. We were doing that anyway, and now a simple
linear scan works.
- ClientHelloOuterAAD now uses an all zero placeholder payload of the
same length. This lets us simplify the server code, but, for now, I've
kept the client code the same. I'll follow this up with a CL to avoid
computing ClientHelloOuter twice.
- ClientHelloOuterAAD is allowed to contain a placeholder PSK. I haven't
filled that in and will do it in a follow-up CL.
Bug: 275
Change-Id: I7464345125c53968b2fe692f9268e392120fc2eb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48912
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
4 years ago
|
|
|
if err := hs.encryptClientHello(outerHello, hello, configID, nil); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if c.config.Bugs.CorruptSecondEncryptedClientHello {
|
|
|
|
if c.config.Bugs.NullAllCiphers {
|
Update to draft-ietf-tls-esni-13.
Later CLs will clean up the ClientHello construction a bit (draft-12
avoids computing ClientHelloOuter twice). I suspect the transcript
handling on the client can also be simpler, but I'll see what's
convenient after I've changed how ClientHelloOuter is constructed.
Changes of note between draft-10 and draft-13:
- There is now an ECH confirmation signal in both HRR and SH. We don't
actually make much use of this in our client right now, but it
resolves a bunch of weird issues around HRR, including edge cases if
HRR applies to one ClientHello but not the other.
- The confirmation signal no longer depends on key_share and PSK, so we
don't have to work around a weird ordering issue.
- ech_is_inner is now folded into the main encrypted_client_hello code
point. This works better with some stuff around HRR.
- Padding is moved from the padding extension, computed with
ClientHelloInner, to something we fill in afterwards. This makes it
easier to pad up the whole thing to a multiple of 32. I've accordingly
updated to the latest recommended padding construction, and updated
the GREASE logic to match.
- ech_outer_extensions is much easier to process because the order is
required to be consistent. We were doing that anyway, and now a simple
linear scan works.
- ClientHelloOuterAAD now uses an all zero placeholder payload of the
same length. This lets us simplify the server code, but, for now, I've
kept the client code the same. I'll follow this up with a CL to avoid
computing ClientHelloOuter twice.
- ClientHelloOuterAAD is allowed to contain a placeholder PSK. I haven't
filled that in and will do it in a follow-up CL.
Bug: 275
Change-Id: I7464345125c53968b2fe692f9268e392120fc2eb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48912
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
4 years ago
|
|
|
outerHello.echOuter.payload = []byte{echBadPayloadByte}
|
|
|
|
} else {
|
Update to draft-ietf-tls-esni-13.
Later CLs will clean up the ClientHello construction a bit (draft-12
avoids computing ClientHelloOuter twice). I suspect the transcript
handling on the client can also be simpler, but I'll see what's
convenient after I've changed how ClientHelloOuter is constructed.
Changes of note between draft-10 and draft-13:
- There is now an ECH confirmation signal in both HRR and SH. We don't
actually make much use of this in our client right now, but it
resolves a bunch of weird issues around HRR, including edge cases if
HRR applies to one ClientHello but not the other.
- The confirmation signal no longer depends on key_share and PSK, so we
don't have to work around a weird ordering issue.
- ech_is_inner is now folded into the main encrypted_client_hello code
point. This works better with some stuff around HRR.
- Padding is moved from the padding extension, computed with
ClientHelloInner, to something we fill in afterwards. This makes it
easier to pad up the whole thing to a multiple of 32. I've accordingly
updated to the latest recommended padding construction, and updated
the GREASE logic to match.
- ech_outer_extensions is much easier to process because the order is
required to be consistent. We were doing that anyway, and now a simple
linear scan works.
- ClientHelloOuterAAD now uses an all zero placeholder payload of the
same length. This lets us simplify the server code, but, for now, I've
kept the client code the same. I'll follow this up with a CL to avoid
computing ClientHelloOuter twice.
- ClientHelloOuterAAD is allowed to contain a placeholder PSK. I haven't
filled that in and will do it in a follow-up CL.
Bug: 275
Change-Id: I7464345125c53968b2fe692f9268e392120fc2eb
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48912
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
4 years ago
|
|
|
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 chainToSend *Certificate
|
|
|
|
var certRequested bool
|
|
|
|
certReq, ok := msg.(*certificateRequestMsg)
|
|
|
|
if ok {
|
|
|
|
certRequested = true
|
|
|
|
if c.config.Bugs.IgnorePeerSignatureAlgorithmPreferences {
|
|
|
|
certReq.signatureAlgorithms = c.config.signSignatureAlgorithms()
|
|
|
|
}
|
|
|
|
|
|
|
|
hs.writeServerHash(certReq.marshal())
|
|
|
|
|
|
|
|
chainToSend, err = selectClientCertificate(c, certReq)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
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 chainToSend != nil {
|
|
|
|
for _, certData := range chainToSend.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 chainToSend != nil {
|
|
|
|
certVerify := &certificateVerifyMsg{
|
|
|
|
hasSignatureAlgorithm: c.vers >= VersionTLS12,
|
|
|
|
}
|
|
|
|
|
|
|
|
// Determine the hash to sign.
|
|
|
|
privKey := c.config.Certificates[0].PrivateKey
|
|
|
|
|
|
|
|
if certVerify.hasSignatureAlgorithm {
|
|
|
|
certVerify.signatureAlgorithm, err = selectSignatureAlgorithm(c.vers, privKey, c.config, certReq.signatureAlgorithms)
|
|
|
|
if err != nil {
|
|
|
|
c.sendAlert(alertInternalError)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
certVerify.signature, err = signMessage(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://tools.ietf.org/html/draft-ietf-tls-subcerts-03#section-3
|
|
|
|
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 c.config.Bugs.FailIfDelegatedCredentials {
|
|
|
|
c.sendAlert(alertIllegalParameter)
|
|
|
|
return errors.New("tls: unexpected delegated credential")
|
|
|
|
}
|
|
|
|
if i != 0 {
|
|
|
|
c.sendAlert(alertIllegalParameter)
|
|
|
|
return errors.New("tls: non-leaf certificate has a delegated credential")
|
|
|
|
}
|
|
|
|
if c.config.Bugs.DisableDelegatedCredentials {
|
|
|
|
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())
|
|
|
|
}
|
|
|
|
|
|
|
|
verifier, err := getSigner(c.vers, hs.peerPublicKey, c.config, dc.algorithm, true)
|
|
|
|
if err != nil {
|
|
|
|
c.sendAlert(alertBadCertificate)
|
|
|
|
return errors.New("tls: failed to get verifier for delegated credential: " + err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := verifier.verifyMessage(leafPublicKey, delegatedCredentialSignedMessage(dc.signedBytes, dc.algorithm, certs[0].Raw), dc.signature); err != nil {
|
|
|
|
c.sendAlert(alertBadCertificate)
|
|
|
|
return errors.New("tls: failed to verify delegated credential: " + err.Error())
|
|
|
|
}
|
|
|
|
} else if c.config.Bugs.ExpectDelegatedCredentials {
|
|
|
|
c.sendAlert(alertInternalError)
|
|
|
|
return errors.New("tls: delegated credentials missing")
|
|
|
|
} 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 interface{}
|
|
|
|
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 {
|
|
|
|
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")
|
|
|
|
}
|
|
|
|
c.hasApplicationSettings = true
|
|
|
|
c.localApplicationSettings = settings
|
|
|
|
c.peerApplicationSettings = serverExtensions.applicationSettings
|
|
|
|
} 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
|
|
|
|
}
|
|
|
|
|
|
|
|
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.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,
|
|
|
|
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)
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
|
|
|
// selectClientCertificate selects a certificate for use with the given
|
|
|
|
// certificate, or none if none match. It may return a particular certificate or
|
|
|
|
// nil on success, or an error on internal error.
|
|
|
|
func selectClientCertificate(c *Conn, certReq *certificateRequestMsg) (*Certificate, error) {
|
|
|
|
if len(c.config.Certificates) == 0 {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// The test is assumed to have configured the certificate it meant to
|
|
|
|
// send.
|
|
|
|
if len(c.config.Certificates) > 1 {
|
|
|
|
return nil, errors.New("tls: multiple certificates configured")
|
|
|
|
}
|
|
|
|
|
|
|
|
return &c.config.Certificates[0], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 {
|
|
|
|
for _, c := range protos {
|
|
|
|
if s == c {
|
|
|
|
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) {
|
|
|
|
for i := range b {
|
|
|
|
b[i] = 0
|
|
|
|
}
|
|
|
|
xb := x.Bytes()
|
|
|
|
copy(b[len(b)-len(xb):], xb)
|
|
|
|
}
|
|
|
|
|
|
|
|
func generatePSKBinders(version uint16, 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, 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
|
|
|
|
}
|