Add initial handshake reassembly tests.

For now, only test reorderings when we always or never fragment messages.
There's a third untested case: when full messages and fragments are mixed. That
will be tested later after making it actually work.

Change-Id: Ic4efb3f5e87b1319baf2d4af31eafa40f6a50fa6
Reviewed-on: https://boringssl-review.googlesource.com/3216
Reviewed-by: Adam Langley <agl@google.com>
This commit is contained in:
David Benjamin 2015-01-31 17:16:01 -05:00 committed by Adam Langley
parent 5ba305643f
commit b3774b9619
6 changed files with 114 additions and 14 deletions

View File

@ -610,6 +610,12 @@ type ProtocolBugs struct {
// PacketAdaptor is the packetAdaptor to use to simulate timeouts.
PacketAdaptor *packetAdaptor
// ReorderHandshakeFragments, if true, causes handshake fragments in
// DTLS to overlap and be sent in the wrong order. It also causes
// pre-CCS flights to be sent twice. (Post-CCS flights consist of
// Finished and will trigger a spurious retransmit.)
ReorderHandshakeFragments bool
}
func (c *Config) serverInit() {

View File

@ -69,8 +69,9 @@ type Conn struct {
// DTLS state
sendHandshakeSeq uint16
recvHandshakeSeq uint16
handMsg []byte // pending assembled handshake message
handMsgLen int // handshake message length, not including the header
handMsg []byte // pending assembled handshake message
handMsgLen int // handshake message length, not including the header
pendingFragments [][]byte // pending outgoing handshake fragments.
tmp [16]byte
}

View File

@ -20,6 +20,7 @@ import (
"errors"
"fmt"
"io"
"math/rand"
"net"
)
@ -125,10 +126,6 @@ func (c *Conn) dtlsWriteRecord(typ recordType, data []byte) (n int, err error) {
// FragmentAcrossChangeCipherSpec. (Which is unfortunate
// because OpenSSL's DTLS implementation will probably accept
// such fragmentation and could do with a fix + tests.)
if len(data) < 4 {
// This should not happen.
panic(data)
}
header := data[:4]
data = data[4:]
@ -151,12 +148,13 @@ func (c *Conn) dtlsWriteRecord(typ recordType, data []byte) (n int, err error) {
fragment = append(fragment, byte(m>>16), byte(m>>8), byte(m))
fragment = append(fragment, data[:m]...)
// TODO(davidben): A real DTLS implementation needs to
// retransmit handshake messages. For testing purposes, we don't
// actually care.
_, err = c.dtlsWriteRawRecord(recordTypeHandshake, fragment)
if err != nil {
break
// Buffer the fragment for later. They will be sent (and
// reordered) on flush.
c.pendingFragments = append(c.pendingFragments, fragment)
if c.config.Bugs.ReorderHandshakeFragments && m > (maxLen+1)/2 {
// Overlap each fragment by half.
m = (maxLen + 1) / 2
}
n += m
data = data[m:]
@ -168,6 +166,38 @@ func (c *Conn) dtlsWriteRecord(typ recordType, data []byte) (n int, err error) {
return
}
func (c *Conn) dtlsFlushHandshake(duplicate bool) error {
if !c.isDTLS {
return nil
}
var fragments []byte
fragments, c.pendingFragments = c.pendingFragments, fragments
if c.config.Bugs.ReorderHandshakeFragments {
if duplicate {
fragments = append(fragments, fragments...)
}
perm := rand.New(rand.NewSource(0)).Perm(len(fragments))
tmp := make([][]byte, len(fragments))
for i := range tmp {
tmp[i] = fragments[perm[i]]
}
fragments = tmp
}
// Send them all.
for _, fragment := range fragments {
// TODO(davidben): A real DTLS implementation needs to
// retransmit handshake messages. For testing purposes, we don't
// actually care.
if _, err := c.dtlsWriteRawRecord(recordTypeHandshake, fragment); err != nil {
return err
}
}
return nil
}
func (c *Conn) dtlsWriteRawRecord(typ recordType, data []byte) (n int, err error) {
recordHeaderLen := dtlsRecordHeaderLen
maxLen := c.config.Bugs.MaxHandshakeRecordLength

View File

@ -214,6 +214,9 @@ NextCipherSuite:
helloBytes = hello.marshal()
c.writeRecord(recordTypeHandshake, helloBytes)
}
if err := c.dtlsFlushHandshake(true); err != nil {
return err
}
if err := c.simulatePacketLoss(nil); err != nil {
return err
@ -237,6 +240,9 @@ NextCipherSuite:
hello.cookie = helloVerifyRequest.cookie
helloBytes = hello.marshal()
c.writeRecord(recordTypeHandshake, helloBytes)
if err := c.dtlsFlushHandshake(true); err != nil {
return err
}
if err := c.simulatePacketLoss(nil); err != nil {
return err
@ -327,7 +333,10 @@ NextCipherSuite:
// 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.writeRecord(recordTypeHandshake, hs.finishedBytes) }); err != nil {
if err := c.simulatePacketLoss(func() {
c.writeRecord(recordTypeHandshake, hs.finishedBytes)
c.dtlsFlushHandshake(false)
}); err != nil {
return err
}
if err := hs.readSessionTicket(); err != nil {
@ -612,6 +621,9 @@ func (hs *clientHandshakeState) doFullHandshake() error {
hs.writeClientHash(certVerify.marshal())
c.writeRecord(recordTypeHandshake, certVerify.marshal())
}
if err := c.dtlsFlushHandshake(true); err != nil {
return err
}
hs.finishedHash.discardHandshakeBuffer()
@ -847,6 +859,9 @@ func (hs *clientHandshakeState) sendFinished(isResume bool) error {
c.writeRecord(recordTypeHandshake, postCCSBytes[:5])
postCCSBytes = postCCSBytes[5:]
}
if err := c.dtlsFlushHandshake(true); err != nil {
return err
}
if !c.config.Bugs.SkipChangeCipherSpec &&
c.config.Bugs.EarlyChangeCipherSpec == 0 {
@ -858,6 +873,9 @@ func (hs *clientHandshakeState) sendFinished(isResume bool) error {
}
c.writeRecord(recordTypeHandshake, postCCSBytes)
if err := c.dtlsFlushHandshake(false); err != nil {
return err
}
return nil
}

View File

@ -75,7 +75,10 @@ func (c *Conn) serverHandshake() error {
// 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.writeRecord(recordTypeHandshake, hs.finishedBytes) }); err != nil {
if err := c.simulatePacketLoss(func() {
c.writeRecord(recordTypeHandshake, hs.finishedBytes)
c.dtlsFlushHandshake(false)
}); err != nil {
return err
}
if err := hs.readFinished(isResume); err != nil {
@ -146,6 +149,9 @@ func (hs *serverHandshakeState) readClientHello() (isResume bool, err error) {
return false, errors.New("dtls: short read from Rand: " + err.Error())
}
c.writeRecord(recordTypeHandshake, helloVerifyRequest.marshal())
if err := c.dtlsFlushHandshake(true); err != nil {
return false, err
}
if err := c.simulatePacketLoss(nil); err != nil {
return false, err
@ -543,6 +549,9 @@ func (hs *serverHandshakeState) doFullHandshake() error {
helloDone := new(serverHelloDoneMsg)
hs.writeServerHash(helloDone.marshal())
c.writeRecord(recordTypeHandshake, helloDone.marshal())
if err := c.dtlsFlushHandshake(true); err != nil {
return err
}
var pub crypto.PublicKey // public key for client auth, if any
@ -836,6 +845,9 @@ func (hs *serverHandshakeState) sendFinished() error {
c.writeRecord(recordTypeHandshake, postCCSBytes[:5])
postCCSBytes = postCCSBytes[5:]
}
if err := c.dtlsFlushHandshake(true); err != nil {
return err
}
if !c.config.Bugs.SkipChangeCipherSpec {
c.writeRecord(recordTypeChangeCipherSpec, []byte{1})
@ -846,6 +858,9 @@ func (hs *serverHandshakeState) sendFinished() error {
}
c.writeRecord(recordTypeHandshake, postCCSBytes)
if err := c.dtlsFlushHandshake(false); err != nil {
return err
}
c.cipherSuite = hs.suite.id

View File

@ -652,6 +652,36 @@ var testCases = []testCase{
},
},
},
{
protocol: dtls,
name: "ReorderHandshakeFragments-Small-DTLS",
config: Config{
Bugs: ProtocolBugs{
ReorderHandshakeFragments: true,
// Small enough that every handshake message is
// fragmented.
MaxHandshakeRecordLength: 2,
},
},
},
{
protocol: dtls,
name: "ReorderHandshakeFragments-Large-DTLS",
config: Config{
Bugs: ProtocolBugs{
ReorderHandshakeFragments: true,
// Large enough that no handshake message is
// fragmented.
//
// TODO(davidben): Also test interaction of
// complete handshake messages with
// fragments. The current logic is full of bugs
// here, so the reassembly logic needs a rewrite
// before those tests will pass.
MaxHandshakeRecordLength: 2048,
},
},
},
}
func doExchange(test *testCase, config *Config, conn net.Conn, messageLen int, isResume bool) error {