Support sending KeyUpdate in DTLS 1.3

Bug: 42290594
Change-Id: I38312da6425d25038f59dc5c65a17987d3688048
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/73587
Reviewed-by: Nick Harper <nharper@chromium.org>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/ssl/d1_lib.cc b/ssl/d1_lib.cc
index 5cca499..d0a38b3 100644
--- a/ssl/d1_lib.cc
+++ b/ssl/d1_lib.cc
@@ -77,7 +77,8 @@
       handshake_write_overflow(false),
       handshake_read_overflow(false),
       sending_flight(false),
-      sending_ack(false) {}
+      sending_ack(false),
+      queued_key_update(QueuedKeyUpdate::kNone) {}
 
 DTLS1_STATE::~DTLS1_STATE() {}
 
diff --git a/ssl/d1_pkt.cc b/ssl/d1_pkt.cc
index b4d4c67..88e76b3 100644
--- a/ssl/d1_pkt.cc
+++ b/ssl/d1_pkt.cc
@@ -222,6 +222,26 @@
                   [](const auto &msg) { return msg.IsFullyAcked(); })) {
     dtls1_stop_timer(ssl);
     dtls_clear_outgoing_messages(ssl);
+
+    // DTLS 1.3 defers the key update to when the message is ACKed.
+    if (ssl->s3->key_update_pending) {
+      if (!tls13_rotate_traffic_key(ssl, evp_aead_seal)) {
+        return ssl_open_record_error;
+      }
+      ssl->s3->key_update_pending = false;
+    }
+
+    // Check for deferred messages.
+    if (ssl->d1->queued_key_update != QueuedKeyUpdate::kNone) {
+      int request_type =
+          ssl->d1->queued_key_update == QueuedKeyUpdate::kUpdateRequested
+              ? SSL_KEY_UPDATE_REQUESTED
+              : SSL_KEY_UPDATE_NOT_REQUESTED;
+      ssl->d1->queued_key_update = QueuedKeyUpdate::kNone;
+      if (!tls13_add_key_update(ssl, request_type)) {
+        return ssl_open_record_error;
+      }
+    }
   } else {
     // We may still be able to drop unused write epochs.
     dtls_clear_unused_write_epochs(ssl);
diff --git a/ssl/internal.h b/ssl/internal.h
index 463852b..42bf3f3 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -2604,10 +2604,9 @@
 const char *tls13_client_handshake_state(SSL_HANDSHAKE *hs);
 const char *tls13_server_handshake_state(SSL_HANDSHAKE *hs);
 
-// tls13_add_key_update queues a KeyUpdate message on |ssl|. The
-// |update_requested| argument must be one of |SSL_KEY_UPDATE_REQUESTED| or
-// |SSL_KEY_UPDATE_NOT_REQUESTED|.
-bool tls13_add_key_update(SSL *ssl, int update_requested);
+// tls13_add_key_update queues a KeyUpdate message on |ssl|. |request_type| must
+// be one of |SSL_KEY_UPDATE_REQUESTED| or |SSL_KEY_UPDATE_NOT_REQUESTED|.
+bool tls13_add_key_update(SSL *ssl, int request_type);
 
 // tls13_post_handshake processes a post-handshake message. It returns true on
 // success and false on failure.
@@ -3238,8 +3237,10 @@
   // Channel ID and the |channel_id| field is filled in.
   bool channel_id_valid : 1;
 
-  // key_update_pending is true if we have a KeyUpdate acknowledgment
-  // outstanding.
+  // key_update_pending is true if we are in the process of sending a KeyUpdate
+  // message. As a DoS mitigation (and a requirement in DTLS), we never send
+  // more than one KeyUpdate at once. In DTLS, this tracks whether there is an
+  // unACKed KeyUpdate.
   bool key_update_pending : 1;
 
   // early_data_accepted is true if early data was accepted by the server.
@@ -3533,6 +3534,12 @@
   uint32_t last_msg_end = 0;
 };
 
+enum class QueuedKeyUpdate {
+  kNone,
+  kUpdateNotRequested,
+  kUpdateRequested,
+};
+
 struct DTLS1_STATE {
   static constexpr bool kAllowUniquePtr = true;
 
@@ -3565,6 +3572,10 @@
   bool sending_flight : 1;
   bool sending_ack : 1;
 
+  // queued_key_update, if not kNone, indicates we've queued a KeyUpdate message
+  // to send after the current flight is ACKed.
+  QueuedKeyUpdate queued_key_update : 2;
+
   uint16_t handshake_write_seq = 0;
   uint16_t handshake_read_seq = 0;
 
diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc
index 379e628..d601695 100644
--- a/ssl/ssl_lib.cc
+++ b/ssl/ssl_lib.cc
@@ -1121,12 +1121,7 @@
     return 0;
   }
 
-  if (!ssl->s3->key_update_pending &&
-      !tls13_add_key_update(ssl, request_type)) {
-    return 0;
-  }
-
-  return 1;
+  return tls13_add_key_update(ssl, request_type);
 }
 
 int SSL_shutdown(SSL *ssl) {
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index fda853c..44a7cfc 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -1196,6 +1196,12 @@
     }
     if (!config->shim_shuts_down) {
       for (;;) {
+        if (config->key_update_before_read &&
+            !SSL_key_update(ssl, SSL_KEY_UPDATE_NOT_REQUESTED)) {
+          fprintf(stderr, "SSL_key_update failed.\n");
+          return false;
+        }
+
         // Read only 512 bytes at a time in TLS to ensure records may be
         // returned in multiple reads.
         size_t read_size = config->is_dtls ? 16384 : 512;
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 6e76f23..76195f6 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -2007,9 +2007,7 @@
 	// OmitPublicName omits the server name extension from ClientHelloOuter.
 	OmitPublicName bool
 
-	// AllowEpochOverflow allows the DTLS write epoch to wrap around. The DTLS
-	// read epoch is never allowed to wrap around because that would be a bug in
-	// the shim.
+	// AllowEpochOverflow allows DTLS epoch numbers to wrap around.
 	AllowEpochOverflow bool
 }
 
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go
index 071e262..d262507 100644
--- a/ssl/test/runner/conn.go
+++ b/ssl/test/runner/conn.go
@@ -1660,6 +1660,20 @@
 	return c.ackHandshake()
 }
 
+func (c *Conn) processKeyUpdate(keyUpdate *keyUpdateMsg) error {
+	epoch := c.in.epoch.epoch + 1
+	if epoch == 0 && !c.config.Bugs.AllowEpochOverflow {
+		return errors.New("tls: too many KeyUpdates")
+	}
+	if err := c.useInTrafficSecret(epoch, c.in.wireVersion, c.cipherSuite, updateTrafficSecret(c.cipherSuite.hash(), c.wireVersion, c.in.trafficSecret, c.isDTLS)); err != nil {
+		return err
+	}
+	if keyUpdate.keyUpdateRequest == keyUpdateRequested {
+		c.keyUpdateRequested = true
+	}
+	return c.ackHandshake()
+}
+
 func (c *Conn) handlePostHandshakeMessage() error {
 	msg, err := c.readHandshake()
 	if err != nil {
@@ -1690,21 +1704,10 @@
 
 	if keyUpdate, ok := msg.(*keyUpdateMsg); ok {
 		c.keyUpdateSeen = true
-
 		if c.config.Bugs.RejectUnsolicitedKeyUpdate {
 			return errors.New("tls: unexpected KeyUpdate message")
 		}
-		epoch := c.in.epoch.epoch + 1
-		if epoch == 0 {
-			return errors.New("tls: too many KeyUpdates")
-		}
-		if err := c.useInTrafficSecret(epoch, c.in.wireVersion, c.cipherSuite, updateTrafficSecret(c.cipherSuite.hash(), c.wireVersion, c.in.trafficSecret, c.isDTLS)); err != nil {
-			return err
-		}
-		if keyUpdate.keyUpdateRequest == keyUpdateRequested {
-			c.keyUpdateRequested = true
-		}
-		return c.ackHandshake()
+		return c.processKeyUpdate(keyUpdate)
 	}
 
 	c.sendAlert(alertUnexpectedMessage)
@@ -1717,27 +1720,16 @@
 	c.in.Lock()
 	defer c.in.Unlock()
 
-	msg, err := c.readHandshake()
+	keyUpdate, err := readHandshakeType[keyUpdateMsg](c)
 	if err != nil {
 		return err
 	}
 
-	keyUpdate, ok := msg.(*keyUpdateMsg)
-	if !ok {
-		c.sendAlert(alertUnexpectedMessage)
-		return fmt.Errorf("tls: unexpected message (%T) when reading KeyUpdate", msg)
-	}
-
 	if keyUpdate.keyUpdateRequest != keyUpdateNotRequested {
 		return errors.New("tls: received invalid KeyUpdate message")
 	}
 
-	epoch := c.in.epoch.epoch + 1
-	if epoch == 0 {
-		return errors.New("tls: too many KeyUpdates")
-	}
-
-	return c.useInTrafficSecret(epoch, c.in.wireVersion, c.cipherSuite, updateTrafficSecret(c.cipherSuite.hash(), c.wireVersion, c.in.trafficSecret, c.isDTLS))
+	return c.processKeyUpdate(keyUpdate)
 }
 
 func (c *Conn) Renegotiate() error {
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 25651f6..075f6fd 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -627,6 +627,9 @@
 	sendKeyUpdates int
 	// keyUpdateRequest is the KeyUpdateRequest value to send in KeyUpdate messages.
 	keyUpdateRequest byte
+	// shimSendsKeyUpdateBeforeRead indicates the shim should send a KeyUpdate
+	// message before each read.
+	shimSendsKeyUpdateBeforeRead bool
 	// expectUnsolicitedKeyUpdate makes the test expect a one or more KeyUpdate
 	// messages while reading data from the shim. Don't use this in combination
 	// with any of the fields that send a KeyUpdate otherwise any received
@@ -1149,6 +1152,12 @@
 			}
 		}
 
+		if test.shimSendsKeyUpdateBeforeRead {
+			if err := tlsConn.ReadKeyUpdate(); err != nil {
+				return err
+			}
+		}
+
 		testMessage := makeTestMessage(j, messageLen)
 		if _, err := tlsConn.Write(testMessage); err != nil {
 			return err
@@ -1214,6 +1223,14 @@
 		}
 	}
 
+	// The shim will end attempting to read, sending one last KeyUpdate. Consume
+	// the KeyUpdate before closing the connection.
+	if test.shimSendsKeyUpdateBeforeRead {
+		if err := tlsConn.ReadKeyUpdate(); err != nil {
+			return err
+		}
+	}
+
 	return nil
 }
 
@@ -1659,6 +1676,10 @@
 		flags = append(flags, "-tls-unique")
 	}
 
+	if test.shimSendsKeyUpdateBeforeRead {
+		flags = append(flags, "-key-update-before-read")
+	}
+
 	if *waitForDebugger {
 		flags = append(flags, "-wait-for-debugger")
 	}
@@ -22248,6 +22269,159 @@
 		flags:            []string{"-async"},
 	})
 
+	// In DTLS, we KeyUpdate before read, rather than write, because the
+	// KeyUpdate will not be applied before the shim reads the ACK.
+	testCases = append(testCases, testCase{
+		protocol: dtls,
+		name:     "KeyUpdate-FromClient-DTLS",
+		config: Config{
+			MaxVersion: VersionTLS13,
+		},
+		shimSendsKeyUpdateBeforeRead: true,
+		// Perform several message exchanges to update keys several times.
+		messageCount: 10,
+	})
+	testCases = append(testCases, testCase{
+		protocol: dtls,
+		testType: serverTest,
+		name:     "KeyUpdate-FromServer-DTLS",
+		config: Config{
+			MaxVersion: VersionTLS13,
+		},
+		shimSendsKeyUpdateBeforeRead: true,
+		// Perform several message exchanges to update keys several times.
+		messageCount: 10,
+		// Avoid NewSessionTicket messages getting in the way of ReadKeyUpdate.
+		flags: []string{"-no-ticket"},
+	})
+
+	// If the shim has a pending unACKed flight, it defers sending KeyUpdate.
+	// BoringSSL does not support multiple outgoing flights at once.
+	testCases = append(testCases, testCase{
+		protocol: dtls,
+		name:     "KeyUpdate-DeferredSend-DTLS",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			// Request a client certificate, so the shim has more to send.
+			ClientAuth: RequireAnyClientCert,
+			Bugs: ProtocolBugs{
+				MaxPacketLength: 512,
+				ACKFlightDTLS: func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
+					if received[len(received)-1].Type != typeFinished {
+						c.WriteACK(c.OutEpoch(), records)
+						return
+					}
+
+					// This test relies on the Finished flight being multiple
+					// records.
+					if len(records) <= 1 {
+						panic("shim sent Finished flight in one record")
+					}
+
+					// Before ACKing Finished, do some rounds of exchanging
+					// application data. Although the shim has already scheduled
+					// KeyUpdate, it should not send the KeyUpdate until it gets
+					// an ACK. (If it sent KeyUpdate, ReadAppData would report
+					// an unexpected record.)
+					msg := []byte("test")
+					for i := 0; i < 10; i++ {
+						c.WriteAppData(c.OutEpoch(), msg)
+						c.ReadAppData(c.InEpoch(), expectedReply(msg))
+					}
+
+					// ACK some of the Finished flight, but not all of it.
+					c.WriteACK(c.OutEpoch(), records[:1])
+
+					// The shim continues to defer KeyUpdate.
+					for i := 0; i < 10; i++ {
+						c.WriteAppData(c.OutEpoch(), msg)
+						c.ReadAppData(c.InEpoch(), expectedReply(msg))
+					}
+
+					// ACK the remainder.
+					c.WriteACK(c.OutEpoch(), records[1:])
+
+					// The shim should now send KeyUpdate. Return to the test
+					// harness, which will look for it.
+				},
+			},
+		},
+		shimCertificate:              &rsaChainCertificate,
+		shimSendsKeyUpdateBeforeRead: true,
+		flags:                        []string{"-mtu", "512"},
+	})
+
+	// The shim should not switch keys until it receives an ACK.
+	testCases = append(testCases, testCase{
+		protocol: dtls,
+		name:     "KeyUpdate-WaitForACK-DTLS",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				MaxPacketLength: 512,
+				ACKFlightDTLS: func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
+					if received[0].Type != typeKeyUpdate {
+						c.WriteACK(c.OutEpoch(), records)
+						return
+					}
+
+					// Make the shim send application data. We have not yet
+					// ACKed KeyUpdate, so the shim should send at the previous
+					// epoch. Through each of these rounds, the shim will also
+					// try to KeyUpdate again. These calls will be suppressed
+					// because there is still an outstanding KeyUpdate.
+					msg := []byte("test")
+					for i := 0; i < 10; i++ {
+						c.WriteAppData(c.OutEpoch(), msg)
+						c.ReadAppData(c.InEpoch()-1, expectedReply(msg))
+					}
+
+					// ACK the KeyUpdate. Ideally we'd test a partial ACK, but
+					// BoringSSL's minimum MTU is such that KeyUpdate always
+					// fits in one record.
+					c.WriteACK(c.OutEpoch(), records)
+
+					// The shim should now send at the new epoch. Return to the
+					// test harness, which will enforce this.
+				},
+			},
+		},
+		shimSendsKeyUpdateBeforeRead: true,
+	})
+
+	// Test that shim responds to KeyUpdate requests.
+	fixKeyUpdateReply := func(c *DTLSController, prev, received []DTLSMessage, records []DTLSRecordNumberInfo) {
+		c.WriteACK(c.OutEpoch(), records)
+		if received[0].Type != typeKeyUpdate {
+			return
+		}
+		// This works around an awkward testing mismatch. The test
+		// harness expects the shim to immediately change keys, but
+		// the shim writes app data before seeing the ACK. The app
+		// data will be sent at the previous epoch. Consume this and
+		// prime the shim to resend its reply at the new epoch.
+		msg := makeTestMessage(int(received[0].Sequence)-2, 32)
+		c.ReadAppData(c.InEpoch()-1, expectedReply(msg))
+		c.WriteAppData(c.OutEpoch(), msg)
+	}
+	testCases = append(testCases, testCase{
+		protocol: dtls,
+		name:     "KeyUpdate-Requested-DTLS",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				RejectUnsolicitedKeyUpdate: true,
+				ACKFlightDTLS:              fixKeyUpdateReply,
+			},
+		},
+		// Test the shim receiving many KeyUpdates in a row. They will be
+		// combined into one reply KeyUpdate.
+		sendKeyUpdates:   5,
+		messageLen:       32,
+		messageCount:     5,
+		keyUpdateRequest: keyUpdateRequested,
+	})
+
 	mergeNewSessionTicketAndKeyUpdate := func(f WriteFlightFunc) WriteFlightFunc {
 		return func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
 			// Send NewSessionTicket and the first KeyUpdate all together.
@@ -22358,12 +22532,15 @@
 
 	// Test KeyUpdate overflow conditions. Both the epoch number and the message
 	// number may overflow, in either the read or write direction.
-	//
-	// TODO(crbug.com/42290594): Test the epoch write number overflowing, once
-	// we implement sending KeyUpdates.
-	//
-	// TODO(crbug.com/42290594): Test the message write number overflowing, once
-	// we implement sending KeyUpdates.
+
+	// When the sender is the client, the first KeyUpdate is message 2 at epoch
+	// 3, so the epoch number overflows first.
+	const maxClientKeyUpdates = 0xffff - 3
+
+	// Test that the shim, as a server, rejects KeyUpdates at epoch 0xffff. RFC
+	// 9147 does not prescribe this limit, but we enforce it. See
+	// https://mailarchive.ietf.org/arch/msg/tls/6y8wTv8Q_IPM-PCcbCAmDOYg6bM/
+	// and https://www.rfc-editor.org/errata/eid8050
 	writeFlightKeyUpdate := func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
 		if next[0].Type == typeKeyUpdate {
 			// Exchange some data to avoid tripping KeyUpdate DoS limits.
@@ -22373,12 +22550,6 @@
 		}
 		c.WriteFlight(next)
 	}
-
-	// When the runner is the client, the first KeyUpdate is message 2 at epoch
-	// 3, so the epoch number overflows first. Test that the shim, as a server,
-	// rejects KeyUpdates at epoch 0xffff. RFC 9147 does not prescribe this
-	// limit, but we enforce it. See https://mailarchive.ietf.org/arch/msg/tls/6y8wTv8Q_IPM-PCcbCAmDOYg6bM/
-	// and https://www.rfc-editor.org/errata/eid8050
 	testCases = append(testCases, testCase{
 		testType: serverTest,
 		protocol: dtls,
@@ -22391,9 +22562,8 @@
 			},
 		},
 		// Avoid the NewSessionTicket messages interfering with the callback.
-		flags: []string{"-no-ticket"},
-		// Application data starts at epoch 3. Epoch 0xffff is the limit.
-		sendKeyUpdates:   0xffff - 3,
+		flags:            []string{"-no-ticket"},
+		sendKeyUpdates:   maxClientKeyUpdates,
 		keyUpdateRequest: keyUpdateNotRequested,
 	})
 	testCases = append(testCases, testCase{
@@ -22408,23 +22578,56 @@
 			},
 		},
 		// Avoid the NewSessionTicket messages interfering with the callback.
-		flags: []string{"-no-ticket"},
-		// Application data starts at epoch 3. Epoch 0xffff is the limit.
-		sendKeyUpdates:     0xffff - 3 + 1,
+		flags:              []string{"-no-ticket"},
+		sendKeyUpdates:     maxClientKeyUpdates + 1,
 		keyUpdateRequest:   keyUpdateNotRequested,
 		shouldFail:         true,
 		expectedError:      ":TOO_MANY_KEY_UPDATES:",
 		expectedLocalError: "remote error: unexpected message",
 	})
 
-	// When the runner is a server, the first KeyUpdate is message 7 (SH, EE, C,
-	// CV, Fin, NST, NST) at epoch 3, so the message number overflows first.
+	// Test that the shim, as a client, notices its epoch overflow condition
+	// when asked to send too many KeyUpdates. The shim sends KeyUpdate before
+	// every read, including reading connection close, so the number of
+	// KeyUpdates is one more than the message count.
+	testCases = append(testCases, testCase{
+		protocol: dtls,
+		name:     "KeyUpdate-MaxWriteEpoch-DTLS",
+		config: Config{
+			MaxVersion: VersionTLS13,
+		},
+		shimSendsKeyUpdateBeforeRead: true,
+		messageCount:                 maxClientKeyUpdates - 1,
+	})
+	testCases = append(testCases, testCase{
+		protocol: dtls,
+		name:     "KeyUpdate-WriteEpochOverflow-DTLS",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				// The shim does not notice the overflow until immediately after
+				// sending KeyUpdate, so tolerate the overflow on the runner.
+				AllowEpochOverflow: true,
+			},
+		},
+		shimSendsKeyUpdateBeforeRead: true,
+		messageCount:                 maxClientKeyUpdates,
+		shouldFail:                   true,
+		expectedError:                ":TOO_MANY_KEY_UPDATES:",
+	})
+
+	// When the sender is a server that doesn't send tickets, the first
+	// KeyUpdate is message 5 (SH, EE, C, CV, Fin) at epoch 3, so the message
+	// number overflows first.
+	const maxServerKeyUpdates = 0xffff - 5
+
 	// Test that the shim, as a client, does not allow the value to wraparound.
 	testCases = append(testCases, testCase{
 		protocol: dtls,
 		name:     "KeyUpdate-ReadMessageOverflow-DTLS",
 		config: Config{
-			MaxVersion: VersionTLS13,
+			MaxVersion:             VersionTLS13,
+			SessionTicketsDisabled: true,
 			Bugs: ProtocolBugs{
 				AllowEpochOverflow: true,
 				WriteFlightDTLS: func(c *DTLSController, prev, received, next []DTLSMessage, records []DTLSRecordNumberInfo) {
@@ -22450,9 +22653,38 @@
 				},
 			},
 		},
-		sendKeyUpdates:   0xffff - 7 + 1,
+		sendKeyUpdates:   maxServerKeyUpdates + 1,
 		keyUpdateRequest: keyUpdateNotRequested,
-		flags:            []string{"-async"},
+		flags:            []string{"-async", "-expect-no-session"},
+	})
+
+	// Test that the shim, as a server, notices its message overflow condition,
+	// when asked to send too many KeyUpdates.
+	testCases = append(testCases, testCase{
+		protocol: dtls,
+		testType: serverTest,
+		name:     "KeyUpdate-MaxWriteMessage-DTLS",
+		config: Config{
+			MaxVersion: VersionTLS13,
+		},
+		shimSendsKeyUpdateBeforeRead: true,
+		messageCount:                 maxServerKeyUpdates,
+		// Avoid NewSessionTicket messages getting in the way of ReadKeyUpdate.
+		flags: []string{"-no-ticket"},
+	})
+	testCases = append(testCases, testCase{
+		protocol: dtls,
+		testType: serverTest,
+		name:     "KeyUpdate-WriteMessageOverflow-DTLS",
+		config: Config{
+			MaxVersion: VersionTLS13,
+		},
+		shimSendsKeyUpdateBeforeRead: true,
+		messageCount:                 maxServerKeyUpdates + 1,
+		shouldFail:                   true,
+		expectedError:                ":overflow:",
+		// Avoid NewSessionTicket messages getting in the way of ReadKeyUpdate.
+		flags: []string{"-no-ticket"},
 	})
 }
 
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index 6583b74..9471379 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -471,6 +471,8 @@
         BoolFlag("-export-traffic-secrets",
                  &TestConfig::export_traffic_secrets),
         BoolFlag("-key-update", &TestConfig::key_update),
+        BoolFlag("-key-update-before-read",
+                 &TestConfig::key_update_before_read),
         StringFlag("-expect-early-data-reason",
                    &TestConfig::expect_early_data_reason),
         BoolFlag("-expect-hrr", &TestConfig::expect_hrr),
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index b4b290d..06817b6 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -208,6 +208,7 @@
   bool server_preference = false;
   bool export_traffic_secrets = false;
   bool key_update = false;
+  bool key_update_before_read = false;
   std::string expect_early_data_reason;
   bool expect_hrr = false;
   bool expect_no_hrr = false;
diff --git a/ssl/tls13_both.cc b/ssl/tls13_both.cc
index e0d5ad0..b26693b 100644
--- a/ssl/tls13_both.cc
+++ b/ssl/tls13_both.cc
@@ -618,13 +618,31 @@
   return true;
 }
 
-bool tls13_add_key_update(SSL *ssl, int update_requested) {
+bool tls13_add_key_update(SSL *ssl, int request_type) {
+  if (ssl->s3->key_update_pending) {
+    return true;
+  }
+
+  // We do not support multiple parallel outgoing flights. If there is an
+  // outgoing flight pending, queue the KeyUpdate for later.
+  if (SSL_is_dtls(ssl) && !ssl->d1->outgoing_messages.empty()) {
+    ssl->d1->queued_key_update = request_type == SSL_KEY_UPDATE_REQUESTED
+                                     ? QueuedKeyUpdate::kUpdateRequested
+                                     : QueuedKeyUpdate::kUpdateNotRequested;
+    return true;
+  }
+
   ScopedCBB cbb;
   CBB body_cbb;
   if (!ssl->method->init_message(ssl, cbb.get(), &body_cbb,
                                  SSL3_MT_KEY_UPDATE) ||
-      !CBB_add_u8(&body_cbb, update_requested) ||
-      !ssl_add_message_cbb(ssl, cbb.get()) ||
+      !CBB_add_u8(&body_cbb, request_type) ||
+      !ssl_add_message_cbb(ssl, cbb.get())) {
+    return false;
+  }
+
+  // In DTLS, the actual key update is deferred until KeyUpdate is ACKed.
+  if (!SSL_is_dtls(ssl) &&
       !tls13_rotate_traffic_key(ssl, evp_aead_seal)) {
     return false;
   }
@@ -654,10 +672,7 @@
   }
 
   // Acknowledge the KeyUpdate
-  // TODO(crbug.com/42290594): Support sending KeyUpdate in DTLS 1.3.
-  if (!SSL_is_dtls(ssl) &&  //
-      key_update_request == SSL_KEY_UPDATE_REQUESTED &&
-      !ssl->s3->key_update_pending &&
+  if (key_update_request == SSL_KEY_UPDATE_REQUESTED &&
       !tls13_add_key_update(ssl, SSL_KEY_UPDATE_NOT_REQUESTED)) {
     return false;
   }