Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ private static X509ExtensionCollection BuildTlsCertExtensions(string targetName,
return extensions;
}

public static (X509Certificate2 certificate, X509Certificate2Collection) GenerateCertificates(string targetName, [CallerMemberName] string? testName = null, bool longChain = false, bool serverCertificate = true)
public static (X509Certificate2 certificate, X509Certificate2Collection) GenerateCertificates(string targetName, [CallerMemberName] string? testName = null, bool longChain = false, bool serverCertificate = true, bool ephemeralKey = false)
{
const int keySize = 2048;
if (PlatformDetection.IsWindows && testName != null)
Expand Down Expand Up @@ -161,10 +161,10 @@ public static (X509Certificate2 certificate, X509Certificate2Collection) Generat
responder.Dispose();
root.Dispose();

if (PlatformDetection.IsWindows)
if (!ephemeralKey && PlatformDetection.IsWindows)
{
X509Certificate2 ephemeral = endEntity;
endEntity = new X509Certificate2(endEntity.Export(X509ContentType.Pfx));
endEntity = new X509Certificate2(endEntity.Export(X509ContentType.Pfx), (string?)null, X509KeyStorageFlags.Exportable);
ephemeral.Dispose();
}

Expand Down
3 changes: 3 additions & 0 deletions src/libraries/System.Net.Quic/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,9 @@
<data name="net_quic_tls_version_notsupported" xml:space="preserve">
<value>Could not use a TLS version required by Quic. TLS 1.3 may have been disabled in the registry.</value>
</data>
<data name="net_auth_ephemeral" xml:space="preserve">
<value>Authentication failed because the platform does not support ephemeral keys.</value>
</data>
<data name="net_quic_empty_cipher_suite" xml:space="preserve">
<value>CipherSuitePolicy must specify at least one cipher supported by QUIC.</value>
</data>
Expand Down
6 changes: 6 additions & 0 deletions src/libraries/System.Net.Quic/src/System.Net.Quic.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'windows'">
<Compile Include="$(CommonPath)Interop\Windows\Interop.Libraries.cs" Link="Common\Interop\Windows\Interop.Libraries.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CERT_CONTEXT.cs" Link="Common\Interop\Windows\Crypt32\Interop.CERT_CONTEXT.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CertContextPropId.cs" Link="Common\Interop\Windows\Crypt32\Interop.CertContextPropId.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CertDuplicateCertificateContext.cs" Link="Common\Interop\Windows\Crypt32\Interop.CertDuplicateCertificateContext.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CertGetCertificateContextProperty_NO_NULLABLE.cs" Link="Common\Interop\Windows\Crypt32\Interop.CertGetCertificateContextProperty_NO_NULLABLE.cs" />
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeCrypt32Handle.cs" Link="Common\Microsoft\Win32\SafeHandles\SafeCrypt32Handle.cs" />
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeHandleCache.cs" Link="Common\Microsoft\Win32\SafeHandles\SafeHandleCache.cs" />
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeCertContextHandle.cs" Link="Common\Microsoft\Win32\SafeHandles\SafeCertContextHandle.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CERT_INFO.cs" Link="Common\Interop\Windows\Crypt32\Interop.CERT_INFO.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CERT_PUBLIC_KEY_INFO.cs" Link="Common\Interop\Windows\Crypt32\Interop.CERT_PUBLIC_KEY_INFO.cs" />
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CRYPT_ALGORITHM_IDENTIFIER.cs" Link="Common\Interop\Windows\Crypt32\Interop.Interop.CRYPT_ALGORITHM_IDENTIFIER.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using Microsoft.Quic;
using Microsoft.Win32.SafeHandles;
using static Microsoft.Quic.MsQuic;

namespace System.Net.Quic;
Expand Down Expand Up @@ -206,6 +208,16 @@ private static unsafe MsQuicSafeHandle Create(QuicConnectionOptions options, QUI
{
ThrowHelper.ThrowIfMsQuicError(status, SR.net_quic_tls_version_notsupported);
}

if ((status == QUIC_STATUS_CERT_NO_CERT || (Interop.SECURITY_STATUS)status == Interop.SECURITY_STATUS.UnknownCredentials) &&
certificate is not null && certificate.HasPrivateKey())
{
using SafeCertContextHandle safeCertContextHandle = Interop.Crypt32.CertDuplicateCertificateContext(certificate.Handle);
if (safeCertContextHandle.HasEphemeralPrivateKey)
{
throw new AuthenticationException(SR.net_auth_ephemeral);
}
Comment thread
rzikm marked this conversation as resolved.
}
#endif

ThrowHelper.ThrowIfMsQuicError(status, "ConfigurationLoadCredential failed");
Expand Down
85 changes: 85 additions & 0 deletions src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1364,5 +1364,90 @@ private async Task SniTestCore(string hostname, bool shouldSendSni)
[InlineData("fe80::9c3a:b64d:6249:1de8%2")]
[InlineData("fe80::9c3a:b64d:6249:1de8")]
public Task DoesNotSendIPAsSni(string target) => SniTestCore(target, false);

[Fact]
[PlatformSpecific(TestPlatforms.Windows)]
public async Task Server_CertificateWithEphemeralKey_Throws()
{
(X509Certificate2 serverCertificate, X509Certificate2Collection chain) = Configuration.Certificates.GenerateCertificates(nameof(Server_CertificateWithEphemeralKey_Throws), ephemeralKey: true);
Configuration.Certificates.CleanupCertificates(nameof(Server_CertificateWithEphemeralKey_Throws));
Comment thread
akoeplinger marked this conversation as resolved.

try
{
QuicListenerOptions listenerOptions = new QuicListenerOptions()
{
ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0),
ApplicationProtocols = new List<SslApplicationProtocol>() { ApplicationProtocol },
ConnectionOptionsCallback = (_, _, _) =>
{
var serverOptions = CreateQuicServerOptions();
serverOptions.ServerAuthenticationOptions.ServerCertificate = null;
serverOptions.ServerAuthenticationOptions.ServerCertificateContext = SslStreamCertificateContext.Create(serverCertificate, chain);
return ValueTask.FromResult(serverOptions);
}
};
await using QuicListener listener = await CreateQuicListener(listenerOptions);

QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(listener.LocalEndPoint);
clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = delegate { return true; };

// client connection attempt will fail
await Assert.ThrowsAsync<AuthenticationException>(async () => await CreateQuicConnection(clientOptions));

// server-side failure will be reported from AcceptConnectionAsync
AuthenticationException e = await Assert.ThrowsAsync<AuthenticationException>(async () => await listener.AcceptConnectionAsync());
Assert.Contains("ephemeral", e.Message);
}
finally
{
Configuration.Certificates.CleanupCertificates(nameof(Server_CertificateWithEphemeralKey_Throws));
serverCertificate.Dispose();
foreach (X509Certificate c in chain)
{
c.Dispose();
}
}
}

[Fact]
[PlatformSpecific(TestPlatforms.Windows)]
public async Task Client_CertificateWithEphemeralKey_Throws()
{
(X509Certificate2 clientCertificate, X509Certificate2Collection chain) = Configuration.Certificates.GenerateCertificates(nameof(Client_CertificateWithEphemeralKey_Throws), ephemeralKey: true);
Configuration.Certificates.CleanupCertificates(nameof(Client_CertificateWithEphemeralKey_Throws));

try
{
QuicListenerOptions listenerOptions = new QuicListenerOptions()
{
ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0),
ApplicationProtocols = new List<SslApplicationProtocol>() { ApplicationProtocol },
ConnectionOptionsCallback = (_, _, _) =>
{
var serverOptions = CreateQuicServerOptions();
serverOptions.ServerAuthenticationOptions.ClientCertificateRequired = true;
serverOptions.ServerAuthenticationOptions.RemoteCertificateValidationCallback = delegate { return true; };
return ValueTask.FromResult(serverOptions);
}
};
await using QuicListener listener = await CreateQuicListener(listenerOptions);

QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(listener.LocalEndPoint);
clientOptions.ClientAuthenticationOptions.ClientCertificates = new X509CertificateCollection() { clientCertificate };
clientOptions.ClientAuthenticationOptions.RemoteCertificateValidationCallback = delegate { return true; };

AuthenticationException e = await Assert.ThrowsAsync<AuthenticationException>(async () => await CreateQuicConnection(clientOptions));
Assert.Contains("ephemeral", e.Message);
}
finally
{
Configuration.Certificates.CleanupCertificates(nameof(Client_CertificateWithEphemeralKey_Throws));
clientCertificate.Dispose();
foreach (X509Certificate c in chain)
{
c.Dispose();
}
}
}
}
}
Loading