Test-only DTLS implementation in runner.go. Run against openssl s_client and openssl s_server. This seems to work for a start, although it may need to become cleverer to stress more of BoringSSL's implementation for test purposes. In particular, it assumes a reliable, in-order channel. And it requires that the peer send handshake fragments in order. Retransmit and whatnot are not implemented. The peer under test will be expected to handle a lossy channel, but all loss in the channel will be controlled. MAC errors, etc., are fatal. Change-Id: I329233cfb0994938fd012667ddf7c6a791ac7164 Reviewed-on: https://boringssl-review.googlesource.com/1390 Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/test/runner/cipher_suites.go b/ssl/test/runner/cipher_suites.go index 8a9df4c..ed26f09 100644 --- a/ssl/test/runner/cipher_suites.go +++ b/ssl/test/runner/cipher_suites.go
@@ -52,6 +52,9 @@ // suiteSHA384 indicates that the cipher suite uses SHA384 as the // handshake hash. suiteSHA384 + // suiteNoDTLS indicates that the cipher suite cannot be used + // in DTLS. + suiteNoDTLS ) // A cipherSuite is a specific combination of key agreement, cipher and MAC @@ -76,8 +79,8 @@ {TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, ecdheRSAKA, suiteECDHE | suiteTLS12, nil, nil, aeadAESGCM}, {TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, ecdheECDSAKA, suiteECDHE | suiteECDSA | suiteTLS12, nil, nil, aeadAESGCM}, {TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 32, 0, 4, ecdheRSAKA, suiteECDHE | suiteTLS12 | suiteSHA384, nil, nil, aeadAESGCM}, - {TLS_ECDHE_RSA_WITH_RC4_128_SHA, 16, 20, 0, ecdheRSAKA, suiteECDHE, cipherRC4, macSHA1, nil}, - {TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, 16, 20, 0, ecdheECDSAKA, suiteECDHE | suiteECDSA, cipherRC4, macSHA1, nil}, + {TLS_ECDHE_RSA_WITH_RC4_128_SHA, 16, 20, 0, ecdheRSAKA, suiteECDHE | suiteNoDTLS, cipherRC4, macSHA1, nil}, + {TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, 16, 20, 0, ecdheECDSAKA, suiteECDHE | suiteECDSA | suiteNoDTLS, cipherRC4, macSHA1, nil}, {TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, 16, 20, 16, ecdheRSAKA, suiteECDHE, cipherAES, macSHA1, nil}, {TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, 16, 20, 16, ecdheECDSAKA, suiteECDHE | suiteECDSA, cipherAES, macSHA1, nil}, {TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, 32, 20, 16, ecdheRSAKA, suiteECDHE, cipherAES, macSHA1, nil}, @@ -88,8 +91,8 @@ {TLS_DHE_RSA_WITH_AES_256_CBC_SHA, 32, 20, 16, dheRSAKA, 0, cipherAES, macSHA1, nil}, {TLS_RSA_WITH_AES_128_GCM_SHA256, 16, 0, 4, rsaKA, suiteTLS12, nil, nil, aeadAESGCM}, {TLS_RSA_WITH_AES_256_GCM_SHA384, 32, 0, 4, rsaKA, suiteTLS12 | suiteSHA384, nil, nil, aeadAESGCM}, - {TLS_RSA_WITH_RC4_128_SHA, 16, 20, 0, rsaKA, 0, cipherRC4, macSHA1, nil}, - {TLS_RSA_WITH_RC4_128_MD5, 16, 16, 0, rsaKA, 0, cipherRC4, macMD5, nil}, + {TLS_RSA_WITH_RC4_128_SHA, 16, 20, 0, rsaKA, suiteNoDTLS, cipherRC4, macSHA1, nil}, + {TLS_RSA_WITH_RC4_128_MD5, 16, 16, 0, rsaKA, suiteNoDTLS, cipherRC4, macMD5, nil}, {TLS_RSA_WITH_AES_128_CBC_SHA, 16, 20, 16, rsaKA, 0, cipherAES, macSHA1, nil}, {TLS_RSA_WITH_AES_256_CBC_SHA, 32, 20, 16, rsaKA, 0, cipherAES, macSHA1, nil}, {TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, 24, 20, 8, ecdheRSAKA, suiteECDHE, cipher3DES, macSHA1, nil}, @@ -145,7 +148,7 @@ type macFunction interface { Size() int - MAC(digestBuf, seq, header, data []byte) []byte + MAC(digestBuf, seq, header, length, data []byte) []byte } // fixedNonceAEAD wraps an AEAD and prefixes a fixed portion of the nonce to @@ -203,7 +206,7 @@ var ssl30Pad2 = [48]byte{0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c} -func (s ssl30MAC) MAC(digestBuf, seq, header, data []byte) []byte { +func (s ssl30MAC) MAC(digestBuf, seq, header, length, data []byte) []byte { padLength := 48 if s.h.Size() == 20 { padLength = 40 @@ -214,7 +217,7 @@ s.h.Write(ssl30Pad1[:padLength]) s.h.Write(seq) s.h.Write(header[:1]) - s.h.Write(header[3:5]) + s.h.Write(length) s.h.Write(data) digestBuf = s.h.Sum(digestBuf[:0]) @@ -234,10 +237,11 @@ return s.h.Size() } -func (s tls10MAC) MAC(digestBuf, seq, header, data []byte) []byte { +func (s tls10MAC) MAC(digestBuf, seq, header, length, data []byte) []byte { s.h.Reset() s.h.Write(seq) s.h.Write(header) + s.h.Write(length) s.h.Write(data) return s.h.Sum(digestBuf[:0]) }
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go index ed60a3b..f14f4e9 100644 --- a/ssl/test/runner/common.go +++ b/ssl/test/runner/common.go
@@ -25,10 +25,11 @@ ) const ( - maxPlaintext = 16384 // maximum plaintext payload length - maxCiphertext = 16384 + 2048 // maximum ciphertext payload length - recordHeaderLen = 5 // record header length - maxHandshake = 65536 // maximum handshake we support (protocol max is 16 MB) + maxPlaintext = 16384 // maximum plaintext payload length + maxCiphertext = 16384 + 2048 // maximum ciphertext payload length + tlsRecordHeaderLen = 5 // record header length + dtlsRecordHeaderLen = 13 + maxHandshake = 65536 // maximum handshake we support (protocol max is 16 MB) minVersion = VersionSSL30 maxVersion = VersionTLS12 @@ -48,6 +49,7 @@ const ( typeClientHello uint8 = 1 typeServerHello uint8 = 2 + typeHelloVerifyRequest uint8 = 3 typeNewSessionTicket uint8 = 4 typeCertificate uint8 = 11 typeServerKeyExchange uint8 = 12 @@ -414,6 +416,10 @@ // SendClientVersion, if non-zero, causes the client to send a different // TLS version in the ClientHello than the maximum supported version. SendClientVersion uint16 + + // SkipHelloVerifyRequest causes a DTLS server to skip the + // HelloVerifyRequest message. + SkipHelloVerifyRequest bool } func (c *Config) serverInit() {
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go index f3e2495..5371a64 100644 --- a/ssl/test/runner/conn.go +++ b/ssl/test/runner/conn.go
@@ -24,6 +24,7 @@ type Conn struct { // constant conn net.Conn + isDTLS bool isClient bool // constant after handshake; protected by handshakeMutex @@ -49,8 +50,14 @@ // input/output in, out halfConn // in.Mutex < out.Mutex rawInput *block // raw input, right off the wire - input *block // application data waiting to be read - hand bytes.Buffer // handshake data waiting to be read + input *block // application record waiting to be read + hand bytes.Buffer // handshake record waiting to be read + + // DTLS state + sendHandshakeSeq uint16 + recvHandshakeSeq uint16 + handMsg []byte // pending assembled handshake message + handMsgLen int // handshake message length, not including the header tmp [16]byte } @@ -94,8 +101,9 @@ type halfConn struct { sync.Mutex - err error // first permanent error - version uint16 // protocol version + err error // first permanent error + version uint16 // protocol version + isDTLS bool cipher interface{} // cipher algorithm mac macFunction seq [8]byte // 64-bit sequence number @@ -141,15 +149,18 @@ hc.nextCipher = nil hc.nextMac = nil hc.config = config - for i := range hc.seq { - hc.seq[i] = 0 - } + hc.incEpoch() return nil } // incSeq increments the sequence number. func (hc *halfConn) incSeq() { - for i := 7; i >= 0; i-- { + limit := 0 + if hc.isDTLS { + // Increment up to the epoch in DTLS. + limit = 2 + } + for i := 7; i >= limit; i-- { hc.seq[i]++ if hc.seq[i] != 0 { return @@ -162,11 +173,33 @@ panic("TLS: sequence number wraparound") } -// resetSeq resets the sequence number to zero. -func (hc *halfConn) resetSeq() { - for i := range hc.seq { - hc.seq[i] = 0 +// incEpoch resets the sequence number. In DTLS, it increments the +// epoch half of the sequence number. +func (hc *halfConn) incEpoch() { + limit := 0 + if hc.isDTLS { + for i := 1; i >= 0; i-- { + hc.seq[i]++ + if hc.seq[i] != 0 { + break + } + if i == 0 { + panic("TLS: epoch number wraparound") + } + } + limit = 2 } + seq := hc.seq[limit:] + for i := range seq { + seq[i] = 0 + } +} + +func (hc *halfConn) recordHeaderLen() int { + if hc.isDTLS { + return dtlsRecordHeaderLen + } + return tlsRecordHeaderLen } // removePadding returns an unpadded slice, in constant time, which is a prefix @@ -237,6 +270,8 @@ // success boolean, the number of bytes to skip from the start of the record in // order to get the application payload, and an optional alert value. func (hc *halfConn) decrypt(b *block) (ok bool, prefixLen int, alertValue alert) { + recordHeaderLen := hc.recordHeaderLen() + // pull out payload payload := b.data[recordHeaderLen:] @@ -248,6 +283,12 @@ paddingGood := byte(255) explicitIVLen := 0 + seq := hc.seq[:] + if hc.isDTLS { + // DTLS sequence numbers are explicit. + seq = b.data[3:11] + } + // decrypt if hc.cipher != nil { switch c := hc.cipher.(type) { @@ -262,7 +303,7 @@ payload = payload[8:] var additionalData [13]byte - copy(additionalData[:], hc.seq[:]) + copy(additionalData[:], seq) copy(additionalData[8:], b.data[:3]) n := len(payload) - c.Overhead() additionalData[11] = byte(n >> 8) @@ -275,7 +316,7 @@ b.resize(recordHeaderLen + explicitIVLen + len(payload)) case cbcMode: blockSize := c.BlockSize() - if hc.version >= VersionTLS11 { + if hc.version >= VersionTLS11 || hc.isDTLS { explicitIVLen = blockSize } @@ -318,11 +359,11 @@ // strip mac off payload, b.data n := len(payload) - macSize - b.data[3] = byte(n >> 8) - b.data[4] = byte(n) + b.data[recordHeaderLen-2] = byte(n >> 8) + b.data[recordHeaderLen-1] = byte(n) b.resize(recordHeaderLen + explicitIVLen + n) remoteMAC := payload[n:] - localMAC := hc.mac.MAC(hc.inDigestBuf, hc.seq[0:], b.data[:recordHeaderLen], payload[:n]) + localMAC := hc.mac.MAC(hc.inDigestBuf, seq, b.data[:3], b.data[recordHeaderLen-2:recordHeaderLen], payload[:n]) if subtle.ConstantTimeCompare(localMAC, remoteMAC) != 1 || paddingGood != 255 { return false, 0, alertBadRecordMAC @@ -364,9 +405,11 @@ // encrypt encrypts and macs the data in b. func (hc *halfConn) encrypt(b *block, explicitIVLen int) (bool, alert) { + recordHeaderLen := hc.recordHeaderLen() + // mac if hc.mac != nil { - mac := hc.mac.MAC(hc.outDigestBuf, hc.seq[0:], b.data[:recordHeaderLen], b.data[recordHeaderLen+explicitIVLen:]) + mac := hc.mac.MAC(hc.outDigestBuf, hc.seq[0:], b.data[:3], b.data[recordHeaderLen-2:recordHeaderLen], b.data[recordHeaderLen+explicitIVLen:]) n := len(b.data) b.resize(n + len(mac)) @@ -412,8 +455,8 @@ // update length to include MAC and any block padding needed. n := len(b.data) - recordHeaderLen - b.data[3] = byte(n >> 8) - b.data[4] = byte(n) + b.data[recordHeaderLen-2] = byte(n >> 8) + b.data[recordHeaderLen-1] = byte(n) hc.incSeq() return true, 0 @@ -517,6 +560,86 @@ return b, bb } +func (c *Conn) doReadRecord(want recordType) (recordType, *block, error) { + if c.isDTLS { + return c.dtlsDoReadRecord(want) + } + + recordHeaderLen := tlsRecordHeaderLen + + if c.rawInput == nil { + c.rawInput = c.in.newBlock() + } + b := c.rawInput + + // Read header, payload. + if err := b.readFromUntil(c.conn, recordHeaderLen); err != nil { + // RFC suggests that EOF without an alertCloseNotify is + // an error, but popular web sites seem to do this, + // so we can't make it an error. + // if err == io.EOF { + // err = io.ErrUnexpectedEOF + // } + if e, ok := err.(net.Error); !ok || !e.Temporary() { + c.in.setErrorLocked(err) + } + return 0, nil, err + } + typ := recordType(b.data[0]) + + // No valid TLS record has a type of 0x80, however SSLv2 handshakes + // start with a uint16 length where the MSB is set and the first record + // is always < 256 bytes long. Therefore typ == 0x80 strongly suggests + // an SSLv2 client. + if want == recordTypeHandshake && typ == 0x80 { + c.sendAlert(alertProtocolVersion) + return 0, nil, c.in.setErrorLocked(errors.New("tls: unsupported SSLv2 handshake received")) + } + + vers := uint16(b.data[1])<<8 | uint16(b.data[2]) + n := int(b.data[3])<<8 | int(b.data[4]) + if c.haveVers && vers != c.vers { + c.sendAlert(alertProtocolVersion) + return 0, nil, c.in.setErrorLocked(fmt.Errorf("tls: received record with version %x when expecting version %x", vers, c.vers)) + } + if n > maxCiphertext { + c.sendAlert(alertRecordOverflow) + return 0, nil, c.in.setErrorLocked(fmt.Errorf("tls: oversized record received with length %d", n)) + } + if !c.haveVers { + // First message, be extra suspicious: + // this might not be a TLS client. + // Bail out before reading a full 'body', if possible. + // The current max version is 3.1. + // If the version is >= 16.0, it's probably not real. + // Similarly, a clientHello message encodes in + // well under a kilobyte. If the length is >= 12 kB, + // it's probably not real. + if (typ != recordTypeAlert && typ != want) || vers >= 0x1000 || n >= 0x3000 { + c.sendAlert(alertUnexpectedMessage) + return 0, nil, c.in.setErrorLocked(fmt.Errorf("tls: first record does not look like a TLS handshake")) + } + } + if err := b.readFromUntil(c.conn, recordHeaderLen+n); err != nil { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + if e, ok := err.(net.Error); !ok || !e.Temporary() { + c.in.setErrorLocked(err) + } + return 0, nil, err + } + + // 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 +} + // readRecord reads the next TLS record from the connection // and updates the record layer state. // c.in.Mutex <= L; c.input == nil. @@ -541,76 +664,10 @@ } Again: - if c.rawInput == nil { - c.rawInput = c.in.newBlock() - } - b := c.rawInput - - // Read header, payload. - if err := b.readFromUntil(c.conn, recordHeaderLen); err != nil { - // RFC suggests that EOF without an alertCloseNotify is - // an error, but popular web sites seem to do this, - // so we can't make it an error. - // if err == io.EOF { - // err = io.ErrUnexpectedEOF - // } - if e, ok := err.(net.Error); !ok || !e.Temporary() { - c.in.setErrorLocked(err) - } + typ, b, err := c.doReadRecord(want) + if err != nil { return err } - typ := recordType(b.data[0]) - - // No valid TLS record has a type of 0x80, however SSLv2 handshakes - // start with a uint16 length where the MSB is set and the first record - // is always < 256 bytes long. Therefore typ == 0x80 strongly suggests - // an SSLv2 client. - if want == recordTypeHandshake && typ == 0x80 { - c.sendAlert(alertProtocolVersion) - return c.in.setErrorLocked(errors.New("tls: unsupported SSLv2 handshake received")) - } - - vers := uint16(b.data[1])<<8 | uint16(b.data[2]) - n := int(b.data[3])<<8 | int(b.data[4]) - if c.haveVers && vers != c.vers { - c.sendAlert(alertProtocolVersion) - return c.in.setErrorLocked(fmt.Errorf("tls: received record with version %x when expecting version %x", vers, c.vers)) - } - if n > maxCiphertext { - c.sendAlert(alertRecordOverflow) - return c.in.setErrorLocked(fmt.Errorf("tls: oversized record received with length %d", n)) - } - if !c.haveVers { - // First message, be extra suspicious: - // this might not be a TLS client. - // Bail out before reading a full 'body', if possible. - // The current max version is 3.1. - // If the version is >= 16.0, it's probably not real. - // Similarly, a clientHello message encodes in - // well under a kilobyte. If the length is >= 12 kB, - // it's probably not real. - if (typ != recordTypeAlert && typ != want) || vers >= 0x1000 || n >= 0x3000 { - c.sendAlert(alertUnexpectedMessage) - return c.in.setErrorLocked(fmt.Errorf("tls: first record does not look like a TLS handshake")) - } - } - if err := b.readFromUntil(c.conn, recordHeaderLen+n); err != nil { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - if e, ok := err.(net.Error); !ok || !e.Temporary() { - c.in.setErrorLocked(err) - } - return err - } - - // 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 data := b.data[b.off:] if len(data) > maxPlaintext { err := c.sendAlert(alertRecordOverflow) @@ -713,6 +770,11 @@ // to the connection and updates the record layer state. // c.out.Mutex <= L. func (c *Conn) writeRecord(typ recordType, data []byte) (n int, err error) { + if c.isDTLS { + return c.dtlsWriteRecord(typ, data) + } + + recordHeaderLen := tlsRecordHeaderLen b := c.out.newBlock() first := true isClientHello := typ == recordTypeHandshake && len(data) > 0 && data[0] == typeClientHello @@ -800,10 +862,11 @@ return } -// readHandshake reads the next handshake message from -// the record layer. -// c.in.Mutex < L; c.out.Mutex < L. -func (c *Conn) readHandshake() (interface{}, error) { +func (c *Conn) doReadHandshake() ([]byte, error) { + if c.isDTLS { + return c.dtlsDoReadHandshake() + } + for c.hand.Len() < 4 { if err := c.in.err; err != nil { return nil, err @@ -826,13 +889,28 @@ return nil, err } } - data = c.hand.Next(4 + n) + return c.hand.Next(4 + n), nil +} + +// readHandshake reads the next handshake message from +// the record layer. +// c.in.Mutex < L; c.out.Mutex < L. +func (c *Conn) readHandshake() (interface{}, error) { + data, err := c.doReadHandshake() + if err != nil { + return nil, err + } + var m handshakeMessage switch data[0] { case typeClientHello: - m = new(clientHelloMsg) + m = &clientHelloMsg{ + isDTLS: c.isDTLS, + } case typeServerHello: - m = new(serverHelloMsg) + m = &serverHelloMsg{ + isDTLS: c.isDTLS, + } case typeNewSessionTicket: m = new(newSessionTicketMsg) case typeCertificate: @@ -857,6 +935,8 @@ m = new(nextProtoMsg) case typeFinished: m = new(finishedMsg) + case typeHelloVerifyRequest: + m = new(helloVerifyRequestMsg) default: return nil, c.in.setErrorLocked(c.sendAlert(alertUnexpectedMessage)) } @@ -899,7 +979,7 @@ // http://www.imperialviolet.org/2012/01/15/beastfollowup.html var m int - if len(b) > 1 && c.vers <= VersionTLS10 { + if len(b) > 1 && c.vers <= VersionTLS10 && !c.isDTLS { if _, ok := c.out.cipher.(cipher.BlockMode); ok { n, err := c.writeRecord(recordTypeApplicationData, b[:1]) if err != nil { @@ -938,7 +1018,7 @@ } n, err = c.input.Read(b) - if c.input.off >= len(c.input.data) { + if c.input.off >= len(c.input.data) || c.isDTLS { c.in.freeBlock(c.input) c.input = nil }
diff --git a/ssl/test/runner/dtls.go b/ssl/test/runner/dtls.go new file mode 100644 index 0000000..3b3f5a2 --- /dev/null +++ b/ssl/test/runner/dtls.go
@@ -0,0 +1,326 @@ +// 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) { + 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 + } + 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 && 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)) + } + seq := b.data[3:11] + // 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. 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) + + // 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. + 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 { + return &Conn{ + config: config, + isDTLS: true, + conn: conn, + in: halfConn{isDTLS: true}, + out: halfConn{isDTLS: true}, + } +} + +// 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 { + return &Conn{ + config: config, + isClient: true, + isDTLS: true, + conn: conn, + in: halfConn{isDTLS: true}, + out: halfConn{isDTLS: true}, + } +}
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go index fa84074..f2cbbe4 100644 --- a/ssl/test/runner/handshake_client.go +++ b/ssl/test/runner/handshake_client.go
@@ -39,7 +39,11 @@ return errors.New("tls: either ServerName or InsecureSkipVerify must be specified in the tls.Config") } + c.sendHandshakeSeq = 0 + c.recvHandshakeSeq = 0 + hello := &clientHelloMsg{ + isDTLS: c.isDTLS, vers: c.config.maxVersion(), compressionMethods: []uint8{compressionNone}, random: make([]byte, 32), @@ -70,6 +74,10 @@ if hello.vers < VersionTLS12 && suite.flags&suiteTLS12 != 0 { continue } + // Don't advertise non-DTLS cipher suites on DTLS. + if c.isDTLS && suite.flags&suiteNoDTLS != 0 { + continue + } hello.cipherSuites = append(hello.cipherSuites, suiteId) continue NextCipherSuite } @@ -154,6 +162,22 @@ if err != nil { return err } + + if c.isDTLS { + helloVerifyRequest, ok := msg.(*helloVerifyRequestMsg) + if ok { + hello.raw = nil + hello.cookie = helloVerifyRequest.cookie + helloBytes = hello.marshal() + c.writeRecord(recordTypeHandshake, helloBytes) + + msg, err = c.readHandshake() + if err != nil { + return err + } + } + } + serverHello, ok := msg.(*serverHelloMsg) if !ok { c.sendAlert(alertUnexpectedMessage) @@ -184,8 +208,8 @@ session: session, } - hs.finishedHash.Write(helloBytes) - hs.finishedHash.Write(hs.serverHello.marshal()) + hs.writeHash(helloBytes, hs.c.sendHandshakeSeq-1) + hs.writeServerHash(hs.serverHello.marshal()) if c.config.Bugs.EarlyChangeCipherSpec > 0 { hs.establishKeys() @@ -252,7 +276,7 @@ c.sendAlert(alertUnexpectedMessage) return unexpectedMessageError(certMsg, msg) } - hs.finishedHash.Write(certMsg.marshal()) + hs.writeServerHash(certMsg.marshal()) certs := make([]*x509.Certificate, len(certMsg.certificates)) for i, asn1Data := range certMsg.certificates { @@ -305,7 +329,7 @@ c.sendAlert(alertUnexpectedMessage) return unexpectedMessageError(cs, msg) } - hs.finishedHash.Write(cs.marshal()) + hs.writeServerHash(cs.marshal()) if cs.statusType == statusTypeOCSP { c.ocspResponse = cs.response @@ -321,7 +345,7 @@ skx, ok := msg.(*serverKeyExchangeMsg) if ok { - hs.finishedHash.Write(skx.marshal()) + hs.writeServerHash(skx.marshal()) err = keyAgreement.processServerKeyExchange(c.config, hs.hello, hs.serverHello, certs[0], skx) if err != nil { c.sendAlert(alertUnexpectedMessage) @@ -351,7 +375,7 @@ // ClientCertificateType, unless there is some external // arrangement to the contrary. - hs.finishedHash.Write(certReq.marshal()) + hs.writeServerHash(certReq.marshal()) var rsaAvail, ecdsaAvail bool for _, certType := range certReq.certificateTypes { @@ -417,7 +441,7 @@ c.sendAlert(alertUnexpectedMessage) return unexpectedMessageError(shd, msg) } - hs.finishedHash.Write(shd.marshal()) + hs.writeServerHash(shd.marshal()) // If the server requested a certificate then we have to send a // Certificate message, even if it's empty because we don't have a @@ -427,7 +451,7 @@ if chainToSend != nil { certMsg.certificates = chainToSend.Certificate } - hs.finishedHash.Write(certMsg.marshal()) + hs.writeClientHash(certMsg.marshal()) c.writeRecord(recordTypeHandshake, certMsg.marshal()) } @@ -438,7 +462,7 @@ } if ckx != nil { if c.config.Bugs.EarlyChangeCipherSpec < 2 { - hs.finishedHash.Write(ckx.marshal()) + hs.writeClientHash(ckx.marshal()) } c.writeRecord(recordTypeHandshake, ckx.marshal()) } @@ -486,7 +510,7 @@ } certVerify.signature = signed - hs.finishedHash.Write(certVerify.marshal()) + hs.writeClientHash(certVerify.marshal()) c.writeRecord(recordTypeHandshake, certVerify.marshal()) } @@ -571,7 +595,7 @@ return errors.New("tls: server's Finished message was incorrect") } } - hs.finishedHash.Write(serverFinished.marshal()) + hs.writeServerHash(serverFinished.marshal()) return nil } @@ -590,7 +614,7 @@ c.sendAlert(alertUnexpectedMessage) return unexpectedMessageError(sessionTicketMsg, msg) } - hs.finishedHash.Write(sessionTicketMsg.marshal()) + hs.writeServerHash(sessionTicketMsg.marshal()) hs.session = &ClientSessionState{ sessionTicket: sessionTicketMsg.ticket, @@ -607,6 +631,7 @@ c := hs.c var postCCSBytes []byte + seqno := hs.c.sendHandshakeSeq if hs.serverHello.nextProtoNeg { nextProto := new(nextProtoMsg) proto, fallback := mutualProtocol(c.config.NextProtos, hs.serverHello.nextProtos) @@ -615,7 +640,8 @@ c.clientProtocolFallback = fallback nextProtoBytes := nextProto.marshal() - hs.finishedHash.Write(nextProtoBytes) + hs.writeHash(nextProtoBytes, seqno) + seqno++ postCCSBytes = append(postCCSBytes, nextProtoBytes...) } @@ -626,7 +652,7 @@ finished.verifyData = hs.finishedHash.clientSum(hs.masterSecret) } finishedBytes := finished.marshal() - hs.finishedHash.Write(finishedBytes) + hs.writeHash(finishedBytes, seqno) postCCSBytes = append(postCCSBytes, finishedBytes...) if c.config.Bugs.FragmentAcrossChangeCipherSpec { @@ -643,6 +669,32 @@ return nil } +func (hs *clientHandshakeState) writeClientHash(msg []byte) { + // writeClientHash is called before writeRecord. + hs.writeHash(msg, hs.c.sendHandshakeSeq) +} + +func (hs *clientHandshakeState) writeServerHash(msg []byte) { + // writeServerHash is called after readHandshake. + hs.writeHash(msg, hs.c.recvHandshakeSeq-1) +} + +func (hs *clientHandshakeState) writeHash(msg []byte, seqno uint16) { + if hs.c.isDTLS { + // This is somewhat hacky. DTLS hashes a slightly different format. + // First, the TLS header. + hs.finishedHash.Write(msg[:4]) + // Then the sequence number and reassembled fragment offset (always 0). + hs.finishedHash.Write([]byte{byte(seqno >> 8), byte(seqno), 0, 0, 0}) + // Then the reassembled fragment (always equal to the message length). + hs.finishedHash.Write(msg[1:4]) + // And then the message body. + hs.finishedHash.Write(msg[4:]) + } else { + hs.finishedHash.Write(msg) + } +} + // clientSessionCacheKey returns a key used to cache sessionTickets that could // be used to resume previously negotiated TLS sessions with a server. func clientSessionCacheKey(serverAddr net.Addr, config *Config) string {
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go index 1c633bb..7fe8bf5 100644 --- a/ssl/test/runner/handshake_messages.go +++ b/ssl/test/runner/handshake_messages.go
@@ -8,9 +8,11 @@ type clientHelloMsg struct { raw []byte + isDTLS bool vers uint16 random []byte sessionId []byte + cookie []byte cipherSuites []uint16 compressionMethods []uint8 nextProtoNeg bool @@ -32,9 +34,11 @@ } return bytes.Equal(m.raw, m1.raw) && + m.isDTLS == m1.isDTLS && m.vers == m1.vers && bytes.Equal(m.random, m1.random) && bytes.Equal(m.sessionId, m1.sessionId) && + bytes.Equal(m.cookie, m1.cookie) && eqUint16s(m.cipherSuites, m1.cipherSuites) && bytes.Equal(m.compressionMethods, m1.compressionMethods) && m.nextProtoNeg == m1.nextProtoNeg && @@ -54,6 +58,9 @@ } length := 2 + 32 + 1 + len(m.sessionId) + 2 + len(m.cipherSuites)*2 + 1 + len(m.compressionMethods) + if m.isDTLS { + length += 1 + len(m.cookie) + } numExtensions := 0 extensionsLength := 0 if m.nextProtoNeg { @@ -100,12 +107,18 @@ x[1] = uint8(length >> 16) x[2] = uint8(length >> 8) x[3] = uint8(length) - x[4] = uint8(m.vers >> 8) - x[5] = uint8(m.vers) + vers := versionToWire(m.vers, m.isDTLS) + x[4] = uint8(vers >> 8) + x[5] = uint8(vers) copy(x[6:38], m.random) x[38] = uint8(len(m.sessionId)) copy(x[39:39+len(m.sessionId)], m.sessionId) y := x[39+len(m.sessionId):] + if m.isDTLS { + y[0] = uint8(len(m.cookie)) + copy(y[1:], m.cookie) + y = y[1+len(m.cookie):] + } y[0] = uint8(len(m.cipherSuites) >> 7) y[1] = uint8(len(m.cipherSuites) << 1) for i, suite := range m.cipherSuites { @@ -264,7 +277,7 @@ return false } m.raw = data - m.vers = uint16(data[4])<<8 | uint16(data[5]) + m.vers = wireToVersion(uint16(data[4])<<8|uint16(data[5]), m.isDTLS) m.random = data[6:38] sessionIdLen := int(data[38]) if sessionIdLen > 32 || len(data) < 39+sessionIdLen { @@ -272,6 +285,17 @@ } m.sessionId = data[39 : 39+sessionIdLen] data = data[39+sessionIdLen:] + if m.isDTLS { + if len(data) < 1 { + return false + } + cookieLen := int(data[0]) + if cookieLen > 32 || len(data) < 1+cookieLen { + return false + } + m.cookie = data[1 : 1+cookieLen] + data = data[1+cookieLen:] + } if len(data) < 2 { return false } @@ -425,6 +449,7 @@ type serverHelloMsg struct { raw []byte + isDTLS bool vers uint16 random []byte sessionId []byte @@ -445,6 +470,7 @@ } return bytes.Equal(m.raw, m1.raw) && + m.isDTLS == m1.isDTLS && m.vers == m1.vers && bytes.Equal(m.random, m1.random) && bytes.Equal(m.sessionId, m1.sessionId) && @@ -498,8 +524,9 @@ x[1] = uint8(length >> 16) x[2] = uint8(length >> 8) x[3] = uint8(length) - x[4] = uint8(m.vers >> 8) - x[5] = uint8(m.vers) + vers := versionToWire(m.vers, m.isDTLS) + x[4] = uint8(vers >> 8) + x[5] = uint8(vers) copy(x[6:38], m.random) x[38] = uint8(len(m.sessionId)) copy(x[39:39+len(m.sessionId)], m.sessionId) @@ -571,7 +598,7 @@ return false } m.raw = data - m.vers = uint16(data[4])<<8 | uint16(data[5]) + m.vers = wireToVersion(uint16(data[4])<<8|uint16(data[5]), m.isDTLS) m.random = data[6:38] sessionIdLen := int(data[38]) if sessionIdLen > 32 || len(data) < 39+sessionIdLen { @@ -1368,6 +1395,58 @@ return x } +type helloVerifyRequestMsg struct { + raw []byte + vers uint16 + cookie []byte +} + +func (m *helloVerifyRequestMsg) equal(i interface{}) bool { + m1, ok := i.(*helloVerifyRequestMsg) + if !ok { + return false + } + + return m.vers == m1.vers && + bytes.Equal(m.cookie, m1.cookie) +} + +func (m *helloVerifyRequestMsg) marshal() []byte { + if m.raw != nil { + return m.raw + } + + length := 2 + 1 + len(m.cookie) + + x := make([]byte, 4+length) + x[0] = typeHelloVerifyRequest + x[1] = uint8(length >> 16) + x[2] = uint8(length >> 8) + x[3] = uint8(length) + vers := versionToWire(m.vers, true) + x[4] = uint8(vers >> 8) + x[5] = uint8(vers) + x[6] = uint8(len(m.cookie)) + copy(x[7:7+len(m.cookie)], m.cookie) + + return x +} + +func (m *helloVerifyRequestMsg) unmarshal(data []byte) bool { + if len(data) < 4+2+1 { + return false + } + m.raw = data + m.vers = wireToVersion(uint16(data[4])<<8|uint16(data[5]), true) + cookieLen := int(data[6]) + if cookieLen > 32 || len(data) != 7+cookieLen { + return false + } + m.cookie = data[7 : 7+cookieLen] + + return true +} + func eqUint16s(x, y []uint16) bool { if len(x) != len(y) { return false
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go index 3b8ad6a..3a54eb2 100644 --- a/ssl/test/runner/handshake_server.go +++ b/ssl/test/runner/handshake_server.go
@@ -5,6 +5,7 @@ package main import ( + "bytes" "crypto" "crypto/ecdsa" "crypto/rsa" @@ -40,6 +41,9 @@ // encrypt the tickets with. config.serverInitOnce.Do(config.serverInit) + c.sendHandshakeSeq = 0 + c.recvHandshakeSeq = 0 + hs := serverHandshakeState{ c: c, } @@ -114,9 +118,44 @@ c.sendAlert(alertProtocolVersion) return false, fmt.Errorf("tls: client offered an unsupported, maximum protocol version of %x", hs.clientHello.vers) } + + if c.isDTLS && !config.Bugs.SkipHelloVerifyRequest { + helloVerifyRequest := &helloVerifyRequestMsg{ + vers: c.vers, + cookie: make([]byte, 32), + } + if _, err := io.ReadFull(c.config.rand(), helloVerifyRequest.cookie); err != nil { + c.sendAlert(alertInternalError) + return false, errors.New("dtls: short read from Rand: " + err.Error()) + } + c.writeRecord(recordTypeHandshake, helloVerifyRequest.marshal()) + + msg, err := c.readHandshake() + if err != nil { + return false, err + } + newClientHello, ok := msg.(*clientHelloMsg) + if !ok { + c.sendAlert(alertUnexpectedMessage) + return false, unexpectedMessageError(hs.clientHello, msg) + } + if !bytes.Equal(newClientHello.cookie, helloVerifyRequest.cookie) { + return false, errors.New("dtls: invalid cookie") + } + // Apart from the cookie, client hello must match. + hs.clientHello.cookie = newClientHello.cookie + if hs.clientHello.equal(newClientHello) { + return false, errors.New("dtls: retransmitted ClientHello does not match") + } + hs.clientHello = newClientHello + } + + // Do not set c.haveVers until after HelloVerifyRequest; the + // retransmitted ClientHello may not have the final version. c.haveVers = true hs.hello = new(serverHelloMsg) + hs.hello.isDTLS = c.isDTLS supportedCurve := false preferredCurves := config.curvePreferences() @@ -284,8 +323,8 @@ hs.hello.ticketSupported = c.config.Bugs.RenewTicketOnResume hs.finishedHash = newFinishedHash(c.vers, hs.suite) - hs.finishedHash.Write(hs.clientHello.marshal()) - hs.finishedHash.Write(hs.hello.marshal()) + hs.writeClientHash(hs.clientHello.marshal()) + hs.writeServerHash(hs.hello.marshal()) c.writeRecord(recordTypeHandshake, hs.hello.marshal()) @@ -312,15 +351,15 @@ hs.hello.cipherSuite = hs.suite.id hs.finishedHash = newFinishedHash(c.vers, hs.suite) - hs.finishedHash.Write(hs.clientHello.marshal()) - hs.finishedHash.Write(hs.hello.marshal()) + hs.writeClientHash(hs.clientHello.marshal()) + hs.writeServerHash(hs.hello.marshal()) c.writeRecord(recordTypeHandshake, hs.hello.marshal()) certMsg := new(certificateMsg) certMsg.certificates = hs.cert.Certificate if !config.Bugs.UnauthenticatedECDH { - hs.finishedHash.Write(certMsg.marshal()) + hs.writeServerHash(certMsg.marshal()) c.writeRecord(recordTypeHandshake, certMsg.marshal()) } @@ -328,7 +367,7 @@ certStatus := new(certificateStatusMsg) certStatus.statusType = statusTypeOCSP certStatus.response = hs.cert.OCSPStaple - hs.finishedHash.Write(certStatus.marshal()) + hs.writeServerHash(certStatus.marshal()) c.writeRecord(recordTypeHandshake, certStatus.marshal()) } @@ -339,7 +378,7 @@ return err } if skx != nil && !config.Bugs.SkipServerKeyExchange { - hs.finishedHash.Write(skx.marshal()) + hs.writeServerHash(skx.marshal()) c.writeRecord(recordTypeHandshake, skx.marshal()) } @@ -367,12 +406,12 @@ if config.ClientCAs != nil { certReq.certificateAuthorities = config.ClientCAs.Subjects() } - hs.finishedHash.Write(certReq.marshal()) + hs.writeServerHash(certReq.marshal()) c.writeRecord(recordTypeHandshake, certReq.marshal()) } helloDone := new(serverHelloDoneMsg) - hs.finishedHash.Write(helloDone.marshal()) + hs.writeServerHash(helloDone.marshal()) c.writeRecord(recordTypeHandshake, helloDone.marshal()) var pub crypto.PublicKey // public key for client auth, if any @@ -390,7 +429,7 @@ c.sendAlert(alertUnexpectedMessage) return unexpectedMessageError(certMsg, msg) } - hs.finishedHash.Write(certMsg.marshal()) + hs.writeClientHash(certMsg.marshal()) if len(certMsg.certificates) == 0 { // The client didn't actually send a certificate @@ -418,7 +457,7 @@ c.sendAlert(alertUnexpectedMessage) return unexpectedMessageError(ckx, msg) } - hs.finishedHash.Write(ckx.marshal()) + hs.writeClientHash(ckx.marshal()) // If we received a client cert in response to our certificate request message, // the client will send us a certificateVerifyMsg immediately after the @@ -494,7 +533,7 @@ return errors.New("could not validate signature of connection nonces: " + err.Error()) } - hs.finishedHash.Write(certVerify.marshal()) + hs.writeClientHash(certVerify.marshal()) } preMasterSecret, err := keyAgreement.processClientKeyExchange(config, hs.cert, ckx, c.vers) @@ -550,7 +589,7 @@ c.sendAlert(alertUnexpectedMessage) return unexpectedMessageError(nextProto, msg) } - hs.finishedHash.Write(nextProto.marshal()) + hs.writeClientHash(nextProto.marshal()) c.clientProtocol = nextProto.proto } @@ -571,7 +610,7 @@ return errors.New("tls: client's Finished message is incorrect") } - hs.finishedHash.Write(clientFinished.marshal()) + hs.writeClientHash(clientFinished.marshal()) return nil } @@ -595,7 +634,7 @@ return err } - hs.finishedHash.Write(m.marshal()) + hs.writeServerHash(m.marshal()) c.writeRecord(recordTypeHandshake, m.marshal()) return nil @@ -607,7 +646,7 @@ finished := new(finishedMsg) finished.verifyData = hs.finishedHash.serverSum(hs.masterSecret) postCCSBytes := finished.marshal() - hs.finishedHash.Write(postCCSBytes) + hs.writeServerHash(postCCSBytes) if c.config.Bugs.FragmentAcrossChangeCipherSpec { c.writeRecord(recordTypeHandshake, postCCSBytes[:5]) @@ -690,6 +729,32 @@ return nil, nil } +func (hs *serverHandshakeState) writeServerHash(msg []byte) { + // writeServerHash is called before writeRecord. + hs.writeHash(msg, hs.c.sendHandshakeSeq) +} + +func (hs *serverHandshakeState) writeClientHash(msg []byte) { + // writeClientHash is called after readHandshake. + hs.writeHash(msg, hs.c.recvHandshakeSeq-1) +} + +func (hs *serverHandshakeState) writeHash(msg []byte, seqno uint16) { + if hs.c.isDTLS { + // This is somewhat hacky. DTLS hashes a slightly different format. + // First, the TLS header. + hs.finishedHash.Write(msg[:4]) + // Then the sequence number and reassembled fragment offset (always 0). + hs.finishedHash.Write([]byte{byte(seqno >> 8), byte(seqno), 0, 0, 0}) + // Then the reassembled fragment (always equal to the message length). + hs.finishedHash.Write(msg[1:4]) + // And then the message body. + hs.finishedHash.Write(msg[4:]) + } else { + hs.finishedHash.Write(msg) + } +} + // tryCipherSuite returns a cipherSuite with the given id if that cipher suite // is acceptable to use. func (c *Conn) tryCipherSuite(id uint16, supportedCipherSuites []uint16, version uint16, ellipticOk, ecdsaOk bool) *cipherSuite { @@ -717,6 +782,9 @@ if version < VersionTLS12 && candidate.flags&suiteTLS12 != 0 { continue } + if c.isDTLS && candidate.flags&suiteNoDTLS != 0 { + continue + } return candidate } }
diff --git a/ssl/test/runner/key_agreement.go b/ssl/test/runner/key_agreement.go index 929eb06..a678fee 100644 --- a/ssl/test/runner/key_agreement.go +++ b/ssl/test/runner/key_agreement.go
@@ -74,6 +74,7 @@ if config.Bugs.RsaClientKeyExchangeVersion != 0 { vers = config.Bugs.RsaClientKeyExchangeVersion } + vers = versionToWire(vers, clientHello.isDTLS) preMasterSecret[0] = byte(vers >> 8) preMasterSecret[1] = byte(vers) _, err := io.ReadFull(config.rand(), preMasterSecret[2:])