Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Certificate rejection via ConnectionCertificateValidationComplete leads to connection timeout. #4132

Closed
1 of 4 tasks
rzikm opened this issue Feb 13, 2024 · 3 comments · Fixed by #4145
Closed
1 of 4 tasks
Labels
Area: API Area: Core Related to the shared, core protocol logic Partner: .NET By or For the .NET team
Milestone

Comments

@rzikm
Copy link
Member

rzikm commented Feb 13, 2024

Describe the bug

I am offloading computation-heavy certificate validation to a non-MsQuic thread in System.Net.Quic, and a unit test where the cert is rejected keeps failing.

Wireshark capture of when the ConnectionCertificateValidationComplete function is invoked from inside the PEER_CERTIFICATE_RECEIVED handler:

image

Notably, the Connection Close frame is sent at the Handshake protection level. This works fine.

When the invocation is moved to be done later from another thread:

image

Note that this time, the Connection Close is sent at 1RTT level, which seems to get ignored by the server. The connection attempt times out and get's closed by the server (the last line). The capture is attached below, together with sslkeylogfile to decrypt it.

quic-cert-validation.zip

Affected OS

  • Windows
  • Linux
  • macOS
  • Other (specify below)

Additional OS information

Reproduced on Windows, didn't try other OSes

MsQuic version

main

Steps taken to reproduce bug

Use ConnectionCertificateValidationComplete to reject certificate from another thread.

Expected behavior

The server-side connection receives TLS alert which was sent by the client.

Actual outcome

The connection attempt times out

Additional details

No response

@nibanks nibanks added this to the Release 2.4 milestone Feb 13, 2024
@nibanks nibanks added Area: API Area: Core Related to the shared, core protocol logic labels Feb 13, 2024
@rzikm
Copy link
Member Author

rzikm commented Feb 16, 2024

I think I know what is going on.

As per RFC 9001

[...] the server's use of 1-RTT keys before the handshake is complete is limited to sending data. A server MUST NOT process incoming 1-RTT protected packets before the TLS handshake is complete.

This means the mistake is in the client sending the CONNECTION_CLOSE on 1-RTT level.

There is also this comment

if (SendFlags & (QUIC_CONN_SEND_FLAG_CONNECTION_CLOSE | QUIC_CONN_SEND_FLAG_PING)) {
//
// CLOSE or PING is ready to be sent. This is always sent with the
// current write key.
//
// TODO - This logic isn't correct. The peer might not be able to read
// this key, so the CLOSE frame should be sent at the current and
// previous encryption level if the handshake hasn't been confirmed.
//

I don't know how to go about sending the CONNECTION_CLOSE/PING frames on two protection levels, but in this specific scenario the code does not get into that block anyway because of

QUIC_ENCRYPT_LEVEL EncryptLevel = QuicKeyTypeToEncryptLevel(KeyType);
if (EncryptLevel == QUIC_ENCRYPT_LEVEL_1_RTT) {
//
// Always allowed to send with 1-RTT.
//
*PacketKeyType = QUIC_PACKET_KEY_1_RTT;
return TRUE;
}

In the working (synchronous) case, there was data (ACKs) to be sent on the Handshake level, so the CONNECTION_CLOSE piggybacked on top of that.

I had some success with fixing it in PacketBuilder

diff --git a/src/core/packet_builder.c b/src/core/packet_builder.c
index 84d1a2705..52704b836 100644
--- a/src/core/packet_builder.c
+++ b/src/core/packet_builder.c
@@ -479,8 +479,22 @@ QuicPacketBuilderGetPacketTypeAndKeyForControlFrames(
     CXPLAT_DBG_ASSERT(SendFlags != 0);
     QuicSendValidate(&Builder->Connection->Send);

+    QUIC_PACKET_KEY_TYPE MaxKeyType = Connection->Crypto.TlsState.WriteKey;
+
+    if (QuicConnIsClient(Connection) &&
+        !Connection->State.HandshakeConfirmed &&
+        MaxKeyType == QUIC_PACKET_KEY_1_RTT &&
+        (SendFlags & QUIC_CONN_SEND_FLAG_CONNECTION_CLOSE)) {
+        //
+        // Server is not allowed to process 1-RTT packets until the handshake is confirmed and since we are
+        // closing the connection, the handshake is unlikely to complete. Ensure the CONNECTION_CLOSE is sent
+        // in a packet which server can process.
+        //
+        MaxKeyType = QUIC_PACKET_KEY_HANDSHAKE;
+    }
+
     for (QUIC_PACKET_KEY_TYPE KeyType = 0;
-         KeyType <= Connection->Crypto.TlsState.WriteKey;
+         KeyType <= MaxKeyType;
          ++KeyType) {

         if (KeyType == QUIC_PACKET_KEY_0_RTT) {
@@ -538,7 +552,7 @@ QuicPacketBuilderGetPacketTypeAndKeyForControlFrames(
         if (Connection->Crypto.TlsState.WriteKey == QUIC_PACKET_KEY_0_RTT) {
             *PacketKeyType = QUIC_PACKET_KEY_INITIAL;
         } else {
-            *PacketKeyType = Connection->Crypto.TlsState.WriteKey;
+            *PacketKeyType = MaxKeyType;
         }
         return TRUE;
     }

@rzikm
Copy link
Member Author

rzikm commented Feb 19, 2024

@nibanks does the fix above sound reasonable? Should I open a PR?

@nibanks
Copy link
Member

nibanks commented Feb 19, 2024

Go ahead and create the PR. We can go from there. Let's see if all the tests still pass.

rzikm added a commit to rzikm/dotnet-runtime that referenced this issue Feb 26, 2024
rzikm added a commit to dotnet/runtime that referenced this issue Feb 28, 2024
* Allow switching execution profiles using env vars

* Quick and dirty version to enable benchmarking

* Don't call callbacks from MsQuic threads

* Remove unintentional changes

* Offload parsing to threadpool as well

* Customize TLS ALERT code

* Code review feedback

* Apply suggestions from code review

Co-authored-by: Marie Píchová <[email protected]>

* use ConfigureAwaitOptions.ForceYielding

* Version check to work around microsoft/msquic#4132

* Use configure await to yield to threadpool

* Fix functionality on older msquic versions

---------

Co-authored-by: Marie Píchová <[email protected]>
@nibanks nibanks added the Partner: .NET By or For the .NET team label Mar 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area: API Area: Core Related to the shared, core protocol logic Partner: .NET By or For the .NET team
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants