Skip to content

Commit 926682e

Browse files
authored
Add test for async client certificate validation. (#3381)
1 parent 2e2b9c1 commit 926682e

File tree

9 files changed

+174
-19
lines changed

9 files changed

+174
-19
lines changed

docs/api/ConnectionCertificateValidationComplete.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
ConnectionCertificateValidationComplete function
22
======
33

4-
Uses the QUIC (client) handle to complete resumption ticket validation. This must be called after client app handles certificate validation and then return QUIC_STATUS_PENDING.
4+
Uses the QUIC handle to complete certificate validation. This must be called after the app receives `QUIC_CONNECTION_EVENT_PEER_CERTIFICATE_RECEIVED` and returns QUIC_STATUS_PENDING. The app should complete certificate validation and call this before the idle timeout and disconnect timeouts occur.
55

66
# Syntax
77

@@ -23,7 +23,7 @@ The valid handle to an open connection object.
2323
2424
`Result`
2525
26-
Ticket validation result.
26+
Certificate validation result.
2727
2828
# Return Value
2929

src/core/api.c

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1756,11 +1756,6 @@ MsQuicConnectionCertificateValidationComplete(
17561756

17571757
QUIC_CONN_VERIFY(Connection, !Connection->State.Freed);
17581758

1759-
if (QuicConnIsServer(Connection)) {
1760-
Status = QUIC_STATUS_INVALID_PARAMETER;
1761-
goto Error;
1762-
}
1763-
17641759
Oper = QuicOperationAlloc(Connection->Worker, QUIC_OPER_TYPE_API_CALL);
17651760
if (Oper == NULL) {
17661761
Status = QUIC_STATUS_OUT_OF_MEMORY;

src/core/connection.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3158,6 +3158,9 @@ QuicConnPeerCertReceived(
31583158
return FALSE;
31593159
}
31603160
if (Status == QUIC_STATUS_PENDING) {
3161+
//
3162+
// Don't set pending here because validation may have completed in the callback.
3163+
//
31613164
QuicTraceLogConnInfo(
31623165
CustomCertValidationPending,
31633166
Connection,
@@ -7314,7 +7317,6 @@ QuicConnProcessApiOperation(
73147317
break;
73157318

73167319
case QUIC_API_TYPE_CONN_COMPLETE_CERTIFICATE_VALIDATION:
7317-
CXPLAT_DBG_ASSERT(QuicConnIsClient(Connection));
73187320
QuicCryptoCustomCertValidationComplete(
73197321
&Connection->Crypto,
73207322
ApiCtx->CONN_COMPLETE_CERTIFICATE_VALIDATION.Result);

src/core/crypto.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1368,6 +1368,7 @@ QuicCryptoProcessTlsCompletion(
13681368
_In_ QUIC_CRYPTO* Crypto
13691369
)
13701370
{
1371+
CXPLAT_DBG_ASSERT(!Crypto->TicketValidationPending && !Crypto->CertValidationPending);
13711372
QUIC_CONNECTION* Connection = QuicCryptoGetConnection(Crypto);
13721373

13731374
if (Crypto->ResultFlags & CXPLAT_TLS_RESULT_ERROR) {
@@ -1556,6 +1557,7 @@ QuicCryptoProcessTlsCompletion(
15561557
if (Crypto->ResultFlags & CXPLAT_TLS_RESULT_HANDSHAKE_COMPLETE) {
15571558
CXPLAT_DBG_ASSERT(!(Crypto->ResultFlags & CXPLAT_TLS_RESULT_ERROR));
15581559
CXPLAT_TEL_ASSERT(!Connection->State.Connected);
1560+
CXPLAT_DBG_ASSERT(!Crypto->TicketValidationPending && !Crypto->CertValidationPending);
15591561

15601562
QuicTraceEvent(
15611563
ConnHandshakeComplete,

src/test/MsQuicTests.h

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,13 @@ QuicTestFailedVersionNegotiation(
189189
#endif // QUIC_API_ENABLE_PREVIEW_FEATURES
190190

191191
void
192-
QuicTestCustomCertificateValidation(
192+
QuicTestCustomServerCertificateValidation(
193+
_In_ bool AcceptCert,
194+
_In_ bool AsyncValidation
195+
);
196+
197+
void
198+
QuicTestCustomClientCertificateValidation(
193199
_In_ bool AcceptCert,
194200
_In_ bool AsyncValidation
195201
);
@@ -908,7 +914,7 @@ typedef struct {
908914
BOOLEAN AsyncValidation;
909915
} QUIC_RUN_CUSTOM_CERT_VALIDATION;
910916

911-
#define IOCTL_QUIC_RUN_CUSTOM_CERT_VALIDATION \
917+
#define IOCTL_QUIC_RUN_CUSTOM_SERVER_CERT_VALIDATION \
912918
QUIC_CTL_CODE(47, METHOD_BUFFERED, FILE_WRITE_DATA)
913919
// QUIC_RUN_CUSTOM_CERT_VALIDATION
914920

@@ -1166,4 +1172,8 @@ typedef struct {
11661172
QUIC_CTL_CODE(109, METHOD_BUFFERED, FILE_WRITE_DATA)
11671173
// int - Family
11681174

1169-
#define QUIC_MAX_IOCTL_FUNC_CODE 109
1175+
#define IOCTL_QUIC_RUN_CUSTOM_CLIENT_CERT_VALIDATION \
1176+
QUIC_CTL_CODE(110, METHOD_BUFFERED, FILE_WRITE_DATA)
1177+
// QUIC_RUN_CUSTOM_CERT_VALIDATION
1178+
1179+
#define QUIC_MAX_IOCTL_FUNC_CODE 110

src/test/bin/quic_gtest.cpp

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -966,16 +966,29 @@ TEST_P(WithFamilyArgs, FailedVersionNegotiation) {
966966
}
967967
#endif // QUIC_API_ENABLE_PREVIEW_FEATURES
968968

969-
TEST_P(WithHandshakeArgs5, CustomCertificateValidation) {
970-
TestLoggerT<ParamType> Logger("QuicTestCustomCertificateValidation", GetParam());
969+
TEST_P(WithHandshakeArgs5, CustomServerCertificateValidation) {
970+
TestLoggerT<ParamType> Logger("QuicTestCustomServerCertificateValidation", GetParam());
971971
if (TestingKernelMode) {
972972
QUIC_RUN_CUSTOM_CERT_VALIDATION Params = {
973973
GetParam().AcceptCert,
974974
GetParam().AsyncValidation
975975
};
976-
ASSERT_TRUE(DriverClient.Run(IOCTL_QUIC_RUN_CUSTOM_CERT_VALIDATION, Params));
976+
ASSERT_TRUE(DriverClient.Run(IOCTL_QUIC_RUN_CUSTOM_SERVER_CERT_VALIDATION, Params));
977977
} else {
978-
QuicTestCustomCertificateValidation(GetParam().AcceptCert, GetParam().AsyncValidation);
978+
QuicTestCustomServerCertificateValidation(GetParam().AcceptCert, GetParam().AsyncValidation);
979+
}
980+
}
981+
982+
TEST_P(WithHandshakeArgs5, CustomClientCertificateValidation) {
983+
TestLoggerT<ParamType> Logger("QuicTestCustomClientCertificateValidation", GetParam());
984+
if (TestingKernelMode) {
985+
QUIC_RUN_CUSTOM_CERT_VALIDATION Params = {
986+
GetParam().AcceptCert,
987+
GetParam().AsyncValidation
988+
};
989+
ASSERT_TRUE(DriverClient.Run(IOCTL_QUIC_RUN_CUSTOM_CLIENT_CERT_VALIDATION, Params));
990+
} else {
991+
QuicTestCustomClientCertificateValidation(GetParam().AcceptCert, GetParam().AsyncValidation);
979992
}
980993
}
981994

src/test/bin/winkernel/control.cpp

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,7 @@ size_t QUIC_IOCTL_BUFFER_SIZES[] =
481481
sizeof(BOOLEAN),
482482
sizeof(INT32),
483483
sizeof(INT32),
484+
sizeof(QUIC_RUN_CUSTOM_CERT_VALIDATION),
484485
};
485486

486487
CXPLAT_STATIC_ASSERT(
@@ -920,10 +921,10 @@ QuicTestCtlEvtIoDeviceControl(
920921
QuicTestAckSendDelay(Params->Family));
921922
break;
922923

923-
case IOCTL_QUIC_RUN_CUSTOM_CERT_VALIDATION:
924+
case IOCTL_QUIC_RUN_CUSTOM_SERVER_CERT_VALIDATION:
924925
CXPLAT_FRE_ASSERT(Params != nullptr);
925926
QuicTestCtlRun(
926-
QuicTestCustomCertificateValidation(
927+
QuicTestCustomServerCertificateValidation(
927928
Params->CustomCertValidationParams.AcceptCert,
928929
Params->CustomCertValidationParams.AsyncValidation));
929930
break;
@@ -1330,6 +1331,14 @@ QuicTestCtlEvtIoDeviceControl(
13301331
QuicTestCtlRun(QuicTestHandshakeSpecificLossPatterns(Params->Family));
13311332
break;
13321333

1334+
case IOCTL_QUIC_RUN_CUSTOM_CLIENT_CERT_VALIDATION:
1335+
CXPLAT_FRE_ASSERT(Params != nullptr);
1336+
QuicTestCtlRun(
1337+
QuicTestCustomClientCertificateValidation(
1338+
Params->CustomCertValidationParams.AcceptCert,
1339+
Params->CustomCertValidationParams.AsyncValidation));
1340+
break;
1341+
13331342
default:
13341343
Status = STATUS_NOT_IMPLEMENTED;
13351344
break;

src/test/lib/HandshakeTest.cpp

Lines changed: 122 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,12 @@ ListenerAcceptConnection(
111111
)
112112
{
113113
ServerAcceptContext* AcceptContext = (ServerAcceptContext*)Listener->Context;
114-
*AcceptContext->NewConnection = new(std::nothrow) TestConnection(ConnectionHandle);
114+
*AcceptContext->NewConnection = new(std::nothrow) TestConnection(ConnectionHandle, (NEW_STREAM_CALLBACK_HANDLER)AcceptContext->NewStreamHandler);
115115
(*AcceptContext->NewConnection)->SetExpectedCustomTicketValidationResult(AcceptContext->ExpectedCustomTicketValidationResult);
116+
(*AcceptContext->NewConnection)->SetAsyncCustomValidationResult(AcceptContext->AsyncCustomCertValidation);
117+
if (AcceptContext->IsCustomCertValidationResultSet) {
118+
(*AcceptContext->NewConnection)->SetExpectedCustomValidationResult(AcceptContext->CustomCertValidationResult);
119+
}
116120
if (*AcceptContext->NewConnection == nullptr || !(*AcceptContext->NewConnection)->IsValid()) {
117121
TEST_FAILURE("Failed to accept new TestConnection.");
118122
delete *AcceptContext->NewConnection;
@@ -744,7 +748,7 @@ QuicTestConnectAndIdle(
744748
}
745749

746750
void
747-
QuicTestCustomCertificateValidation(
751+
QuicTestCustomServerCertificateValidation(
748752
_In_ bool AcceptCert,
749753
_In_ bool AsyncValidation
750754
)
@@ -820,6 +824,122 @@ QuicTestCustomCertificateValidation(
820824
}
821825
}
822826

827+
void
828+
NoOpStreamShutdownCallback(
829+
_In_ TestStream* Stream
830+
)
831+
{
832+
UNREFERENCED_PARAMETER(Stream);
833+
}
834+
835+
void
836+
NewStreamCallbackTestFail(
837+
_In_ TestConnection* Connection,
838+
_In_ HQUIC StreamHandle,
839+
_In_ QUIC_STREAM_OPEN_FLAGS Flags
840+
)
841+
{
842+
UNREFERENCED_PARAMETER(Connection);
843+
UNREFERENCED_PARAMETER(Flags);
844+
MsQuic->StreamClose(StreamHandle);
845+
TEST_FAILURE("Unexpected new Stream received");
846+
}
847+
848+
void
849+
QuicTestCustomClientCertificateValidation(
850+
_In_ bool AcceptCert,
851+
_In_ bool AsyncValidation
852+
)
853+
{
854+
MsQuicRegistration Registration;
855+
TEST_TRUE(Registration.IsValid());
856+
857+
MsQuicAlpn Alpn("MsQuicTest");
858+
859+
MsQuicSettings Settings;
860+
Settings.SetPeerBidiStreamCount(1);
861+
Settings.SetIdleTimeoutMs(3000);
862+
863+
MsQuicConfiguration ServerConfiguration(Registration, Alpn, Settings, ServerSelfSignedCredConfigClientAuth);
864+
TEST_TRUE(ServerConfiguration.IsValid());
865+
866+
MsQuicConfiguration ClientConfiguration(Registration, Alpn, Settings, ClientCertCredConfig);
867+
TEST_TRUE(ClientConfiguration.IsValid());
868+
869+
{
870+
TestListener Listener(Registration, ListenerAcceptConnection, ServerConfiguration);
871+
TEST_TRUE(Listener.IsValid());
872+
TEST_QUIC_SUCCEEDED(Listener.Start(Alpn));
873+
874+
QuicAddr ServerLocalAddr;
875+
TEST_QUIC_SUCCEEDED(Listener.GetLocalAddr(ServerLocalAddr));
876+
877+
{
878+
UniquePtr<TestConnection> Server;
879+
ServerAcceptContext ServerAcceptCtx((TestConnection**)&Server);
880+
if (!AcceptCert) {
881+
ServerAcceptCtx.ExpectedTransportCloseStatus = QUIC_STATUS_BAD_CERTIFICATE;
882+
ServerAcceptCtx.NewStreamHandler = (void*)NewStreamCallbackTestFail;
883+
}
884+
ServerAcceptCtx.AsyncCustomCertValidation = AsyncValidation;
885+
if (!AsyncValidation) {
886+
ServerAcceptCtx.IsCustomCertValidationResultSet = true;
887+
ServerAcceptCtx.CustomCertValidationResult = AcceptCert;
888+
}
889+
ServerAcceptCtx.AddExpectedClientCertValidationResult(QUIC_STATUS_CERT_UNTRUSTED_ROOT);
890+
Listener.Context = &ServerAcceptCtx;
891+
892+
{
893+
TestConnection Client(Registration);
894+
TEST_TRUE(Client.IsValid());
895+
896+
if (!AcceptCert) {
897+
Client.SetExpectedTransportCloseStatus(QUIC_STATUS_BAD_CERTIFICATE);
898+
}
899+
900+
UniquePtr<TestStream> ClientStream(
901+
TestStream::FromConnectionHandle(
902+
Client.GetConnection(),
903+
NoOpStreamShutdownCallback,
904+
QUIC_STREAM_OPEN_FLAG_NONE));
905+
906+
TEST_QUIC_SUCCEEDED(ClientStream->Start(QUIC_STREAM_START_FLAG_IMMEDIATE));
907+
908+
TEST_QUIC_SUCCEEDED(
909+
Client.Start(
910+
ClientConfiguration,
911+
QUIC_ADDRESS_FAMILY_UNSPEC,
912+
QUIC_TEST_LOOPBACK_FOR_AF(
913+
QuicAddrGetFamily(&ServerLocalAddr.SockAddr)),
914+
ServerLocalAddr.GetPort()));
915+
916+
if (!CxPlatEventWaitWithTimeout(ServerAcceptCtx.NewConnectionReady, TestWaitTimeout)) {
917+
TEST_FAILURE("Timed out waiting for server accept.");
918+
}
919+
920+
if (AsyncValidation) {
921+
CxPlatSleep(2000);
922+
TEST_QUIC_SUCCEEDED(Server->SetCustomValidationResult(AcceptCert));
923+
}
924+
925+
if (!Client.WaitForConnectionComplete()) {
926+
return;
927+
}
928+
929+
if (AcceptCert) { // Server will be deleted on reject case, so can't validate.
930+
TEST_NOT_EQUAL(nullptr, Server);
931+
if (!Server->WaitForConnectionComplete()) {
932+
return;
933+
}
934+
TEST_TRUE(Server->GetIsConnected());
935+
}
936+
// In all cases, the client "connects", but in the rejection case, it gets disconnected.
937+
TEST_TRUE(Client.GetIsConnected());
938+
}
939+
}
940+
}
941+
}
942+
823943
void
824944
QuicTestConnectUnreachable(
825945
_In_ int Family

src/test/lib/TestHelpers.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,17 @@ class TestConnection;
7979
struct ServerAcceptContext {
8080
CXPLAT_EVENT NewConnectionReady;
8181
TestConnection** NewConnection;
82+
void* NewStreamHandler{nullptr};
8283
QUIC_STATUS ExpectedTransportCloseStatus{QUIC_STATUS_SUCCESS};
8384
QUIC_STATUS ExpectedClientCertValidationResult[2]{};
8485
uint32_t ExpectedClientCertValidationResultCount{0};
8586
QUIC_STATUS PeerCertEventReturnStatus{false};
8687
QUIC_PRIVATE_TRANSPORT_PARAMETER* TestTP{nullptr};
8788
bool AsyncCustomTicketValidation{false};
8889
QUIC_STATUS ExpectedCustomTicketValidationResult{QUIC_STATUS_SUCCESS};
90+
bool AsyncCustomCertValidation{false};
91+
bool IsCustomCertValidationResultSet{false};
92+
bool CustomCertValidationResult{false};
8993
ServerAcceptContext(TestConnection** _NewConnection) :
9094
NewConnection(_NewConnection) {
9195
CxPlatEventInitialize(&NewConnectionReady, TRUE, FALSE);

0 commit comments

Comments
 (0)