Skip to content

Commit

Permalink
Support ClientCertificateContext on QuicConnection (dotnet#97821)
Browse files Browse the repository at this point in the history
* Implement ClientCertificateContext on Linux

* Make order of cert selection consistent with SslStream
  • Loading branch information
rzikm authored Feb 1, 2024
1 parent 350954c commit e719462
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@ public static MsQuicSafeHandle Create(QuicClientConnectionOptions options)

// Find the first certificate with private key, either from selection callback or from a provided collection.
X509Certificate? certificate = null;
if (authenticationOptions.LocalCertificateSelectionCallback != null)
ReadOnlyCollection<X509Certificate2>? intermediates = null;
if (authenticationOptions.ClientCertificateContext is not null)
{
certificate = authenticationOptions.ClientCertificateContext.TargetCertificate;
intermediates = authenticationOptions.ClientCertificateContext.IntermediateCertificates;
}
else if (authenticationOptions.LocalCertificateSelectionCallback != null)
{
X509Certificate selectedCertificate = authenticationOptions.LocalCertificateSelectionCallback(
options,
Expand Down Expand Up @@ -69,7 +75,7 @@ public static MsQuicSafeHandle Create(QuicClientConnectionOptions options)
}
}

return Create(options, flags, certificate, null, authenticationOptions.ApplicationProtocols, authenticationOptions.CipherSuitesPolicy, authenticationOptions.EncryptionPolicy);
return Create(options, flags, certificate, intermediates, authenticationOptions.ApplicationProtocols, authenticationOptions.CipherSuitesPolicy, authenticationOptions.EncryptionPolicy);
}

public static MsQuicSafeHandle Create(QuicServerConnectionOptions options, string? targetHost)
Expand All @@ -86,13 +92,22 @@ public static MsQuicSafeHandle Create(QuicServerConnectionOptions options, strin

X509Certificate? certificate = null;
ReadOnlyCollection<X509Certificate2>? intermediates = default;
if (authenticationOptions.ServerCertificateContext is not null)

// the order of checking here matches the order of checking in SslStream
if (authenticationOptions.ServerCertificateSelectionCallback is not null)
{
certificate = authenticationOptions.ServerCertificateSelectionCallback.Invoke(authenticationOptions, targetHost);
}
else if (authenticationOptions.ServerCertificateContext is not null)
{
certificate = authenticationOptions.ServerCertificateContext.TargetCertificate;
intermediates = authenticationOptions.ServerCertificateContext.IntermediateCertificates;
}
else if (authenticationOptions.ServerCertificate is not null)
{
certificate = authenticationOptions.ServerCertificate;
}

certificate ??= authenticationOptions.ServerCertificate ?? authenticationOptions.ServerCertificateSelectionCallback?.Invoke(authenticationOptions, targetHost);
if (certificate is null)
{
throw new ArgumentException(SR.Format(SR.net_quic_not_null_ceritifcate, nameof(SslServerAuthenticationOptions.ServerCertificate), nameof(SslServerAuthenticationOptions.ServerCertificateContext), nameof(SslServerAuthenticationOptions.ServerCertificateSelectionCallback)), nameof(options));
Expand Down
46 changes: 32 additions & 14 deletions src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -646,12 +646,20 @@ public async Task ConnectWithCertificateForLoopbackIP_IndicatesExpectedError(str
}
}

public enum ClientCertSource
{
ClientCertificate,
SelectionCallback,
CertificateContext
}

[ConditionalTheory]
[InlineData(true, true)]
[InlineData(false, true)]
[InlineData(true, false)]
[InlineData(false, false)]
public async Task ConnectWithClientCertificate(bool sendCertificate, bool useClientSelectionCallback)
[InlineData(true, ClientCertSource.ClientCertificate)]
[InlineData(false, ClientCertSource.ClientCertificate)]
[InlineData(true, ClientCertSource.SelectionCallback)]
[InlineData(false, ClientCertSource.SelectionCallback)]
[InlineData(true, ClientCertSource.CertificateContext)]
public async Task ConnectWithClientCertificate(bool sendCertificate, ClientCertSource clientCertSource)
{
if (PlatformDetection.IsWindows10Version20348OrLower)
{
Expand Down Expand Up @@ -686,16 +694,26 @@ public async Task ConnectWithClientCertificate(bool sendCertificate, bool useCli

await using QuicListener listener = await CreateQuicListener(listenerOptions);
QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(listener.LocalEndPoint);
if (useClientSelectionCallback)
switch (clientCertSource)
{
clientOptions.ClientAuthenticationOptions.LocalCertificateSelectionCallback = delegate
{
return sendCertificate ? ClientCertificate : null;
};
}
else if (sendCertificate)
{
clientOptions.ClientAuthenticationOptions.ClientCertificates = new X509CertificateCollection() { ClientCertificate };
case ClientCertSource.ClientCertificate:
clientOptions.ClientAuthenticationOptions.ClientCertificates = new X509CertificateCollection();
if (sendCertificate)
{
clientOptions.ClientAuthenticationOptions.ClientCertificates.Add(ClientCertificate);
}
break;

case ClientCertSource.SelectionCallback:
clientOptions.ClientAuthenticationOptions.LocalCertificateSelectionCallback = delegate
{
return sendCertificate ? ClientCertificate : null;
};
break;

case ClientCertSource.CertificateContext:
clientOptions.ClientAuthenticationOptions.ClientCertificateContext = SslStreamCertificateContext.Create(ClientCertificate, null);
break;
}
(QuicConnection clientConnection, QuicConnection serverConnection) = await CreateConnectedQuicConnection(clientOptions, listener);

Expand Down

0 comments on commit e719462

Please sign in to comment.