Stage new DTLS 1.3 read epochs until the first record comes in

In DTLS 1.2, we install the new read epoch as soon as we change keys,
and immediately stop accepting data from the previous epoch. This is
fine because there is only one key change, and we don't need to receive
data from the previous epoch at that point. (Even if we used previous
flights to trigger retransmits---which we probably should but don't---I
believe all DTLS 1.2 key changes are such that the previous flight will
contain *some* message from the epoch we expect and we can key on that.)

In DTLS 1.3, things are different. The peer's KeyUpdates do not apply
until we send an ACK, but that ACK might be lost. So that means, at
least for KeyUpdate, we must stage the new epoch and only apply it
later.

During the handshake, this comes up in two cases:

- A server might send ServerHello..Finished but some of ServerHello (or
  the whole flight) is lost. The server is now expecting
  Certificate..Finished (epoch 2), but the client cannot transition to
  epoch 2 without the ServerHello. The client will then try to induce a
  retransmit by either sending an ACK or resending the ClientHello. The
  client must parse epoch 0 to be able to catch this. This is nice
  but not critical, because the server also has a retransmit timer.

- A client might send Certificate..Finished but it was all lost. The
  client is now done with the handshake (epoch 3), but the server
  cannot complete. The server will then retransmit its
  ServerHello..Finished flight or send an ACK. The ACK will actually
  come at epoch 3, so we don't need epoch 2 in that case. But the
  retransmit is purely in epochs 0 and 2, so the client must parse
  epoch 2 to catch this. This is also not critical because the
  client is expected to keep its retransmit timer running after the
  handshake (still TODO).

To support all this, new DTLS 1.3 read epochs are staged until we get a
record.

This means that record processing must now account for receiving records
at more epochs, including some invalid cases. Specifically:

- We might receive new handshake messages at the current epoch, even
  though the current epoch is being closed. This is an error.

- We might receive application data at epoch 2 even though we've
  finished the handshake and expect it at epoch 3. This is an error.

I've added checks for both of these, with tests. This also resolves a
few TODOs. The app data check will also help with 0-RTT, where epochs 1
and 2 will flow concurrently. (We'll still need an inverse check that
epoch 1 never carries handshake data.) It also once again changes the
broken SSL_get_read_sequence API from one kind of broken to another kind
of broken, so I've updated the comment to explain the current state.

As part of this, stop tracking read_level and write_level outside of
QUIC. It's only used in QUIC and the semantics are a little weird when
we defer activating new read epochs.

(I'm not sure whether this behavior is really necessary for epochs
before epoch 3. The only case it accomodates is the client using epoch 0
to induce a retransmit when ServerHello is lost. The impact it has on
SSL_get_read_sequence immediately after the handshake is kind of weird,
so maybe we want to special case it a bit. I can't think of any case
where we'd want to defer installing epoch 3.)

Bug: 42290594
Change-Id: Id222de2b77ff8e68e64f301f32d8c2b95ad3e7a7
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/72450
Reviewed-by: Nick Harper <nharper@chromium.org>
Commit-Queue: David Benjamin <davidben@google.com>
15 files changed
tree: 1844d900086dbd2c99244d559ebfe38c00da44ca
  1. .bcr/
  2. .github/
  3. cmake/
  4. crypto/
  5. decrepit/
  6. docs/
  7. fuzz/
  8. gen/
  9. include/
  10. infra/
  11. pki/
  12. rust/
  13. ssl/
  14. third_party/
  15. tool/
  16. util/
  17. .bazelignore
  18. .bazelrc
  19. .clang-format
  20. .gitignore
  21. API-CONVENTIONS.md
  22. BREAKING-CHANGES.md
  23. BUILD.bazel
  24. build.json
  25. BUILDING.md
  26. CMakeLists.txt
  27. codereview.settings
  28. CONTRIBUTING.md
  29. FUZZING.md
  30. go.mod
  31. go.sum
  32. INCORPORATING.md
  33. LICENSE
  34. MODULE.bazel
  35. MODULE.bazel.lock
  36. PORTING.md
  37. PrivacyInfo.xcprivacy
  38. README.md
  39. SANDBOXING.md
  40. STYLE.md
README.md

BoringSSL

BoringSSL is a fork of OpenSSL that is designed to meet Google's needs.

Although BoringSSL is an open source project, it is not intended for general use, as OpenSSL is. We don't recommend that third parties depend upon it. Doing so is likely to be frustrating because there are no guarantees of API or ABI stability.

Programs ship their own copies of BoringSSL when they use it and we update everything as needed when deciding to make API changes. This allows us to mostly avoid compromises in the name of compatibility. It works for us, but it may not work for you.

BoringSSL arose because Google used OpenSSL for many years in various ways and, over time, built up a large number of patches that were maintained while tracking upstream OpenSSL. As Google's product portfolio became more complex, more copies of OpenSSL sprung up and the effort involved in maintaining all these patches in multiple places was growing steadily.

Currently BoringSSL is the SSL library in Chrome/Chromium, Android (but it's not part of the NDK) and a number of other apps/programs.

Project links:

To file a security issue, use the Chromium process and mention in the report this is for BoringSSL. You can ignore the parts of the process that are specific to Chromium/Chrome.

There are other files in this directory which might be helpful: