summaryrefslogtreecommitdiffstats
path: root/src/ssl/test/runner/dtls.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/ssl/test/runner/dtls.go')
-rw-r--r--src/ssl/test/runner/dtls.go288
1 files changed, 171 insertions, 117 deletions
diff --git a/src/ssl/test/runner/dtls.go b/src/ssl/test/runner/dtls.go
index a395980..85c4247 100644
--- a/src/ssl/test/runner/dtls.go
+++ b/src/ssl/test/runner/dtls.go
@@ -16,10 +16,10 @@ package main
import (
"bytes"
- "crypto/cipher"
"errors"
"fmt"
"io"
+ "math/rand"
"net"
)
@@ -38,7 +38,6 @@ func wireToVersion(vers uint16, isDTLS bool) uint16 {
}
func (c *Conn) dtlsDoReadRecord(want recordType) (recordType, *block, error) {
-Again:
recordHeaderLen := dtlsRecordHeaderLen
if c.rawInput == nil {
@@ -82,13 +81,6 @@ Again:
}
}
seq := b.data[3:11]
- if !bytes.Equal(seq[:2], c.in.seq[:2]) {
- // If the epoch didn't match, silently drop the record.
- // BoringSSL retransmits on an internal timer, so it may flakily
- // revisit the previous epoch if retransmiting ChangeCipherSpec
- // and Finished.
- goto Again
- }
// For test purposes, we assume a reliable channel. Require
// that the explicit sequence number matches the incrementing
// one we maintain. A real implementation would maintain a
@@ -113,127 +105,196 @@ Again:
return typ, b, nil
}
+func (c *Conn) makeFragment(header, data []byte, fragOffset, fragLen int) []byte {
+ fragment := make([]byte, 0, 12+fragLen)
+ fragment = append(fragment, header...)
+ fragment = append(fragment, byte(c.sendHandshakeSeq>>8), byte(c.sendHandshakeSeq))
+ fragment = append(fragment, byte(fragOffset>>16), byte(fragOffset>>8), byte(fragOffset))
+ fragment = append(fragment, byte(fragLen>>16), byte(fragLen>>8), byte(fragLen))
+ fragment = append(fragment, data[fragOffset:fragOffset+fragLen]...)
+ return fragment
+}
+
func (c *Conn) dtlsWriteRecord(typ recordType, data []byte) (n int, err error) {
- recordHeaderLen := dtlsRecordHeaderLen
+ if typ != recordTypeHandshake {
+ // Only handshake messages are fragmented.
+ return c.dtlsWriteRawRecord(typ, data)
+ }
+
maxLen := c.config.Bugs.MaxHandshakeRecordLength
if maxLen <= 0 {
maxLen = 1024
}
- b := c.out.newBlock()
+ // Handshake messages have to be modified to include fragment
+ // offset and length and with the header replicated. Save the
+ // TLS header here.
+ //
+ // TODO(davidben): This assumes that data contains exactly one
+ // handshake message. This is incompatible with
+ // FragmentAcrossChangeCipherSpec. (Which is unfortunate
+ // because OpenSSL's DTLS implementation will probably accept
+ // such fragmentation and could do with a fix + tests.)
+ header := data[:4]
+ data = data[4:]
- var header []byte
- if typ == recordTypeHandshake {
- // Handshake messages have to be modified to include
- // fragment offset and length and with the header
- // replicated. Save the header here.
- //
- // TODO(davidben): This assumes that data contains
- // exactly one handshake message. This is incompatible
- // with 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:]
+ isFinished := header[0] == typeFinished
+
+ if c.config.Bugs.SendEmptyFragments {
+ fragment := c.makeFragment(header, data, 0, 0)
+ c.pendingFragments = append(c.pendingFragments, fragment)
}
firstRun := true
- for firstRun || len(data) > 0 {
+ fragOffset := 0
+ for firstRun || fragOffset < len(data) {
firstRun = false
- m := len(data)
- var fragment []byte
- // Handshake messages get fragmented. Other records we
- // pass-through as is. DTLS should be a packet
- // interface.
- if typ == recordTypeHandshake {
- if m > maxLen {
- m = maxLen
- }
+ fragLen := len(data) - fragOffset
+ if fragLen > maxLen {
+ fragLen = maxLen
+ }
- // Standard handshake header.
- fragment = make([]byte, 0, 12+m)
- fragment = append(fragment, header...)
- // message_seq
- fragment = append(fragment, byte(c.sendHandshakeSeq>>8), byte(c.sendHandshakeSeq))
- // fragment_offset
- fragment = append(fragment, byte(n>>16), byte(n>>8), byte(n))
- // fragment_length
- fragment = append(fragment, byte(m>>16), byte(m>>8), byte(m))
- fragment = append(fragment, data[:m]...)
- } else {
- fragment = data[:m]
+ fragment := c.makeFragment(header, data, fragOffset, fragLen)
+ if c.config.Bugs.FragmentMessageTypeMismatch && fragOffset > 0 {
+ fragment[0]++
+ }
+ if c.config.Bugs.FragmentMessageLengthMismatch && fragOffset > 0 {
+ fragment[3]++
}
- // Send the fragment.
- explicitIVLen := 0
- explicitIVIsSeq := false
+ // Buffer the fragment for later. They will be sent (and
+ // reordered) on flush.
+ c.pendingFragments = append(c.pendingFragments, fragment)
+ if c.config.Bugs.ReorderHandshakeFragments {
+ // Don't duplicate Finished to avoid the peer
+ // interpreting it as a retransmit request.
+ if !isFinished {
+ c.pendingFragments = append(c.pendingFragments, fragment)
+ }
- if cbc, ok := c.out.cipher.(cbcMode); ok {
- // Block cipher modes have an explicit IV.
- explicitIVLen = cbc.BlockSize()
- } else if _, ok := c.out.cipher.(cipher.AEAD); ok {
- explicitIVLen = 8
- // The AES-GCM construction in TLS has an
- // explicit nonce so that the nonce can be
- // random. However, the nonce is only 8 bytes
- // which is too small for a secure, random
- // nonce. Therefore we use the sequence number
- // as the nonce.
- explicitIVIsSeq = true
- } else if c.out.cipher != nil {
- panic("Unknown cipher")
+ if fragLen > (maxLen+1)/2 {
+ // Overlap each fragment by half.
+ fragLen = (maxLen + 1) / 2
+ }
}
- b.resize(recordHeaderLen + explicitIVLen + len(fragment))
- b.data[0] = byte(typ)
- vers := c.vers
- if vers == 0 {
- // Some TLS servers fail if the record version is
- // greater than TLS 1.0 for the initial ClientHello.
- vers = VersionTLS10
+ fragOffset += fragLen
+ n += fragLen
+ }
+ if !isFinished && c.config.Bugs.MixCompleteMessageWithFragments {
+ fragment := c.makeFragment(header, data, 0, len(data))
+ c.pendingFragments = append(c.pendingFragments, fragment)
+ }
+
+ // Increment the handshake sequence number for the next
+ // handshake message.
+ c.sendHandshakeSeq++
+ return
+}
+
+func (c *Conn) dtlsFlushHandshake() error {
+ if !c.isDTLS {
+ return nil
+ }
+
+ var fragments [][]byte
+ fragments, c.pendingFragments = c.pendingFragments, fragments
+
+ if c.config.Bugs.ReorderHandshakeFragments {
+ perm := rand.New(rand.NewSource(0)).Perm(len(fragments))
+ tmp := make([][]byte, len(fragments))
+ for i := range tmp {
+ tmp[i] = fragments[perm[i]]
}
- vers = versionToWire(vers, c.isDTLS)
- b.data[1] = byte(vers >> 8)
- b.data[2] = byte(vers)
- // DTLS records include an explicit sequence number.
- copy(b.data[3:11], c.out.seq[0:])
- b.data[11] = byte(len(fragment) >> 8)
- b.data[12] = byte(len(fragment))
- if explicitIVLen > 0 {
- explicitIV := b.data[recordHeaderLen : recordHeaderLen+explicitIVLen]
- if explicitIVIsSeq {
- copy(explicitIV, c.out.seq[:])
- } else {
- if _, err = io.ReadFull(c.config.rand(), explicitIV); err != nil {
- break
- }
+ fragments = tmp
+ }
+
+ // Send them all.
+ for _, fragment := range fragments {
+ if c.config.Bugs.SplitFragmentHeader {
+ if _, err := c.dtlsWriteRawRecord(recordTypeHandshake, fragment[:2]); err != nil {
+ return err
}
+ fragment = fragment[2:]
+ } else if c.config.Bugs.SplitFragmentBody && len(fragment) > 12 {
+ if _, err := c.dtlsWriteRawRecord(recordTypeHandshake, fragment[:13]); err != nil {
+ return err
+ }
+ fragment = fragment[13:]
}
- copy(b.data[recordHeaderLen+explicitIVLen:], fragment)
- c.out.encrypt(b, explicitIVLen)
// TODO(davidben): A real DTLS implementation needs to
- // retransmit handshake messages. For testing
- // purposes, we don't actually care.
- _, err = c.conn.Write(b.data)
- if err != nil {
- break
+ // retransmit handshake messages. For testing purposes, we don't
+ // actually care.
+ if _, err := c.dtlsWriteRawRecord(recordTypeHandshake, fragment); err != nil {
+ return err
}
- n += m
- data = data[m:]
}
- c.out.freeBlock(b)
+ return nil
+}
- // Increment the handshake sequence number for the next
- // handshake message.
- if typ == recordTypeHandshake {
- c.sendHandshakeSeq++
+func (c *Conn) dtlsWriteRawRecord(typ recordType, data []byte) (n int, err error) {
+ recordHeaderLen := dtlsRecordHeaderLen
+ maxLen := c.config.Bugs.MaxHandshakeRecordLength
+ if maxLen <= 0 {
+ maxLen = 1024
}
+ b := c.out.newBlock()
+
+ explicitIVLen := 0
+ explicitIVIsSeq := false
+
+ if cbc, ok := c.out.cipher.(cbcMode); ok {
+ // Block cipher modes have an explicit IV.
+ explicitIVLen = cbc.BlockSize()
+ } else if aead, ok := c.out.cipher.(*tlsAead); ok {
+ if aead.explicitNonce {
+ explicitIVLen = 8
+ // The AES-GCM construction in TLS has an explicit nonce so that
+ // the nonce can be random. However, the nonce is only 8 bytes
+ // which is too small for a secure, random nonce. Therefore we
+ // use the sequence number as the nonce.
+ explicitIVIsSeq = true
+ }
+ } else if c.out.cipher != nil {
+ panic("Unknown cipher")
+ }
+ b.resize(recordHeaderLen + explicitIVLen + len(data))
+ b.data[0] = byte(typ)
+ vers := c.vers
+ if vers == 0 {
+ // Some TLS servers fail if the record version is greater than
+ // TLS 1.0 for the initial ClientHello.
+ vers = VersionTLS10
+ }
+ vers = versionToWire(vers, c.isDTLS)
+ b.data[1] = byte(vers >> 8)
+ b.data[2] = byte(vers)
+ // DTLS records include an explicit sequence number.
+ copy(b.data[3:11], c.out.seq[0:])
+ b.data[11] = byte(len(data) >> 8)
+ b.data[12] = byte(len(data))
+ if explicitIVLen > 0 {
+ explicitIV := b.data[recordHeaderLen : recordHeaderLen+explicitIVLen]
+ if explicitIVIsSeq {
+ copy(explicitIV, c.out.seq[:])
+ } else {
+ if _, err = io.ReadFull(c.config.rand(), explicitIV); err != nil {
+ return
+ }
+ }
+ }
+ copy(b.data[recordHeaderLen+explicitIVLen:], data)
+ c.out.encrypt(b, explicitIVLen)
+
+ _, err = c.conn.Write(b.data)
+ if err != nil {
+ return
+ }
+ n = len(data)
+
+ c.out.freeBlock(b)
+
if typ == recordTypeChangeCipherSpec {
err = c.out.changeCipherSpec(c.config)
if err != nil {
@@ -250,9 +311,9 @@ func (c *Conn) dtlsWriteRecord(typ recordType, data []byte) (n int, err error) {
func (c *Conn) dtlsDoReadHandshake() ([]byte, error) {
// Assemble a full handshake message. For test purposes, this
- // implementation assumes fragments arrive in order, but tolerates
- // retransmits. It may need to be cleverer if we ever test BoringSSL's
- // retransmit behavior.
+ // implementation assumes fragments arrive in order. It may
+ // need to be cleverer if we ever test BoringSSL's retransmit
+ // behavior.
for len(c.handMsg) < 4+c.handMsgLen {
// Get a new handshake record if the previous has been
// exhausted.
@@ -281,16 +342,9 @@ func (c *Conn) dtlsDoReadHandshake() ([]byte, error) {
}
fragment := c.hand.Next(fragLen)
- if fragSeq < c.recvHandshakeSeq {
- // BoringSSL retransmits based on an internal timer, so
- // it may flakily retransmit part of a handshake
- // message. Ignore those fragments.
- //
- // TODO(davidben): Revise this if BoringSSL's retransmit
- // logic is made more deterministic.
- continue
- } else if fragSeq > c.recvHandshakeSeq {
- return nil, errors.New("dtls: handshake messages sent out of order")
+ // Check it's a fragment for the right message.
+ if fragSeq != c.recvHandshakeSeq {
+ return nil, errors.New("dtls: bad handshake sequence number")
}
// Check that the length is consistent.