Add runner test support for DTLS 1.25

Change-Id: Ic4095814ee814b4274b1e90b8a937a21244b4c55
Bug: 715
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/69427
Reviewed-by: Bob Beck <bbe@google.com>
Commit-Queue: Bob Beck <bbe@google.com>
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index 72793f2..2c70c55 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -1264,7 +1264,8 @@
     return false;
   }
 
-  if (GetProtocolVersion(ssl) >= TLS1_3_VERSION && !config->is_server) {
+  if (GetProtocolVersion(ssl) >= TLS1_3_VERSION && !SSL_is_dtls(ssl) &&
+      !config->is_server) {
     bool expect_new_session =
         !config->expect_no_session && !config->shim_shuts_down;
     if (expect_new_session != test_state->got_new_session) {
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 6bdd664..ab36e56 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -31,8 +31,9 @@
 )
 
 const (
-	VersionDTLS10 = 0xfeff
-	VersionDTLS12 = 0xfefd
+	VersionDTLS10              = 0xfeff
+	VersionDTLS12              = 0xfefd
+	VersionDTLS125Experimental = 0xfc25
 )
 
 var allTLSWireVersions = []uint16{
@@ -44,14 +45,16 @@
 }
 
 var allDTLSWireVersions = []uint16{
+	VersionDTLS125Experimental,
 	VersionDTLS12,
 	VersionDTLS10,
 }
 
 const (
-	maxPlaintext        = 16384        // maximum plaintext payload length
-	maxCiphertext       = 16384 + 2048 // maximum ciphertext payload length
-	tlsRecordHeaderLen  = 5            // record header length
+	maxPlaintext       = 16384        // maximum plaintext payload length
+	maxCiphertext      = 16384 + 2048 // maximum ciphertext payload length
+	tlsRecordHeaderLen = 5            // record header length
+	// TODO(nharper): check whether this value needs to be changed for DTLS 1.3
 	dtlsRecordHeaderLen = 13
 	maxHandshake        = 65536 // maximum handshake we support (protocol max is 16 MB)
 
@@ -2035,10 +2038,6 @@
 		ret = c.MaxVersion
 	}
 	if isDTLS {
-		// We only implement up to DTLS 1.2.
-		if ret > VersionTLS12 {
-			return VersionTLS12
-		}
 		// There is no such thing as DTLS 1.1.
 		if ret == VersionTLS11 {
 			return VersionTLS10
@@ -2084,6 +2083,8 @@
 func wireToVersion(vers uint16, isDTLS bool) (uint16, bool) {
 	if isDTLS {
 		switch vers {
+		case VersionDTLS125Experimental:
+			return VersionTLS13, true
 		case VersionDTLS12:
 			return VersionTLS12, true
 		case VersionDTLS10:
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go
index ca1a037..c94f112 100644
--- a/ssl/test/runner/conn.go
+++ b/ssl/test/runner/conn.go
@@ -242,7 +242,7 @@
 }
 
 // useTrafficSecret sets the current cipher state for TLS 1.3.
-func (hc *halfConn) useTrafficSecret(version uint16, suite *cipherSuite, secret []byte, side trafficDirection) {
+func (hc *halfConn) useTrafficSecret(version uint16, suite *cipherSuite, secret []byte, side trafficDirection, level encryptionLevel) {
 	hc.wireVersion = version
 	protocolVersion, ok := wireToVersion(version, hc.isDTLS)
 	if !ok {
@@ -254,7 +254,11 @@
 		hc.cipher = nullCipher{}
 	}
 	hc.trafficSecret = secret
-	hc.incEpoch()
+	if hc.isDTLS && protocolVersion == VersionTLS13 {
+		hc.setEpoch(uint16(level))
+	} else {
+		hc.incEpoch()
+	}
 }
 
 // resetCipher changes the cipher state back to no encryption to be able
@@ -326,6 +330,19 @@
 	hc.updateOutSeq()
 }
 
+func (hc *halfConn) setEpoch(epoch uint16) {
+	if !hc.isDTLS {
+		panic("Internal error: called setEpoch on non-DTLS connection")
+	}
+	hc.seq[0] = byte(epoch >> 8)
+	hc.seq[1] = byte(epoch)
+	copy(hc.seq[2:], hc.nextSeq[:])
+	for i := range hc.nextSeq {
+		hc.nextSeq[i] = 0
+	}
+	hc.updateOutSeq()
+}
+
 func (hc *halfConn) updateOutSeq() {
 	if hc.config.Bugs.SequenceNumberMapping != nil {
 		seqU64 := binary.BigEndian.Uint64(hc.seq[:])
@@ -486,7 +503,7 @@
 			panic("unknown cipher type")
 		}
 
-		if hc.version >= VersionTLS13 {
+		if hc.version >= VersionTLS13 && !hc.isDTLS {
 			i := len(payload)
 			for i > 0 && payload[i-1] == 0 {
 				i--
@@ -734,7 +751,7 @@
 		c.config.Bugs.MockQUICTransport.readSecret = secret
 		c.config.Bugs.MockQUICTransport.readCipherSuite = suite.id
 	}
-	c.in.useTrafficSecret(version, suite, secret, side)
+	c.in.useTrafficSecret(version, suite, secret, side, level)
 	c.seenHandshakePackEnd = false
 	return nil
 }
@@ -749,7 +766,7 @@
 		c.config.Bugs.MockQUICTransport.writeSecret = secret
 		c.config.Bugs.MockQUICTransport.writeCipherSuite = suite.id
 	}
-	c.out.useTrafficSecret(version, suite, secret, side)
+	c.out.useTrafficSecret(version, suite, secret, side, level)
 }
 
 func (c *Conn) setSkipEarlyData() {
@@ -896,6 +913,10 @@
 	if c.config.Bugs.MockQUICTransport != nil {
 		return nil
 	}
+	if c.isDTLS {
+		// ChangeCipherSpec in DTLS 1.3 is handled within dtlsDoReadRecord.
+		return nil
+	}
 	if !c.expectTLS13ChangeCipherSpec {
 		panic("c.expectTLS13ChangeCipherSpec not set")
 	}
@@ -1173,17 +1194,21 @@
 	if c.out.version < VersionTLS13 || c.out.cipher == nil {
 		return recordLen
 	}
-	paddingLen := c.config.Bugs.RecordPadding
-	if c.config.Bugs.OmitRecordContents {
-		recordLen = paddingLen
-		b.resize(recordHeaderLen + paddingLen)
-	} else {
-		recordLen += 1 + paddingLen
-		b.resize(len(b.data) + 1 + paddingLen)
-		b.data[len(b.data)-paddingLen-1] = byte(typ)
-	}
-	for i := 0; i < paddingLen; i++ {
-		b.data[len(b.data)-paddingLen+i] = 0
+	// TODO(nharper): DTLS 1.3 should be adding padding, but the currently
+	// implemented DTLS 1.25 doesn't include padding.
+	if !c.isDTLS {
+		paddingLen := c.config.Bugs.RecordPadding
+		if c.config.Bugs.OmitRecordContents {
+			recordLen = paddingLen
+			b.resize(recordHeaderLen + paddingLen)
+		} else {
+			recordLen += 1 + paddingLen
+			b.resize(len(b.data) + 1 + paddingLen)
+			b.data[len(b.data)-paddingLen-1] = byte(typ)
+		}
+		for i := 0; i < paddingLen; i++ {
+			b.data[len(b.data)-paddingLen+i] = 0
+		}
 	}
 	if c, ok := c.out.cipher.(*tlsAead); ok {
 		recordLen += c.Overhead()
diff --git a/ssl/test/runner/dtls.go b/ssl/test/runner/dtls.go
index b97f829..b55f347 100644
--- a/ssl/test/runner/dtls.go
+++ b/ssl/test/runner/dtls.go
@@ -62,7 +62,11 @@
 	// version is irrelevant.)
 	if typ != recordTypeAlert {
 		if c.haveVers {
-			if vers != c.wireVersion {
+			wireVersion := c.wireVersion
+			if c.vers >= VersionTLS13 {
+				wireVersion = VersionDTLS12
+			}
+			if vers != wireVersion {
 				c.sendAlert(alertProtocolVersion)
 				return 0, nil, c.in.setErrorLocked(fmt.Errorf("dtls: received record with version %x when expecting version %x", vers, c.wireVersion))
 			}
@@ -76,17 +80,25 @@
 	}
 	epoch := b.data[3:5]
 	seq := b.data[5:11]
-	// For test purposes, require the sequence number be monotonically
-	// increasing, so c.in includes the minimum next sequence number. Gaps
-	// may occur if packets failed to be sent out. A real implementation
-	// would maintain a replay window and such.
-	if !bytes.Equal(epoch, c.in.seq[:2]) {
-		c.sendAlert(alertIllegalParameter)
-		return 0, nil, c.in.setErrorLocked(fmt.Errorf("dtls: bad epoch"))
-	}
-	if bytes.Compare(seq, c.in.seq[2:]) < 0 {
-		c.sendAlert(alertIllegalParameter)
-		return 0, nil, c.in.setErrorLocked(fmt.Errorf("dtls: bad sequence number"))
+	if c.expectTLS13ChangeCipherSpec && typ == recordTypeChangeCipherSpec {
+		// CCS should only be at epoch 0
+		if !bytes.Equal(epoch, []byte{0, 0}) {
+			c.sendAlert(alertIllegalParameter)
+			return 0, nil, c.in.setErrorLocked(fmt.Errorf("dtls: bad epoch, expected CCS at epoch 0"))
+		}
+	} else {
+		// For test purposes, require the sequence number be monotonically
+		// increasing, so c.in includes the minimum next sequence number. Gaps
+		// may occur if packets failed to be sent out. A real implementation
+		// would maintain a replay window and such.
+		if !bytes.Equal(epoch, c.in.seq[:2]) {
+			c.sendAlert(alertIllegalParameter)
+			return 0, nil, c.in.setErrorLocked(fmt.Errorf("dtls: bad epoch, want %x, got %x", c.in.seq[:2], epoch))
+		}
+		if bytes.Compare(seq, c.in.seq[2:]) < 0 {
+			c.sendAlert(alertIllegalParameter)
+			return 0, nil, c.in.setErrorLocked(fmt.Errorf("dtls: bad sequence number"))
+		}
 	}
 	copy(c.in.seq[2:], seq)
 	n := int(b.data[11])<<8 | int(b.data[12])
@@ -94,9 +106,24 @@
 		c.sendAlert(alertRecordOverflow)
 		return 0, nil, c.in.setErrorLocked(fmt.Errorf("dtls: oversized record received with length %d", n))
 	}
+	b, c.rawInput = c.in.splitBlock(b, recordHeaderLen+n)
+
+	// Process CCS
+	if c.expectTLS13ChangeCipherSpec {
+		c.expectTLS13ChangeCipherSpec = false
+		ccsData := b.data[recordHeaderLen:]
+		if typ == recordTypeChangeCipherSpec {
+			if !bytes.Equal(ccsData, []byte{1}) {
+				return 0, nil, c.in.setErrorLocked(fmt.Errorf("dtls: ChangeCipherSpec of length %d did not contain single byte value 0x01", len(ccsData)))
+			}
+			return c.dtlsDoReadRecord(want)
+		}
+		if typ != recordTypeAlert {
+			return 0, nil, c.in.setErrorLocked(fmt.Errorf("dtls: Expected ChangeCipherSpec but got record type %d", typ))
+		}
+	}
 
 	// Process message.
-	b, c.rawInput = c.in.splitBlock(b, recordHeaderLen+n)
 	ok, off, _, alertValue := c.in.decrypt(b)
 	if !ok {
 		// A real DTLS implementation would silently ignore bad records,
@@ -165,7 +192,7 @@
 			}
 		}
 
-		if typ == recordTypeChangeCipherSpec {
+		if typ == recordTypeChangeCipherSpec && c.vers < VersionTLS13 {
 			err = c.out.changeCipherSpec(c.config)
 			if err != nil {
 				return n, c.sendAlertLocked(alertLevelError, err.(alert))
@@ -371,12 +398,13 @@
 			vers = VersionTLS10
 		}
 	}
+	if c.vers >= VersionTLS13 || c.out.version >= VersionTLS13 {
+		vers = VersionDTLS12
+	}
 	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.outSeq[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 {
@@ -388,6 +416,9 @@
 		}
 	}
 	copy(b.data[recordHeaderLen+explicitIVLen:], data)
+	recordLen := c.addTLS13Padding(b, recordHeaderLen, len(data), typ)
+	b.data[11] = byte(recordLen >> 8)
+	b.data[12] = byte(recordLen)
 	c.out.encrypt(b, explicitIVLen, typ)
 
 	// Flush the current pending packet if necessary.
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go
index 6d2f902..38dbc6d 100644
--- a/ssl/test/runner/handshake_messages.go
+++ b/ssl/test/runner/handshake_messages.go
@@ -1219,7 +1219,11 @@
 		if m.versOverride != 0 {
 			hello.AddUint16(m.versOverride)
 		} else if vers >= VersionTLS13 {
-			hello.AddUint16(VersionTLS12)
+			legacyVersion := uint16(VersionTLS12)
+			if m.isDTLS {
+				legacyVersion = VersionDTLS12
+			}
+			hello.AddUint16(legacyVersion)
 		} else {
 			hello.AddUint16(m.vers)
 		}
@@ -1316,7 +1320,7 @@
 	}
 
 	// Parse out the version from supported_versions if available.
-	if m.vers == VersionTLS12 {
+	if vers == VersionTLS12 {
 		extensionsCopy := extensions
 		for len(extensionsCopy) > 0 {
 			var extension uint16
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index 2e87d54..deb9ca5 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -1040,7 +1040,7 @@
 	}
 	c.flushHandshake()
 
-	if !c.config.Bugs.SkipChangeCipherSpec && !sendHelloRetryRequest {
+	if !c.config.Bugs.SkipChangeCipherSpec && !sendHelloRetryRequest && !c.isDTLS {
 		c.writeRecord(recordTypeChangeCipherSpec, []byte{1})
 	}
 
@@ -1420,7 +1420,8 @@
 
 	// TODO(davidben): Allow configuring the number of tickets sent for
 	// testing.
-	if !c.config.SessionTicketsDisabled && foundKEMode {
+	// TODO(nharper): Add support for post-handshake messages in DTLS 1.3.
+	if !c.config.SessionTicketsDisabled && foundKEMode && !c.isDTLS {
 		ticketCount := 2
 		for i := 0; i < ticketCount; i++ {
 			c.SendNewSessionTicket([]byte{byte(i)})
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 97eac02..2cd1549 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -1835,6 +1835,8 @@
 		version:     VersionTLS13,
 		excludeFlag: "-no-tls13",
 		hasQUIC:     true,
+		hasDTLS:     true,
+		versionDTLS: VersionDTLS125Experimental,
 		versionWire: VersionTLS13,
 	},
 }
@@ -2353,7 +2355,7 @@
 		{
 			protocol:      dtls,
 			name:          "DisableEverything-DTLS",
-			flags:         []string{"-no-tls12", "-no-tls1"},
+			flags:         []string{"-no-tls13", "-no-tls12", "-no-tls1"},
 			shouldFail:    true,
 			expectedError: ":NO_SUPPORTED_VERSIONS_ENABLED:",
 		},
@@ -3219,10 +3221,13 @@
 			},
 			resumeSession: true,
 		},
+		// TODO(crbug.com/boringssl/715): This test and the next shouldn't be
+		// restricted to a max version of TLS 1.2, but they're broken in DTLS 1.3.
 		{
 			protocol: dtls,
 			name:     "DTLS-SendExtraFinished",
 			config: Config{
+				MaxVersion: VersionTLS12,
 				Bugs: ProtocolBugs{
 					SendExtraFinished: true,
 				},
@@ -3234,6 +3239,7 @@
 			protocol: dtls,
 			name:     "DTLS-SendExtraFinished-Reordered",
 			config: Config{
+				MaxVersion: VersionTLS12,
 				Bugs: ProtocolBugs{
 					MaxHandshakeRecordLength:  2,
 					ReorderHandshakeFragments: true,
@@ -3767,51 +3773,53 @@
 	// Verify exporters interoperate.
 	exportKeyingMaterial := 1024
 
-	testCases = append(testCases, testCase{
-		testType: serverTest,
-		protocol: protocol,
-		name:     prefix + ver.name + "-" + suite.name + "-server",
-		config: Config{
-			MinVersion:           ver.version,
-			MaxVersion:           ver.version,
-			CipherSuites:         []uint16{suite.id},
-			Credential:           cert,
-			PreSharedKey:         []byte(psk),
-			PreSharedKeyIdentity: pskIdentity,
-			Bugs: ProtocolBugs{
-				AdvertiseAllConfiguredCiphers: true,
+	if ver.version != VersionTLS13 || !ver.hasDTLS {
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			protocol: protocol,
+			name:     prefix + ver.name + "-" + suite.name + "-server",
+			config: Config{
+				MinVersion:           ver.version,
+				MaxVersion:           ver.version,
+				CipherSuites:         []uint16{suite.id},
+				Credential:           cert,
+				PreSharedKey:         []byte(psk),
+				PreSharedKeyIdentity: pskIdentity,
+				Bugs: ProtocolBugs{
+					AdvertiseAllConfiguredCiphers: true,
+				},
 			},
-		},
-		shimCertificate:      cert,
-		flags:                flags,
-		resumeSession:        true,
-		shouldFail:           shouldFail,
-		expectedError:        expectedServerError,
-		exportKeyingMaterial: exportKeyingMaterial,
-	})
+			shimCertificate:      cert,
+			flags:                flags,
+			resumeSession:        true,
+			shouldFail:           shouldFail,
+			expectedError:        expectedServerError,
+			exportKeyingMaterial: exportKeyingMaterial,
+		})
 
-	testCases = append(testCases, testCase{
-		testType: clientTest,
-		protocol: protocol,
-		name:     prefix + ver.name + "-" + suite.name + "-client",
-		config: Config{
-			MinVersion:           ver.version,
-			MaxVersion:           ver.version,
-			CipherSuites:         serverCipherSuites,
-			Credential:           cert,
-			PreSharedKey:         []byte(psk),
-			PreSharedKeyIdentity: pskIdentity,
-			Bugs: ProtocolBugs{
-				IgnorePeerCipherPreferences: shouldFail,
-				SendCipherSuite:             sendCipherSuite,
+		testCases = append(testCases, testCase{
+			testType: clientTest,
+			protocol: protocol,
+			name:     prefix + ver.name + "-" + suite.name + "-client",
+			config: Config{
+				MinVersion:           ver.version,
+				MaxVersion:           ver.version,
+				CipherSuites:         serverCipherSuites,
+				Credential:           cert,
+				PreSharedKey:         []byte(psk),
+				PreSharedKeyIdentity: pskIdentity,
+				Bugs: ProtocolBugs{
+					IgnorePeerCipherPreferences: shouldFail,
+					SendCipherSuite:             sendCipherSuite,
+				},
 			},
-		},
-		flags:                flags,
-		resumeSession:        true,
-		shouldFail:           shouldFail,
-		expectedError:        expectedClientError,
-		exportKeyingMaterial: exportKeyingMaterial,
-	})
+			flags:                flags,
+			resumeSession:        true,
+			shouldFail:           shouldFail,
+			expectedError:        expectedClientError,
+			exportKeyingMaterial: exportKeyingMaterial,
+		})
+	}
 
 	if shouldFail {
 		return
@@ -6579,7 +6587,8 @@
 		name:     "VersionTooLow-DTLS",
 		config: Config{
 			Bugs: ProtocolBugs{
-				SendClientVersion: 0xffff,
+				SendClientVersion:     0xffff,
+				OmitSupportedVersions: true,
 			},
 		},
 		shouldFail:    true,
@@ -8201,7 +8210,7 @@
 						},
 						flags: []string{
 							"-max-version",
-							strconv.Itoa(int(ver.versionWire)),
+							ver.shimFlag(protocol),
 							"-quic-transport-params",
 							base64FlagValue([]byte{3, 4}),
 							"-quic-use-legacy-codepoint", useCodepointFlag,
@@ -20661,6 +20670,27 @@
 	}
 }
 
+// TODO(crbug.com/boringssl/715): Once our DTLS 1.3 implementation supports
+// resumption, remove this filter.
+func filterTests() {
+	tests := make([]testCase, 0, len(testCases))
+	isDTLS13ResumptionTest := func(test testCase) bool {
+		if !test.resumeSession {
+			return false
+		}
+		if test.protocol != dtls {
+			return false
+		}
+		return test.config.MaxVersion == VersionTLS13 || test.config.MinVersion == VersionTLS13 || test.expectations.version == VersionTLS13
+	}
+	for _, test := range testCases {
+		if !isDTLS13ResumptionTest(test) {
+			tests = append(tests, test)
+		}
+	}
+	testCases = tests
+}
+
 func main() {
 	flag.Parse()
 	var err error
@@ -20746,6 +20776,7 @@
 	testCases = append(testCases, toAppend...)
 
 	checkTests()
+	filterTests()
 
 	dispatcher, err := newShimDispatcher()
 	if err != nil {
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index 9ecbea4..ae4f87b 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -2205,6 +2205,10 @@
       !SSL_set_min_proto_version(ssl.get(), min_version)) {
     return nullptr;
   }
+  if (is_dtls && max_version == 0 &&
+      !SSL_set_max_proto_version(ssl.get(), DTLS1_3_EXPERIMENTAL_VERSION)) {
+    return nullptr;
+  }
   if (max_version != 0 &&
       !SSL_set_max_proto_version(ssl.get(), max_version)) {
     return nullptr;