diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go index 1c64c6af..d4a68170 100644 --- a/ssl/test/runner/conn.go +++ b/ssl/test/runner/conn.go @@ -756,8 +756,11 @@ Again: if typ != want { // A client might need to process a HelloRequest from // the server, thus receiving a handshake message when - // application data is expected is ok. - if !c.isClient { + // application data is expected is ok. Moreover, a DTLS + // peer who sends Finished second may retransmit the + // final leg. BoringSSL retrainsmits on an internal + // timer, so this may also occur in test code. + if !c.isClient && !c.isDTLS { return c.in.setErrorLocked(c.sendAlert(alertNoRenegotiation)) } } @@ -1093,9 +1096,9 @@ func (c *Conn) Read(b []byte) (n int, err error) { // Soft error, like EAGAIN return 0, err } - if c.hand.Len() > 0 { + if c.hand.Len() > 0 && !c.isDTLS { // We received handshake bytes, indicating the - // start of a renegotiation. + // start of a renegotiation or a DTLS retransmit. if err := c.handleRenegotiation(); err != nil { return 0, err } diff --git a/ssl/test/runner/dtls.go b/ssl/test/runner/dtls.go index 2e1fb65a..a3959803 100644 --- a/ssl/test/runner/dtls.go +++ b/ssl/test/runner/dtls.go @@ -38,6 +38,7 @@ func wireToVersion(vers uint16, isDTLS bool) uint16 { } func (c *Conn) dtlsDoReadRecord(want recordType) (recordType, *block, error) { +Again: recordHeaderLen := dtlsRecordHeaderLen if c.rawInput == nil { @@ -81,6 +82,13 @@ func (c *Conn) dtlsDoReadRecord(want recordType) (recordType, *block, error) { } } 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 @@ -242,9 +250,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. It may - // need to be cleverer if we ever test BoringSSL's retransmit - // behavior. + // 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. @@ -273,9 +281,16 @@ func (c *Conn) dtlsDoReadHandshake() ([]byte, error) { } 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") + 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.