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.go342
1 files changed, 342 insertions, 0 deletions
diff --git a/src/ssl/test/runner/dtls.go b/src/ssl/test/runner/dtls.go
new file mode 100644
index 0000000..a395980
--- /dev/null
+++ b/src/ssl/test/runner/dtls.go
@@ -0,0 +1,342 @@
+// Copyright 2014 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.
+
+// DTLS implementation.
+//
+// NOTE: This is a not even a remotely production-quality DTLS
+// implementation. It is the bare minimum necessary to be able to
+// achieve coverage on BoringSSL's implementation. Of note is that
+// this implementation assumes the underlying net.PacketConn is not
+// only reliable but also ordered. BoringSSL will be expected to deal
+// with simulated loss, but there is no point in forcing the test
+// driver to.
+
+package main
+
+import (
+ "bytes"
+ "crypto/cipher"
+ "errors"
+ "fmt"
+ "io"
+ "net"
+)
+
+func versionToWire(vers uint16, isDTLS bool) uint16 {
+ if isDTLS {
+ return ^(vers - 0x0201)
+ }
+ return vers
+}
+
+func wireToVersion(vers uint16, isDTLS bool) uint16 {
+ if isDTLS {
+ return ^vers + 0x0201
+ }
+ return vers
+}
+
+func (c *Conn) dtlsDoReadRecord(want recordType) (recordType, *block, error) {
+Again:
+ recordHeaderLen := dtlsRecordHeaderLen
+
+ if c.rawInput == nil {
+ c.rawInput = c.in.newBlock()
+ }
+ b := c.rawInput
+
+ // Read a new packet only if the current one is empty.
+ if len(b.data) == 0 {
+ // Pick some absurdly large buffer size.
+ b.resize(maxCiphertext + recordHeaderLen)
+ n, err := c.conn.Read(c.rawInput.data)
+ if err != nil {
+ return 0, nil, err
+ }
+ if c.config.Bugs.MaxPacketLength != 0 && n > c.config.Bugs.MaxPacketLength {
+ return 0, nil, fmt.Errorf("dtls: exceeded maximum packet length")
+ }
+ c.rawInput.resize(n)
+ }
+
+ // Read out one record.
+ //
+ // A real DTLS implementation should be tolerant of errors,
+ // but this is test code. We should not be tolerant of our
+ // peer sending garbage.
+ if len(b.data) < recordHeaderLen {
+ return 0, nil, errors.New("dtls: failed to read record header")
+ }
+ typ := recordType(b.data[0])
+ vers := wireToVersion(uint16(b.data[1])<<8|uint16(b.data[2]), c.isDTLS)
+ if c.haveVers {
+ if vers != c.vers {
+ c.sendAlert(alertProtocolVersion)
+ return 0, nil, c.in.setErrorLocked(fmt.Errorf("dtls: received record with version %x when expecting version %x", vers, c.vers))
+ }
+ } else {
+ if expect := c.config.Bugs.ExpectInitialRecordVersion; expect != 0 && vers != expect {
+ c.sendAlert(alertProtocolVersion)
+ return 0, nil, c.in.setErrorLocked(fmt.Errorf("dtls: received record with version %x when expecting version %x", vers, expect))
+ }
+ }
+ 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
+ // replay window and such.
+ if !bytes.Equal(seq, c.in.seq[:]) {
+ c.sendAlert(alertIllegalParameter)
+ return 0, nil, c.in.setErrorLocked(fmt.Errorf("dtls: bad sequence number"))
+ }
+ n := int(b.data[11])<<8 | int(b.data[12])
+ if n > maxCiphertext || len(b.data) < recordHeaderLen+n {
+ c.sendAlert(alertRecordOverflow)
+ return 0, nil, c.in.setErrorLocked(fmt.Errorf("dtls: oversized record received with length %d", n))
+ }
+
+ // Process message.
+ b, c.rawInput = c.in.splitBlock(b, recordHeaderLen+n)
+ ok, off, err := c.in.decrypt(b)
+ if !ok {
+ c.in.setErrorLocked(c.sendAlert(err))
+ }
+ b.off = off
+ return typ, b, nil
+}
+
+func (c *Conn) dtlsWriteRecord(typ recordType, data []byte) (n int, err error) {
+ recordHeaderLen := dtlsRecordHeaderLen
+ maxLen := c.config.Bugs.MaxHandshakeRecordLength
+ if maxLen <= 0 {
+ maxLen = 1024
+ }
+
+ b := c.out.newBlock()
+
+ 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:]
+ }
+
+ firstRun := true
+ for firstRun || len(data) > 0 {
+ 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
+ }
+
+ // 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]
+ }
+
+ // Send the fragment.
+ explicitIVLen := 0
+ explicitIVIsSeq := false
+
+ 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")
+ }
+ 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
+ }
+ 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
+ }
+ }
+ }
+ 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
+ }
+ n += m
+ data = data[m:]
+ }
+ c.out.freeBlock(b)
+
+ // Increment the handshake sequence number for the next
+ // handshake message.
+ if typ == recordTypeHandshake {
+ c.sendHandshakeSeq++
+ }
+
+ if typ == recordTypeChangeCipherSpec {
+ err = c.out.changeCipherSpec(c.config)
+ if err != nil {
+ // Cannot call sendAlert directly,
+ // because we already hold c.out.Mutex.
+ c.tmp[0] = alertLevelError
+ c.tmp[1] = byte(err.(alert))
+ c.writeRecord(recordTypeAlert, c.tmp[0:2])
+ return n, c.out.setErrorLocked(&net.OpError{Op: "local error", Err: err})
+ }
+ }
+ return
+}
+
+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.
+ for len(c.handMsg) < 4+c.handMsgLen {
+ // Get a new handshake record if the previous has been
+ // exhausted.
+ if c.hand.Len() == 0 {
+ if err := c.in.err; err != nil {
+ return nil, err
+ }
+ if err := c.readRecord(recordTypeHandshake); err != nil {
+ return nil, err
+ }
+ }
+
+ // Read the next fragment. It must fit entirely within
+ // the record.
+ if c.hand.Len() < 12 {
+ return nil, errors.New("dtls: bad handshake record")
+ }
+ header := c.hand.Next(12)
+ fragN := int(header[1])<<16 | int(header[2])<<8 | int(header[3])
+ fragSeq := uint16(header[4])<<8 | uint16(header[5])
+ fragOff := int(header[6])<<16 | int(header[7])<<8 | int(header[8])
+ fragLen := int(header[9])<<16 | int(header[10])<<8 | int(header[11])
+
+ if c.hand.Len() < fragLen {
+ return nil, errors.New("dtls: fragment length too long")
+ }
+ 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 that the length is consistent.
+ if c.handMsg == nil {
+ c.handMsgLen = fragN
+ if c.handMsgLen > maxHandshake {
+ return nil, c.in.setErrorLocked(c.sendAlert(alertInternalError))
+ }
+ // Start with the TLS handshake header,
+ // without the DTLS bits.
+ c.handMsg = append([]byte{}, header[:4]...)
+ } else if fragN != c.handMsgLen {
+ return nil, errors.New("dtls: bad handshake length")
+ }
+
+ // Add the fragment to the pending message.
+ if 4+fragOff != len(c.handMsg) {
+ return nil, errors.New("dtls: bad fragment offset")
+ }
+ if fragOff+fragLen > c.handMsgLen {
+ return nil, errors.New("dtls: bad fragment length")
+ }
+ c.handMsg = append(c.handMsg, fragment...)
+ }
+ c.recvHandshakeSeq++
+ ret := c.handMsg
+ c.handMsg, c.handMsgLen = nil, 0
+ return ret, nil
+}
+
+// DTLSServer returns a new DTLS server side connection
+// using conn as the underlying transport.
+// The configuration config must be non-nil and must have
+// at least one certificate.
+func DTLSServer(conn net.Conn, config *Config) *Conn {
+ c := &Conn{config: config, isDTLS: true, conn: conn}
+ c.init()
+ return c
+}
+
+// DTLSClient returns a new DTLS client side connection
+// using conn as the underlying transport.
+// The config cannot be nil: users must set either ServerHostname or
+// InsecureSkipVerify in the config.
+func DTLSClient(conn net.Conn, config *Config) *Conn {
+ c := &Conn{config: config, isClient: true, isDTLS: true, conn: conn}
+ c.init()
+ return c
+}