Add tests for packed handshake records in TLS.
I'm surprised we'd never tested this. In addition to splitting handshake
records up, one may pack multiple handshakes into a single record, as
they fit. Generalize the DTLS handshake flush hook to do this in TLS as
well.
Change-Id: Ia546d18c7c56ba45e50f489c5b53e1fcd6404f51
Reviewed-on: https://boringssl-review.googlesource.com/8650
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 0e5d9e4..eabd0c6 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -767,15 +767,19 @@
// Diffie-Hellman group. The generator used is always two.
DHGroupPrime *big.Int
- // PackHandshakeFragments, if true, causes handshake fragments to be
- // packed into individual handshake records, up to the specified record
- // size.
+ // PackHandshakeFragments, if true, causes handshake fragments in DTLS
+ // to be packed into individual handshake records, up to the specified
+ // record size.
PackHandshakeFragments int
- // PackHandshakeRecords, if true, causes handshake records to be packed
- // into individual packets, up to the specified packet size.
+ // PackHandshakeRecords, if true, causes handshake records in DTLS to be
+ // packed into individual packets, up to the specified packet size.
PackHandshakeRecords int
+ // PackHandshakeFlight, if true, causes each handshake flight in TLS to
+ // be packed into records, up to the largest size record available.
+ PackHandshakeFlight bool
+
// EnableAllCiphers, if true, causes all configured ciphers to be
// enabled.
EnableAllCiphers bool
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go
index 551c6bc..c33ac0c 100644
--- a/ssl/test/runner/conn.go
+++ b/ssl/test/runner/conn.go
@@ -77,6 +77,10 @@
input *block // application record waiting to be read
hand bytes.Buffer // handshake record waiting to be read
+ // pendingFlight, if PackHandshakeFlight is enabled, is the buffer of
+ // handshake data to be split into records at the end of the flight.
+ pendingFlight bytes.Buffer
+
// DTLS state
sendHandshakeSeq uint16
recvHandshakeSeq uint16
@@ -934,6 +938,15 @@
return c.dtlsWriteRecord(typ, data)
}
+ if c.config.Bugs.PackHandshakeFlight && typ == recordTypeHandshake {
+ c.pendingFlight.Write(data)
+ return len(data), nil
+ }
+
+ return c.doWriteRecord(typ, data)
+}
+
+func (c *Conn) doWriteRecord(typ recordType, data []byte) (n int, err error) {
recordHeaderLen := tlsRecordHeaderLen
b := c.out.newBlock()
first := true
@@ -1031,6 +1044,23 @@
return
}
+func (c *Conn) flushHandshake() error {
+ if c.isDTLS {
+ return c.dtlsFlushHandshake()
+ }
+
+ for c.pendingFlight.Len() > 0 {
+ var buf [maxPlaintext]byte
+ n, _ := c.pendingFlight.Read(buf[:])
+ if _, err := c.doWriteRecord(recordTypeHandshake, buf[:n]); err != nil {
+ return err
+ }
+ }
+
+ c.pendingFlight.Reset()
+ return nil
+}
+
func (c *Conn) doReadHandshake() ([]byte, error) {
if c.isDTLS {
return c.dtlsDoReadHandshake()
@@ -1217,6 +1247,7 @@
if c.config.Bugs.SendHelloRequestBeforeEveryAppDataRecord {
c.writeRecord(recordTypeHandshake, []byte{typeHelloRequest, 0, 0, 0})
+ c.flushHandshake()
}
// SSL 3.0 and TLS 1.0 are susceptible to a chosen-plaintext
@@ -1269,6 +1300,7 @@
helloReq = c.config.Bugs.BadHelloRequest
}
c.writeRecord(recordTypeHandshake, helloReq)
+ c.flushHandshake()
}
c.handshakeComplete = false
diff --git a/ssl/test/runner/dtls.go b/ssl/test/runner/dtls.go
index f4c5f71..48b8a1b 100644
--- a/ssl/test/runner/dtls.go
+++ b/ssl/test/runner/dtls.go
@@ -242,10 +242,6 @@
}
func (c *Conn) dtlsFlushHandshake() error {
- if !c.isDTLS {
- return nil
- }
-
// This is a test-only DTLS implementation, so there is no need to
// retain |c.pendingFragments| for a future retransmit.
var fragments [][]byte
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index 8e8f7d7..a2f6f65 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -225,7 +225,7 @@
helloBytes = hello.marshal()
c.writeRecord(recordTypeHandshake, helloBytes)
}
- c.dtlsFlushHandshake()
+ c.flushHandshake()
if err := c.simulatePacketLoss(nil); err != nil {
return err
@@ -249,7 +249,7 @@
hello.cookie = helloVerifyRequest.cookie
helloBytes = hello.marshal()
c.writeRecord(recordTypeHandshake, helloBytes)
- c.dtlsFlushHandshake()
+ c.flushHandshake()
if err := c.simulatePacketLoss(nil); err != nil {
return err
@@ -342,7 +342,7 @@
// Finished.
if err := c.simulatePacketLoss(func() {
c.writeRecord(recordTypeHandshake, hs.finishedBytes)
- c.dtlsFlushHandshake()
+ c.flushHandshake()
}); err != nil {
return err
}
@@ -549,7 +549,7 @@
hs.writeClientHash(certVerify.marshal())
c.writeRecord(recordTypeHandshake, certVerify.marshal())
}
- c.dtlsFlushHandshake()
+ c.flushHandshake()
hs.finishedHash.discardHandshakeBuffer()
@@ -893,7 +893,7 @@
c.writeRecord(recordTypeHandshake, postCCSBytes[:5])
postCCSBytes = postCCSBytes[5:]
}
- c.dtlsFlushHandshake()
+ c.flushHandshake()
if !c.config.Bugs.SkipChangeCipherSpec &&
c.config.Bugs.EarlyChangeCipherSpec == 0 {
@@ -914,7 +914,7 @@
if !c.config.Bugs.SkipFinished {
c.writeRecord(recordTypeHandshake, postCCSBytes)
- c.dtlsFlushHandshake()
+ c.flushHandshake()
}
return nil
}
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index 94f5923..771beea 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -76,7 +76,7 @@
// Finished.
if err := c.simulatePacketLoss(func() {
c.writeRecord(recordTypeHandshake, hs.finishedBytes)
- c.dtlsFlushHandshake()
+ c.flushHandshake()
}); err != nil {
return err
}
@@ -154,7 +154,7 @@
return false, errors.New("dtls: short read from Rand: " + err.Error())
}
c.writeRecord(recordTypeHandshake, helloVerifyRequest.marshal())
- c.dtlsFlushHandshake()
+ c.flushHandshake()
if err := c.simulatePacketLoss(nil); err != nil {
return false, err
@@ -614,7 +614,7 @@
helloDone := new(serverHelloDoneMsg)
hs.writeServerHash(helloDone.marshal())
c.writeRecord(recordTypeHandshake, helloDone.marshal())
- c.dtlsFlushHandshake()
+ c.flushHandshake()
var pub crypto.PublicKey // public key for client auth, if any
@@ -894,7 +894,7 @@
c.writeRecord(recordTypeHandshake, postCCSBytes[:5])
postCCSBytes = postCCSBytes[5:]
}
- c.dtlsFlushHandshake()
+ c.flushHandshake()
if !c.config.Bugs.SkipChangeCipherSpec {
ccs := []byte{1}
@@ -914,7 +914,7 @@
if !c.config.Bugs.SkipFinished {
c.writeRecord(recordTypeHandshake, postCCSBytes)
- c.dtlsFlushHandshake()
+ c.flushHandshake()
}
c.cipherSuite = hs.suite
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 7596485..b8f7088 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -2918,10 +2918,39 @@
}
}
+type stateMachineTestConfig struct {
+ protocol protocol
+ async bool
+ splitHandshake, packHandshakeFlight bool
+}
+
// Adds tests that try to cover the range of the handshake state machine, under
// various conditions. Some of these are redundant with other tests, but they
// only cover the synchronous case.
-func addStateMachineCoverageTests(async, splitHandshake bool, protocol protocol) {
+func addAllStateMachineCoverageTests() {
+ for _, async := range []bool{false, true} {
+ for _, protocol := range []protocol{tls, dtls} {
+ addStateMachineCoverageTests(stateMachineTestConfig{
+ protocol: protocol,
+ async: async,
+ })
+ addStateMachineCoverageTests(stateMachineTestConfig{
+ protocol: protocol,
+ async: async,
+ splitHandshake: true,
+ })
+ if protocol == tls {
+ addStateMachineCoverageTests(stateMachineTestConfig{
+ protocol: protocol,
+ async: async,
+ packHandshakeFlight: true,
+ })
+ }
+ }
+ }
+}
+
+func addStateMachineCoverageTests(config stateMachineTestConfig) {
var tests []testCase
// Basic handshake, with resumption. Client and server,
@@ -3024,7 +3053,7 @@
// Setting SSL_VERIFY_PEER allows anonymous clients.
flags: []string{"-verify-peer"},
})
- if protocol == tls {
+ if config.protocol == tls {
tests = append(tests, testCase{
testType: clientTest,
name: "ClientAuth-NoCertificate-Client-SSL3",
@@ -3247,7 +3276,7 @@
},
})
- if protocol == tls {
+ if config.protocol == tls {
tests = append(tests, testCase{
name: "Renegotiate-Client",
config: Config{
@@ -3486,24 +3515,28 @@
}
for _, test := range tests {
- test.protocol = protocol
- if protocol == dtls {
+ test.protocol = config.protocol
+ if config.protocol == dtls {
test.name += "-DTLS"
}
- if async {
+ if config.async {
test.name += "-Async"
test.flags = append(test.flags, "-async")
} else {
test.name += "-Sync"
}
- if splitHandshake {
+ if config.splitHandshake {
test.name += "-SplitHandshakeRecords"
test.config.Bugs.MaxHandshakeRecordLength = 1
- if protocol == dtls {
+ if config.protocol == dtls {
test.config.Bugs.MaxPacketLength = 256
test.flags = append(test.flags, "-mtu", "256")
}
}
+ if config.packHandshakeFlight {
+ test.name += "-PackHandshakeFlight"
+ test.config.Bugs.PackHandshakeFlight = true
+ }
testCases = append(testCases, test)
}
}
@@ -5856,13 +5889,7 @@
addCECPQ1Tests()
addKeyExchangeInfoTests()
addTLS13RecordTests()
- for _, async := range []bool{false, true} {
- for _, splitHandshake := range []bool{false, true} {
- for _, protocol := range []protocol{tls, dtls} {
- addStateMachineCoverageTests(async, splitHandshake, protocol)
- }
- }
- }
+ addAllStateMachineCoverageTests()
var wg sync.WaitGroup