runner: Ensure helloBytes is always the same as hello.marshal().
The client handshake currently defers creating the finishedHash and
writing things into the transcript, which is a little annoying for ECH.
In preparation for simplifying that, one nuisance is that we retain both
hello and helloBytes, across a long span of code. helloBytes is *almost*
the same as hello.marshal() except:
- When we send a V2ClientHello, helloBytes records that we serialized
the ClientHello completely differently.
- For the JDK11 workaround tests, helloBytes records that we swapped out
the ClientHello entirely.
- By the time we finally write helloBytes into the transcript, hello may
have been updated to the second ClientHello.
This CL resolves the first two issues. It replaces the v2ClientHelloMsg
with an option when serializing the clientHelloMsg, and it has the
ClientHello replacement function return a clientHelloMsg instead of a
[]byte. (This is a little weird because we're conflating parsed and
constructed ClientHellos, but ah well.)
A follow-up CL will remove the differed transcript bits and we'll
actually be able to drop helloBytes.
Change-Id: Ib82ac216604e2c4bf421277e57aa5fd3b4cef161
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/46629
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index 116e2be..6ba4e4b 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -52,30 +52,33 @@
panic("Unknown ClientHello version.")
}
-func fixClientHellos(hello *clientHelloMsg, in []byte) ([]byte, error) {
- ret := append([]byte{}, in...)
+// 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(ret) {
+ if !newHello.unmarshal(copied) {
return nil, errors.New("tls: invalid ClientHello")
}
- hello.random = newHello.random
- hello.sessionID = newHello.sessionID
-
- // Replace |ret|'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.
+ // 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 |ret|.
+ // |newHello.keySharesRaw| aliases |copied|.
copy(newHello.keySharesRaw, keyShares)
+ newHello.keyShares = hello.keyShares
- return ret, nil
+ return newHello, nil
}
func (c *Conn) clientHandshake() error {
@@ -446,38 +449,37 @@
hello.sessionID = c.config.Bugs.SendClientHelloSessionID
}
+ // PSK binders must be computed after the rest of the ClientHello is
+ // constructed.
+ 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 err
+ }
+ }
+
var helloBytes []byte
if c.config.Bugs.SendV2ClientHello {
- // Test that the peer left-pads random.
+ hello.isV2ClientHello = true
+ // The V2ClientHello "challenge" field is variable-length and is
+ // left-padded to become the SSL3/TLS random. Test this behavior by
+ // forcing the left padding.
hello.random[0] = 0
- v2Hello := &v2ClientHelloMsg{
- vers: hello.vers,
- cipherSuites: hello.cipherSuites,
- // No session resumption for V2ClientHello.
- sessionID: nil,
- challenge: hello.random[1:],
- }
- helloBytes = v2Hello.marshal()
+ hello.v2Challenge = hello.random[1:]
+ helloBytes = hello.marshal()
c.writeV2Record(helloBytes)
} else {
- 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, []byte{}, []byte{}, c.config)
- }
- if c.config.Bugs.SendClientHelloWithFixes != nil {
- helloBytes, err = fixClientHellos(hello, c.config.Bugs.SendClientHelloWithFixes)
- if err != nil {
- return err
- }
- } else {
- helloBytes = hello.marshal()
- }
-
+ helloBytes = hello.marshal()
var appendToHello byte
if c.config.Bugs.PartialClientFinishedWithClientHello {
appendToHello = typeFinished
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go
index 121244c..158f7c7 100644
--- a/ssl/test/runner/handshake_messages.go
+++ b/ssl/test/runner/handshake_messages.go
@@ -294,8 +294,10 @@
type clientHelloMsg struct {
raw []byte
isDTLS bool
+ isV2ClientHello bool
vers uint16
random []byte
+ v2Challenge []byte
sessionID []byte
cookie []byte
cipherSuites []uint16
@@ -361,6 +363,22 @@
return m.raw
}
+ if m.isV2ClientHello {
+ v2Msg := newByteBuilder()
+ v2Msg.addU8(1)
+ v2Msg.addU16(m.vers)
+ v2Msg.addU16(uint16(len(m.cipherSuites) * 3))
+ v2Msg.addU16(uint16(len(m.sessionID)))
+ v2Msg.addU16(uint16(len(m.v2Challenge)))
+ for _, spec := range m.cipherSuites {
+ v2Msg.addU24(int(spec))
+ }
+ v2Msg.addBytes(m.sessionID)
+ v2Msg.addBytes(m.v2Challenge)
+ m.raw = v2Msg.finish()
+ return m.raw
+ }
+
handshakeMsg := newByteBuilder()
handshakeMsg.addU8(typeClientHello)
hello := handshakeMsg.addU24LengthPrefixed()
@@ -2567,47 +2585,6 @@
return true
}
-type v2ClientHelloMsg struct {
- raw []byte
- vers uint16
- cipherSuites []uint16
- sessionID []byte
- challenge []byte
-}
-
-func (m *v2ClientHelloMsg) marshal() []byte {
- if m.raw != nil {
- return m.raw
- }
-
- length := 1 + 2 + 2 + 2 + 2 + len(m.cipherSuites)*3 + len(m.sessionID) + len(m.challenge)
-
- x := make([]byte, length)
- x[0] = 1
- x[1] = uint8(m.vers >> 8)
- x[2] = uint8(m.vers)
- x[3] = uint8((len(m.cipherSuites) * 3) >> 8)
- x[4] = uint8(len(m.cipherSuites) * 3)
- x[5] = uint8(len(m.sessionID) >> 8)
- x[6] = uint8(len(m.sessionID))
- x[7] = uint8(len(m.challenge) >> 8)
- x[8] = uint8(len(m.challenge))
- y := x[9:]
- for i, spec := range m.cipherSuites {
- y[i*3] = 0
- y[i*3+1] = uint8(spec >> 8)
- y[i*3+2] = uint8(spec)
- }
- y = y[len(m.cipherSuites)*3:]
- copy(y, m.sessionID)
- y = y[len(m.sessionID):]
- copy(y, m.challenge)
-
- m.raw = x
-
- return x
-}
-
type helloVerifyRequestMsg struct {
raw []byte
vers uint16