diff options
Diffstat (limited to 'src/ssl/test/runner/dtls.go')
-rw-r--r-- | src/ssl/test/runner/dtls.go | 342 |
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 +} |