Support resumption in DTLS 1.3.

Bug: 42290594
Change-Id: Ic618dfa59d312979e613b0fb231dff4ec467195e
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/72087
Commit-Queue: David Benjamin <davidben@google.com>
Auto-Submit: Nick Harper <nharper@chromium.org>
Reviewed-by: David Benjamin <davidben@google.com>
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index 827fa18..3f0ffa7 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -856,8 +856,11 @@
 		// We may have a pre-1.3 session if SendBothTickets is set.
 		if session.vers < VersionTLS13 {
 			version = VersionTLS13
+			if c.isDTLS {
+				version = VersionDTLS125Experimental
+			}
 		}
-		generatePSKBinders(version, hello, session, nil, nil, c.config)
+		generatePSKBinders(version, c.isDTLS, hello, session, nil, nil, c.config)
 	}
 
 	if c.config.Bugs.SendClientHelloWithFixes != nil {
@@ -1573,7 +1576,7 @@
 	hello.raw = nil
 
 	if len(hello.pskIdentities) > 0 {
-		generatePSKBinders(c.wireVersion, hello, hs.session, firstHelloBytes, helloRetryRequest.marshal(), c.config)
+		generatePSKBinders(c.wireVersion, c.isDTLS, hello, hs.session, firstHelloBytes, helloRetryRequest.marshal(), c.config)
 	}
 
 	if outerHello != nil {
@@ -2406,7 +2409,7 @@
 	copy(b[len(b)-len(xb):], xb)
 }
 
-func generatePSKBinders(version uint16, hello *clientHelloMsg, session *ClientSessionState, firstClientHello, helloRetryRequest []byte, config *Config) {
+func generatePSKBinders(version uint16, isDTLS bool, hello *clientHelloMsg, session *ClientSessionState, firstClientHello, helloRetryRequest []byte, config *Config) {
 	maybeCorruptBinder := !config.Bugs.OnlyCorruptSecondPSKBinder || len(firstClientHello) > 0
 	binderLen := session.cipherSuite.hash().Size()
 	numBinders := 1
@@ -2438,7 +2441,7 @@
 	helloBytes := hello.marshal()
 	binderSize := len(hello.pskBinders)*(binderLen+1) + 2
 	truncatedHello := helloBytes[:len(helloBytes)-binderSize]
-	binder := computePSKBinder(session.secret, version, resumptionPSKBinderLabel, session.cipherSuite, firstClientHello, helloRetryRequest, truncatedHello)
+	binder := computePSKBinder(session.secret, version, isDTLS, resumptionPSKBinderLabel, session.cipherSuite, firstClientHello, helloRetryRequest, truncatedHello)
 	if maybeCorruptBinder {
 		if config.Bugs.SendShortPSKBinder {
 			binder = binder[:binderLen]
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index 2c210eb..7ed4745 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -665,7 +665,7 @@
 
 			if !replacedPSKIdentities {
 				binderToVerify := hs.clientHello.pskBinders[i]
-				if err := verifyPSKBinder(c.wireVersion, hs.clientHello, sessionState, binderToVerify, []byte{}, []byte{}); err != nil {
+				if err := verifyPSKBinder(c.wireVersion, c.isDTLS, hs.clientHello, sessionState, binderToVerify, []byte{}, []byte{}); err != nil {
 					return err
 				}
 			}
@@ -893,7 +893,7 @@
 			}
 			if found {
 				binderToVerify := newClientHello.pskBinders[pskIndex]
-				if err := verifyPSKBinder(c.wireVersion, newClientHello, hs.sessionState, binderToVerify, hs.clientHello.marshal(), helloRetryRequest.marshal()); err != nil {
+				if err := verifyPSKBinder(c.wireVersion, c.isDTLS, newClientHello, hs.sessionState, binderToVerify, hs.clientHello.marshal(), helloRetryRequest.marshal()); err != nil {
 					return err
 				}
 			} else if !config.Bugs.AcceptAnySession {
@@ -1425,8 +1425,7 @@
 
 	// TODO(davidben): Allow configuring the number of tickets sent for
 	// testing.
-	// TODO(nharper): Add support for post-handshake messages in DTLS 1.3.
-	if !c.config.SessionTicketsDisabled && foundKEMode && !c.isDTLS {
+	if !c.config.SessionTicketsDisabled && foundKEMode {
 		ticketCount := 2
 		for i := 0; i < ticketCount; i++ {
 			c.SendNewSessionTicket([]byte{byte(i)})
@@ -2404,7 +2403,7 @@
 	return val&0x0f0f == 0x0a0a && val&0xff == val>>8
 }
 
-func verifyPSKBinder(version uint16, clientHello *clientHelloMsg, sessionState *sessionState, binderToVerify, firstClientHello, helloRetryRequest []byte) error {
+func verifyPSKBinder(version uint16, isDTLS bool, clientHello *clientHelloMsg, sessionState *sessionState, binderToVerify, firstClientHello, helloRetryRequest []byte) error {
 	binderLen := 2
 	for _, binder := range clientHello.pskBinders {
 		binderLen += 1 + len(binder)
@@ -2417,7 +2416,7 @@
 		return errors.New("tls: Unknown cipher suite for PSK in session")
 	}
 
-	binder := computePSKBinder(sessionState.secret, version, resumptionPSKBinderLabel, pskCipherSuite, firstClientHello, helloRetryRequest, truncatedHello)
+	binder := computePSKBinder(sessionState.secret, version, isDTLS, resumptionPSKBinderLabel, pskCipherSuite, firstClientHello, helloRetryRequest, truncatedHello)
 	if !bytes.Equal(binder, binderToVerify) {
 		return errors.New("tls: PSK binder does not verify")
 	}
diff --git a/ssl/test/runner/prf.go b/ssl/test/runner/prf.go
index 0324c87..5d56257 100644
--- a/ssl/test/runner/prf.go
+++ b/ssl/test/runner/prf.go
@@ -466,8 +466,8 @@
 	return hkdfExpandLabel(hash, secret, applicationTrafficLabel, nil, hash.Size(), isDTLS)
 }
 
-func computePSKBinder(psk []byte, version uint16, label []byte, cipherSuite *cipherSuite, clientHello, helloRetryRequest, truncatedHello []byte) []byte {
-	finishedHash := newFinishedHash(version, false, cipherSuite)
+func computePSKBinder(psk []byte, version uint16, isDTLS bool, label []byte, cipherSuite *cipherSuite, clientHello, helloRetryRequest, truncatedHello []byte) []byte {
+	finishedHash := newFinishedHash(version, isDTLS, cipherSuite)
 	finishedHash.addEntropy(psk)
 	binderKey := finishedHash.deriveSecret(label)
 	finishedHash.Write(clientHello)
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 72937af..ecfc61e 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -5644,7 +5644,8 @@
 						}, flags...),
 						resumeSession: true,
 					})
-					if vers.version >= VersionTLS13 {
+					// TODO(crbug.com/42290594): Support 0-RTT in DTLS 1.3.
+					if vers.version >= VersionTLS13 && config.protocol != dtls {
 						tests = append(tests, testCase{
 							testType: testType,
 							name:     "EarlyData-RejectTicket-Client-Reverify" + suffix,
@@ -7901,255 +7902,258 @@
 					})
 
 					// Test that ALPS is carried over on 0-RTT.
-					for _, empty := range []bool{false, true} {
-						maybeEmpty := ""
-						runnerSettings := "runner"
-						shimSettings := "shim"
-						if empty {
-							maybeEmpty = "Empty-"
-							runnerSettings = ""
-							shimSettings = ""
-						}
+					// TODO(crbug.com/42290594): Support 0-RTT in DTLS 1.3.
+					if protocol != dtls {
+						for _, empty := range []bool{false, true} {
+							maybeEmpty := ""
+							runnerSettings := "runner"
+							shimSettings := "shim"
+							if empty {
+								maybeEmpty = "Empty-"
+								runnerSettings = ""
+								shimSettings = ""
+							}
 
-						expectations = connectionExpectations{
-							peerApplicationSettingsOld: []byte(shimSettings),
-						}
-						if alpsCodePoint == ALPSUseCodepointNew {
 							expectations = connectionExpectations{
-								peerApplicationSettings: []byte(shimSettings),
+								peerApplicationSettingsOld: []byte(shimSettings),
 							}
-						}
-						testCases = append(testCases, testCase{
-							protocol:           protocol,
-							testType:           clientTest,
-							name:               fmt.Sprintf("ALPS-EarlyData-Client-%s-%s-%s", alpsCodePoint, maybeEmpty, suffix),
-							skipQUICALPNConfig: true,
-							config: Config{
-								MaxVersion:          ver.version,
-								NextProtos:          []string{"proto"},
-								ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)},
-								ALPSUseNewCodepoint: alpsCodePoint,
-							},
-							resumeSession: true,
-							earlyData:     true,
-							flags: append([]string{
-								"-advertise-alpn", "\x05proto",
-								"-expect-alpn", "proto",
-								"-application-settings", "proto," + shimSettings,
-								"-expect-peer-application-settings", runnerSettings,
-							}, alpsFlags...),
-							expectations: expectations,
-						})
-						testCases = append(testCases, testCase{
-							protocol:           protocol,
-							testType:           serverTest,
-							name:               fmt.Sprintf("ALPS-EarlyData-Server-%s-%s-%s", alpsCodePoint, maybeEmpty, suffix),
-							skipQUICALPNConfig: true,
-							config: Config{
-								MaxVersion:          ver.version,
-								NextProtos:          []string{"proto"},
-								ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)},
-								ALPSUseNewCodepoint: alpsCodePoint,
-							},
-							resumeSession: true,
-							earlyData:     true,
-							flags: append([]string{
-								"-select-alpn", "proto",
-								"-application-settings", "proto," + shimSettings,
-								"-expect-peer-application-settings", runnerSettings,
-							}, alpsFlags...),
-							expectations: expectations,
-						})
-
-						// Sending application settings in 0-RTT handshakes is forbidden.
-						testCases = append(testCases, testCase{
-							protocol:           protocol,
-							testType:           clientTest,
-							name:               fmt.Sprintf("ALPS-EarlyData-SendApplicationSettingsWithEarlyData-Client-%s-%s-%s", alpsCodePoint, maybeEmpty, suffix),
-							skipQUICALPNConfig: true,
-							config: Config{
-								MaxVersion:          ver.version,
-								NextProtos:          []string{"proto"},
-								ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)},
-								Bugs: ProtocolBugs{
-									SendApplicationSettingsWithEarlyData: true,
-								},
-								ALPSUseNewCodepoint: alpsCodePoint,
-							},
-							resumeSession: true,
-							earlyData:     true,
-							flags: append([]string{
-								"-advertise-alpn", "\x05proto",
-								"-expect-alpn", "proto",
-								"-application-settings", "proto," + shimSettings,
-								"-expect-peer-application-settings", runnerSettings,
-							}, alpsFlags...),
-							expectations:       expectations,
-							shouldFail:         true,
-							expectedError:      ":UNEXPECTED_EXTENSION_ON_EARLY_DATA:",
-							expectedLocalError: "remote error: illegal parameter",
-						})
-						testCases = append(testCases, testCase{
-							protocol:           protocol,
-							testType:           serverTest,
-							name:               fmt.Sprintf("ALPS-EarlyData-SendApplicationSettingsWithEarlyData-Server-%s-%s-%s", alpsCodePoint, maybeEmpty, suffix),
-							skipQUICALPNConfig: true,
-							config: Config{
-								MaxVersion:          ver.version,
-								NextProtos:          []string{"proto"},
-								ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)},
-								Bugs: ProtocolBugs{
-									SendApplicationSettingsWithEarlyData: true,
-								},
-								ALPSUseNewCodepoint: alpsCodePoint,
-							},
-							resumeSession: true,
-							earlyData:     true,
-							flags: append([]string{
-								"-select-alpn", "proto",
-								"-application-settings", "proto," + shimSettings,
-								"-expect-peer-application-settings", runnerSettings,
-							}, alpsFlags...),
-							expectations:       expectations,
-							shouldFail:         true,
-							expectedError:      ":UNEXPECTED_MESSAGE:",
-							expectedLocalError: "remote error: unexpected message",
-						})
-					}
-
-					// Test that the client and server each decline early data if local
-					// ALPS preferences has changed for the current connection.
-					alpsMismatchTests := []struct {
-						name                            string
-						initialSettings, resumeSettings []byte
-					}{
-						{"DifferentValues", []byte("settings1"), []byte("settings2")},
-						{"OnOff", []byte("settings"), nil},
-						{"OffOn", nil, []byte("settings")},
-						// The empty settings value should not be mistaken for ALPS not
-						// being negotiated.
-						{"OnEmpty", []byte("settings"), []byte{}},
-						{"EmptyOn", []byte{}, []byte("settings")},
-						{"EmptyOff", []byte{}, nil},
-						{"OffEmpty", nil, []byte{}},
-					}
-					for _, test := range alpsMismatchTests {
-						flags := []string{"-on-resume-expect-early-data-reason", "alps_mismatch"}
-						flags = append(flags, alpsFlags...)
-						if test.initialSettings != nil {
-							flags = append(flags, "-on-initial-application-settings", "proto,"+string(test.initialSettings))
-							flags = append(flags, "-on-initial-expect-peer-application-settings", "runner")
-						}
-						if test.resumeSettings != nil {
-							flags = append(flags, "-on-resume-application-settings", "proto,"+string(test.resumeSettings))
-							flags = append(flags, "-on-resume-expect-peer-application-settings", "runner")
-						}
-
-						expectations = connectionExpectations{
-							peerApplicationSettingsOld: test.initialSettings,
-						}
-						resumeExpectations = &connectionExpectations{
-							peerApplicationSettingsOld: test.resumeSettings,
-						}
-						if alpsCodePoint == ALPSUseCodepointNew {
-							expectations = connectionExpectations{
-								peerApplicationSettings: test.initialSettings,
+							if alpsCodePoint == ALPSUseCodepointNew {
+								expectations = connectionExpectations{
+									peerApplicationSettings: []byte(shimSettings),
+								}
 							}
-							resumeExpectations = &connectionExpectations{
-								peerApplicationSettings: test.resumeSettings,
-							}
-						}
-						// The client should not offer early data if the session is
-						// inconsistent with the new configuration. Note that if
-						// the session did not negotiate ALPS (test.initialSettings
-						// is nil), the client always offers early data.
-						if test.initialSettings != nil {
 							testCases = append(testCases, testCase{
 								protocol:           protocol,
 								testType:           clientTest,
-								name:               fmt.Sprintf("ALPS-EarlyData-Mismatch-%s-Client-%s-%s", test.name, alpsCodePoint, suffix),
+								name:               fmt.Sprintf("ALPS-EarlyData-Client-%s-%s-%s", alpsCodePoint, maybeEmpty, suffix),
 								skipQUICALPNConfig: true,
 								config: Config{
 									MaxVersion:          ver.version,
-									MaxEarlyDataSize:    16384,
+									NextProtos:          []string{"proto"},
+									ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)},
+									ALPSUseNewCodepoint: alpsCodePoint,
+								},
+								resumeSession: true,
+								earlyData:     true,
+								flags: append([]string{
+									"-advertise-alpn", "\x05proto",
+									"-expect-alpn", "proto",
+									"-application-settings", "proto," + shimSettings,
+									"-expect-peer-application-settings", runnerSettings,
+								}, alpsFlags...),
+								expectations: expectations,
+							})
+							testCases = append(testCases, testCase{
+								protocol:           protocol,
+								testType:           serverTest,
+								name:               fmt.Sprintf("ALPS-EarlyData-Server-%s-%s-%s", alpsCodePoint, maybeEmpty, suffix),
+								skipQUICALPNConfig: true,
+								config: Config{
+									MaxVersion:          ver.version,
+									NextProtos:          []string{"proto"},
+									ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)},
+									ALPSUseNewCodepoint: alpsCodePoint,
+								},
+								resumeSession: true,
+								earlyData:     true,
+								flags: append([]string{
+									"-select-alpn", "proto",
+									"-application-settings", "proto," + shimSettings,
+									"-expect-peer-application-settings", runnerSettings,
+								}, alpsFlags...),
+								expectations: expectations,
+							})
+
+							// Sending application settings in 0-RTT handshakes is forbidden.
+							testCases = append(testCases, testCase{
+								protocol:           protocol,
+								testType:           clientTest,
+								name:               fmt.Sprintf("ALPS-EarlyData-SendApplicationSettingsWithEarlyData-Client-%s-%s-%s", alpsCodePoint, maybeEmpty, suffix),
+								skipQUICALPNConfig: true,
+								config: Config{
+									MaxVersion:          ver.version,
+									NextProtos:          []string{"proto"},
+									ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)},
+									Bugs: ProtocolBugs{
+										SendApplicationSettingsWithEarlyData: true,
+									},
+									ALPSUseNewCodepoint: alpsCodePoint,
+								},
+								resumeSession: true,
+								earlyData:     true,
+								flags: append([]string{
+									"-advertise-alpn", "\x05proto",
+									"-expect-alpn", "proto",
+									"-application-settings", "proto," + shimSettings,
+									"-expect-peer-application-settings", runnerSettings,
+								}, alpsFlags...),
+								expectations:       expectations,
+								shouldFail:         true,
+								expectedError:      ":UNEXPECTED_EXTENSION_ON_EARLY_DATA:",
+								expectedLocalError: "remote error: illegal parameter",
+							})
+							testCases = append(testCases, testCase{
+								protocol:           protocol,
+								testType:           serverTest,
+								name:               fmt.Sprintf("ALPS-EarlyData-SendApplicationSettingsWithEarlyData-Server-%s-%s-%s", alpsCodePoint, maybeEmpty, suffix),
+								skipQUICALPNConfig: true,
+								config: Config{
+									MaxVersion:          ver.version,
+									NextProtos:          []string{"proto"},
+									ApplicationSettings: map[string][]byte{"proto": []byte(runnerSettings)},
+									Bugs: ProtocolBugs{
+										SendApplicationSettingsWithEarlyData: true,
+									},
+									ALPSUseNewCodepoint: alpsCodePoint,
+								},
+								resumeSession: true,
+								earlyData:     true,
+								flags: append([]string{
+									"-select-alpn", "proto",
+									"-application-settings", "proto," + shimSettings,
+									"-expect-peer-application-settings", runnerSettings,
+								}, alpsFlags...),
+								expectations:       expectations,
+								shouldFail:         true,
+								expectedError:      ":UNEXPECTED_MESSAGE:",
+								expectedLocalError: "remote error: unexpected message",
+							})
+						}
+
+						// Test that the client and server each decline early data if local
+						// ALPS preferences has changed for the current connection.
+						alpsMismatchTests := []struct {
+							name                            string
+							initialSettings, resumeSettings []byte
+						}{
+							{"DifferentValues", []byte("settings1"), []byte("settings2")},
+							{"OnOff", []byte("settings"), nil},
+							{"OffOn", nil, []byte("settings")},
+							// The empty settings value should not be mistaken for ALPS not
+							// being negotiated.
+							{"OnEmpty", []byte("settings"), []byte{}},
+							{"EmptyOn", []byte{}, []byte("settings")},
+							{"EmptyOff", []byte{}, nil},
+							{"OffEmpty", nil, []byte{}},
+						}
+						for _, test := range alpsMismatchTests {
+							flags := []string{"-on-resume-expect-early-data-reason", "alps_mismatch"}
+							flags = append(flags, alpsFlags...)
+							if test.initialSettings != nil {
+								flags = append(flags, "-on-initial-application-settings", "proto,"+string(test.initialSettings))
+								flags = append(flags, "-on-initial-expect-peer-application-settings", "runner")
+							}
+							if test.resumeSettings != nil {
+								flags = append(flags, "-on-resume-application-settings", "proto,"+string(test.resumeSettings))
+								flags = append(flags, "-on-resume-expect-peer-application-settings", "runner")
+							}
+
+							expectations = connectionExpectations{
+								peerApplicationSettingsOld: test.initialSettings,
+							}
+							resumeExpectations = &connectionExpectations{
+								peerApplicationSettingsOld: test.resumeSettings,
+							}
+							if alpsCodePoint == ALPSUseCodepointNew {
+								expectations = connectionExpectations{
+									peerApplicationSettings: test.initialSettings,
+								}
+								resumeExpectations = &connectionExpectations{
+									peerApplicationSettings: test.resumeSettings,
+								}
+							}
+							// The client should not offer early data if the session is
+							// inconsistent with the new configuration. Note that if
+							// the session did not negotiate ALPS (test.initialSettings
+							// is nil), the client always offers early data.
+							if test.initialSettings != nil {
+								testCases = append(testCases, testCase{
+									protocol:           protocol,
+									testType:           clientTest,
+									name:               fmt.Sprintf("ALPS-EarlyData-Mismatch-%s-Client-%s-%s", test.name, alpsCodePoint, suffix),
+									skipQUICALPNConfig: true,
+									config: Config{
+										MaxVersion:          ver.version,
+										MaxEarlyDataSize:    16384,
+										NextProtos:          []string{"proto"},
+										ApplicationSettings: map[string][]byte{"proto": []byte("runner")},
+										ALPSUseNewCodepoint: alpsCodePoint,
+									},
+									resumeSession: true,
+									flags: append([]string{
+										"-enable-early-data",
+										"-expect-ticket-supports-early-data",
+										"-expect-no-offer-early-data",
+										"-advertise-alpn", "\x05proto",
+										"-expect-alpn", "proto",
+									}, flags...),
+									expectations:       expectations,
+									resumeExpectations: resumeExpectations,
+								})
+							}
+
+							// The server should reject early data if the session is
+							// inconsistent with the new selection.
+							testCases = append(testCases, testCase{
+								protocol:           protocol,
+								testType:           serverTest,
+								name:               fmt.Sprintf("ALPS-EarlyData-Mismatch-%s-Server-%s-%s", test.name, alpsCodePoint, suffix),
+								skipQUICALPNConfig: true,
+								config: Config{
+									MaxVersion:          ver.version,
 									NextProtos:          []string{"proto"},
 									ApplicationSettings: map[string][]byte{"proto": []byte("runner")},
 									ALPSUseNewCodepoint: alpsCodePoint,
 								},
-								resumeSession: true,
+								resumeSession:           true,
+								earlyData:               true,
+								expectEarlyDataRejected: true,
 								flags: append([]string{
-									"-enable-early-data",
-									"-expect-ticket-supports-early-data",
-									"-expect-no-offer-early-data",
-									"-advertise-alpn", "\x05proto",
-									"-expect-alpn", "proto",
+									"-select-alpn", "proto",
 								}, flags...),
 								expectations:       expectations,
 								resumeExpectations: resumeExpectations,
 							})
 						}
 
-						// The server should reject early data if the session is
-						// inconsistent with the new selection.
+						// Test that 0-RTT continues working when the shim configures
+						// ALPS but the peer does not.
 						testCases = append(testCases, testCase{
 							protocol:           protocol,
-							testType:           serverTest,
-							name:               fmt.Sprintf("ALPS-EarlyData-Mismatch-%s-Server-%s-%s", test.name, alpsCodePoint, suffix),
+							testType:           clientTest,
+							name:               fmt.Sprintf("ALPS-EarlyData-Client-ServerDecline-%s-%s", alpsCodePoint, suffix),
 							skipQUICALPNConfig: true,
 							config: Config{
 								MaxVersion:          ver.version,
 								NextProtos:          []string{"proto"},
-								ApplicationSettings: map[string][]byte{"proto": []byte("runner")},
 								ALPSUseNewCodepoint: alpsCodePoint,
 							},
-							resumeSession:           true,
-							earlyData:               true,
-							expectEarlyDataRejected: true,
+							resumeSession: true,
+							earlyData:     true,
+							flags: append([]string{
+								"-advertise-alpn", "\x05proto",
+								"-expect-alpn", "proto",
+								"-application-settings", "proto,shim",
+							}, alpsFlags...),
+						})
+						testCases = append(testCases, testCase{
+							protocol:           protocol,
+							testType:           serverTest,
+							name:               fmt.Sprintf("ALPS-EarlyData-Server-ClientNoOffe-%s-%s", alpsCodePoint, suffix),
+							skipQUICALPNConfig: true,
+							config: Config{
+								MaxVersion:          ver.version,
+								NextProtos:          []string{"proto"},
+								ALPSUseNewCodepoint: alpsCodePoint,
+							},
+							resumeSession: true,
+							earlyData:     true,
 							flags: append([]string{
 								"-select-alpn", "proto",
-							}, flags...),
-							expectations:       expectations,
-							resumeExpectations: resumeExpectations,
+								"-application-settings", "proto,shim",
+							}, alpsFlags...),
 						})
 					}
-
-					// Test that 0-RTT continues working when the shim configures
-					// ALPS but the peer does not.
-					testCases = append(testCases, testCase{
-						protocol:           protocol,
-						testType:           clientTest,
-						name:               fmt.Sprintf("ALPS-EarlyData-Client-ServerDecline-%s-%s", alpsCodePoint, suffix),
-						skipQUICALPNConfig: true,
-						config: Config{
-							MaxVersion:          ver.version,
-							NextProtos:          []string{"proto"},
-							ALPSUseNewCodepoint: alpsCodePoint,
-						},
-						resumeSession: true,
-						earlyData:     true,
-						flags: append([]string{
-							"-advertise-alpn", "\x05proto",
-							"-expect-alpn", "proto",
-							"-application-settings", "proto,shim",
-						}, alpsFlags...),
-					})
-					testCases = append(testCases, testCase{
-						protocol:           protocol,
-						testType:           serverTest,
-						name:               fmt.Sprintf("ALPS-EarlyData-Server-ClientNoOffe-%s-%s", alpsCodePoint, suffix),
-						skipQUICALPNConfig: true,
-						config: Config{
-							MaxVersion:          ver.version,
-							NextProtos:          []string{"proto"},
-							ALPSUseNewCodepoint: alpsCodePoint,
-						},
-						resumeSession: true,
-						earlyData:     true,
-						flags: append([]string{
-							"-select-alpn", "proto",
-							"-application-settings", "proto,shim",
-						}, alpsFlags...),
-					})
 				}
 			} else {
 				// Test the client rejects the ALPS extension if the server
@@ -8779,6 +8783,10 @@
 				if ech && ver.version < VersionTLS13 {
 					continue
 				}
+				// TODO(crbug.com/42290594): This test is broken when run with DTLS 1.3 and ECH.
+				if protocol == dtls && ver.version >= VersionTLS13 && ech {
+					continue
+				}
 
 				test := testCase{
 					protocol:           protocol,
@@ -9090,6 +9098,13 @@
 				suffix := "-" + sessionVers.name + "-" + resumeVers.name
 				suffix += "-" + protocol.String()
 
+				// TODO(crbug.com/42290594): Attempting to resume a DTLS 1.3 session
+				// when the new connection is an older version is currently broken.
+				// The current implementation recomputes the PSK binder value for the
+				// second ClientHello (sent in response to HelloVerifyRequest), but
+				// the runner expects all extension values to stay byte-for-byte the
+				// same (a possible interpretation of RFC 6347 section 4.2.1).
+				isBadDTLSResumption := protocol == dtls && sessionVers.version == VersionTLS13 && resumeVers.version < VersionTLS13
 				if sessionVers.version == resumeVers.version {
 					testCases = append(testCases, testCase{
 						protocol:      protocol,
@@ -9108,7 +9123,7 @@
 							version: resumeVers.version,
 						},
 					})
-				} else {
+				} else if !isBadDTLSResumption {
 					testCases = append(testCases, testCase{
 						protocol:      protocol,
 						name:          "Resume-Client-Mismatch" + suffix,
@@ -9133,25 +9148,27 @@
 					})
 				}
 
-				testCases = append(testCases, testCase{
-					protocol:      protocol,
-					name:          "Resume-Client-NoResume" + suffix,
-					resumeSession: true,
-					config: Config{
-						MaxVersion: sessionVers.version,
-					},
-					expectations: connectionExpectations{
-						version: sessionVers.version,
-					},
-					resumeConfig: &Config{
-						MaxVersion: resumeVers.version,
-					},
-					newSessionsOnResume:  true,
-					expectResumeRejected: true,
-					resumeExpectations: &connectionExpectations{
-						version: resumeVers.version,
-					},
-				})
+				if !isBadDTLSResumption {
+					testCases = append(testCases, testCase{
+						protocol:      protocol,
+						name:          "Resume-Client-NoResume" + suffix,
+						resumeSession: true,
+						config: Config{
+							MaxVersion: sessionVers.version,
+						},
+						expectations: connectionExpectations{
+							version: sessionVers.version,
+						},
+						resumeConfig: &Config{
+							MaxVersion: resumeVers.version,
+						},
+						newSessionsOnResume:  true,
+						expectResumeRejected: true,
+						resumeExpectations: &connectionExpectations{
+							version: resumeVers.version,
+						},
+					})
+				}
 
 				testCases = append(testCases, testCase{
 					protocol:      protocol,
@@ -20948,27 +20965,6 @@
 	}
 }
 
-// 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
@@ -21054,7 +21050,6 @@
 	testCases = append(testCases, toAppend...)
 
 	checkTests()
-	filterTests()
 
 	dispatcher, err := newShimDispatcher()
 	if err != nil {
diff --git a/ssl/tls13_both.cc b/ssl/tls13_both.cc
index 67c7b42..676b423 100644
--- a/ssl/tls13_both.cc
+++ b/ssl/tls13_both.cc
@@ -664,11 +664,15 @@
 }
 
 bool tls13_post_handshake(SSL *ssl, const SSLMessage &msg) {
-  if (SSL_is_dtls(ssl)) {
-    // TODO(crbug.com/42290594): Process post-handshake messages in DTLS 1.3.
-    return true;
+  if (msg.type == SSL3_MT_NEW_SESSION_TICKET && !ssl->server) {
+    return tls13_process_new_session_ticket(ssl, msg);
   }
+
   if (msg.type == SSL3_MT_KEY_UPDATE) {
+    if (SSL_is_dtls(ssl)) {
+      // TODO(crbug.com/42290594): Process post-handshake messages in DTLS 1.3.
+      return true;
+    }
     ssl->s3->key_update_count++;
     if (ssl->quic_method != nullptr ||
         ssl->s3->key_update_count > kMaxKeyUpdates) {
@@ -682,10 +686,6 @@
 
   ssl->s3->key_update_count = 0;
 
-  if (msg.type == SSL3_MT_NEW_SESSION_TICKET && !ssl->server) {
-    return tls13_process_new_session_ticket(ssl, msg);
-  }
-
   ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNEXPECTED_MESSAGE);
   OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_MESSAGE);
   return false;
diff --git a/ssl/tls13_enc.cc b/ssl/tls13_enc.cc
index 486908a..db59f71 100644
--- a/ssl/tls13_enc.cc
+++ b/ssl/tls13_enc.cc
@@ -558,11 +558,29 @@
   uint8_t context[EVP_MAX_MD_SIZE];
   unsigned context_len;
   ScopedEVP_MD_CTX ctx;
-  if (!transcript.CopyToHashContext(ctx.get(), digest) ||
-      !EVP_DigestUpdate(ctx.get(), truncated.data(),
-                        truncated.size()) ||
-      !EVP_DigestFinal_ex(ctx.get(), context, &context_len)) {
-    return false;
+  if (!is_dtls) {
+    if (!transcript.CopyToHashContext(ctx.get(), digest) ||
+        !EVP_DigestUpdate(ctx.get(), truncated.data(), truncated.size()) ||
+        !EVP_DigestFinal_ex(ctx.get(), context, &context_len)) {
+      return false;
+    }
+  } else {
+    // In DTLS 1.3, the transcript hash is computed over only the TLS 1.3
+    // handshake messages (i.e. only type and length in the header), not the
+    // full DTLSHandshake messages that are in |truncated|. This code pulls
+    // the header and body out of the truncated ClientHello and writes those
+    // to the hash context so the correct binder value is computed.
+    if (truncated.size() < DTLS1_HM_HEADER_LENGTH) {
+      return false;
+    }
+    auto header = truncated.subspan(0, 4);
+    auto body = truncated.subspan(12);
+    if (!transcript.CopyToHashContext(ctx.get(), digest) ||
+        !EVP_DigestUpdate(ctx.get(), header.data(), header.size()) ||
+        !EVP_DigestUpdate(ctx.get(), body.data(), body.size()) ||
+        !EVP_DigestFinal_ex(ctx.get(), context, &context_len)) {
+      return false;
+    }
   }
 
   if (!tls13_verify_data(out, out_len, digest, session->ssl_version, binder_key,
diff --git a/ssl/tls13_server.cc b/ssl/tls13_server.cc
index d3cb492..65232f0 100644
--- a/ssl/tls13_server.cc
+++ b/ssl/tls13_server.cc
@@ -1275,7 +1275,8 @@
   // the case of a small server write buffer. Consumers which don't write data
   // to the client will need to do a zero-byte write if they wish to flush the
   // tickets.
-  if (hs->ssl->quic_method != nullptr && sent_tickets) {
+  if ((hs->ssl->quic_method != nullptr || SSL_is_dtls(hs->ssl)) &&
+      sent_tickets) {
     return ssl_hs_flush;
   }
   return ssl_hs_ok;