{
private static readonly IdnMapping s_idnMapping = new IdnMapping();
- internal static SslPolicyErrors BuildChainAndVerifyProperties(X509Chain chain, X509Certificate2 remoteCertificate, bool checkCertName, string? hostName)
+ internal static SslPolicyErrors BuildChainAndVerifyProperties(X509Chain chain, X509Certificate2 remoteCertificate, bool checkCertName, bool isServer, string? hostName)
{
SslPolicyErrors errors = chain.Build(remoteCertificate) ?
SslPolicyErrors.None :
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Win32.SafeHandles;
+using System.Diagnostics;
+using System.Net.Security;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using System.Security.Principal;
+
+namespace System.Net
+{
+ internal static partial class CertificateValidation
+ {
+ internal static SslPolicyErrors BuildChainAndVerifyProperties(X509Chain chain, X509Certificate2 remoteCertificate, bool checkCertName, bool isServer, string? hostName)
+ {
+ SslPolicyErrors sslPolicyErrors = SslPolicyErrors.None;
+
+ bool chainBuildResult = chain.Build(remoteCertificate);
+ if (!chainBuildResult // Build failed on handle or on policy.
+ && chain.SafeHandle!.DangerousGetHandle() == IntPtr.Zero) // Build failed to generate a valid handle.
+ {
+ throw new CryptographicException(Marshal.GetLastPInvokeError());
+ }
+
+ if (checkCertName)
+ {
+ unsafe
+ {
+ uint status = 0;
+
+ var eppStruct = new Interop.Crypt32.SSL_EXTRA_CERT_CHAIN_POLICY_PARA()
+ {
+ cbSize = (uint)sizeof(Interop.Crypt32.SSL_EXTRA_CERT_CHAIN_POLICY_PARA),
+ // Authenticate the remote party: (e.g. when operating in server mode, authenticate the client).
+ dwAuthType = isServer ? Interop.Crypt32.AuthType.AUTHTYPE_CLIENT : Interop.Crypt32.AuthType.AUTHTYPE_SERVER,
+ fdwChecks = 0,
+ pwszServerName = null
+ };
+
+ var cppStruct = new Interop.Crypt32.CERT_CHAIN_POLICY_PARA()
+ {
+ cbSize = (uint)sizeof(Interop.Crypt32.CERT_CHAIN_POLICY_PARA),
+ dwFlags = 0,
+ pvExtraPolicyPara = &eppStruct
+ };
+
+ fixed (char* namePtr = hostName)
+ {
+ eppStruct.pwszServerName = namePtr;
+ cppStruct.dwFlags |=
+ (Interop.Crypt32.CertChainPolicyIgnoreFlags.CERT_CHAIN_POLICY_IGNORE_ALL &
+ ~Interop.Crypt32.CertChainPolicyIgnoreFlags.CERT_CHAIN_POLICY_IGNORE_INVALID_NAME_FLAG);
+
+ SafeX509ChainHandle chainContext = chain.SafeHandle!;
+ status = Verify(chainContext, ref cppStruct);
+ if (status == Interop.Crypt32.CertChainPolicyErrors.CERT_E_CN_NO_MATCH)
+ {
+ sslPolicyErrors |= SslPolicyErrors.RemoteCertificateNameMismatch;
+ }
+ }
+ }
+ }
+
+ if (!chainBuildResult)
+ {
+ sslPolicyErrors |= SslPolicyErrors.RemoteCertificateChainErrors;
+ }
+
+ return sslPolicyErrors;
+ }
+
+ private static unsafe uint Verify(SafeX509ChainHandle chainContext, ref Interop.Crypt32.CERT_CHAIN_POLICY_PARA cpp)
+ {
+ Interop.Crypt32.CERT_CHAIN_POLICY_STATUS status = default;
+ status.cbSize = (uint)sizeof(Interop.Crypt32.CERT_CHAIN_POLICY_STATUS);
+
+ bool errorCode =
+ Interop.Crypt32.CertVerifyCertificateChainPolicy(
+ (IntPtr)Interop.Crypt32.CertChainPolicy.CERT_CHAIN_POLICY_SSL,
+ chainContext,
+ ref cpp,
+ ref status);
+
+ if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(chainContext, $"CertVerifyCertificateChainPolicy returned: {errorCode}. Status: {status.dwError}");
+ return status.dwError;
+ }
+ }
+}
<data name="net_quic_cert_chain_validation" xml:space="preserve">
<value>The remote certificate is invalid because of errors in the certificate chain: {0}</value>
</data>
+ <data name="net_ssl_app_protocols_invalid" xml:space="preserve">
+ <value>The application protocol list is invalid.</value>
+ </data>
</root>
<!-- Windows specific files -->
<ItemGroup Condition=" '$(TargetsWindows)' == 'true'">
<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.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" />
+ <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CRYPT_BIT_BLOB.cs" Link="Common\Interop\Windows\Crypt32\Interop.Interop.CRYPT_BIT_BLOB.cs" />
+ <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.DATA_BLOB.cs" Link="Common\Interop\Windows\Crypt32\Interop.DATA_BLOB.cs" />
+ <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.certificates.cs" Link="Common\Interop\Windows\Crypt32\Interop.certificates.cs" />
+ <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.certificates_types.cs" Link="Common\Interop\Windows\Crypt32\Interop.certificates_types.cs" />
+ <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CertEnumCertificatesInStore.cs" Link="Common\Interop\Windows\Crypt32\Interop.CertEnumCertificatesInStore.cs" />
+ <Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.MsgEncodingType.cs" Link="Common\Interop\Windows\Crypt32\Interop.Interop.MsgEncodingType.cs" />
+ <Compile Include="$(CommonPath)System\Net\Security\CertificateValidation.Windows.cs" Link="Common\System\Net\Security\CertificateValidation.Windows.cs" />
<Compile Include="System\Net\Quic\Implementations\MsQuic\Interop\MsQuicStatusCodes.Windows.cs" />
</ItemGroup>
+ <!-- Unix (OSX + Linux) specific files -->
+ <ItemGroup Condition="'$(TargetsUnix)' == 'true'">
+ <Compile Include="$(CommonPath)Interop\Unix\Interop.Libraries.cs" Link="Common\Interop\Unix\Interop.Libraries.cs" />
+ <Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.ASN1.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.ASN1.cs" />
+ <Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.BIO.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.BIO.cs" />
+ <Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.ERR.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.ERR.cs" />
+ <Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.Initialization.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Initialization.cs" />
+ <Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.Crypto.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Crypto.cs" />
+ <Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.OpenSslVersion.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.OpenSslVersion.cs" />
+ <Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.Ssl.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Ssl.cs" />
+ <Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.SslCtx.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.SslCtx.cs" />
+ <Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.SetProtocolOptions.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.SetProtocolOptions.cs" />
+ <Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.X509.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.X509.cs" />
+ <Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.X509Name.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.X509Name.cs" />
+ <Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.X509Ext.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.X509Ext.cs" />
+ <Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.X509Stack.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.X509Stack.cs" />
+ <Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.X509StoreCtx.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.X509StoreCtx.cs" />
+ <Compile Include="$(CommonPath)Interop\Unix\System.Net.Security.Native\Interop.Initialization.cs" Link="Common\Interop\Unix\System.Net.Security.Native\Interop.Initialization.cs" />
+ <Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeX509Handles.Unix.cs" Link="Common\Microsoft\Win32\SafeHandles\SafeX509Handles.Unix.cs" />
+ <Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\X509ExtensionSafeHandles.Unix.cs" Link="Common\Microsoft\Win32\SafeHandles\X509ExtensionSafeHandles.Unix.cs" />
+ <Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeInteriorHandle.cs" Link="Common\Microsoft\Win32\SafeHandles\SafeInteriorHandle.cs" />
+ <Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeBioHandle.Unix.cs" Link="Common\Microsoft\Win32\SafeHandles\SafeBioHandle.Unix.cs" />
+ <Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\Asn1SafeHandles.Unix.cs" Link="Common\Microsoft\Win32\SafeHandles\Asn1SafeHandles.Unix.cs" />
+ <Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeHandleCache.cs" Link="Common\Microsoft\Win32\SafeHandles\SafeHandleCache.cs" />
+ <Compile Include="$(CommonPath)System\Net\Security\CertificateValidation.Unix.cs" Link="Common\System\Net\Security\CertificateValidation.Unix.cs" />
+ </ItemGroup>
<!-- Linux specific files -->
<ItemGroup Condition="'$(TargetsLinux)' == 'true'">
<Compile Include="$(CommonPath)Interop\Linux\Interop.Libraries.cs" Link="Common\Interop\Linux\Interop.Libraries.cs" />
<Compile Include="$(CommonPath)Interop\OSX\Interop.Libraries.cs" Link="Common\Interop\OSX\Interop.Libraries.cs" />
<Compile Include="System\Net\Quic\Implementations\MsQuic\Interop\MsQuicStatusCodes.OSX.cs" />
</ItemGroup>
+
<!-- Project references -->
<ItemGroup>
<Reference Include="System.Threading.Channels" />
</ItemGroup>
+ <ItemGroup Condition="'$(TargetsUnix)' == 'true'">
+ <ProjectReference Include="$(LibrariesProjectRoot)System.Security.Cryptography.OpenSsl\src\System.Security.Cryptography.OpenSsl.csproj" />
+ <Reference Include="System.Diagnostics.StackTrace" Condition="'$(Configuration)' == 'Debug'" />
+ </ItemGroup>
+
<!-- Support for deploying msquic -->
<ItemGroup Condition="'$(TargetsWindows)' == 'true' and
('$(TargetArchitecture)' == 'x64' or '$(TargetArchitecture)' == 'x86')">
chain.ChainPolicy.ExtraStore.AddRange(additionalCertificates);
}
- if (!chain.Build(certificate))
- {
- sslPolicyErrors |= SslPolicyErrors.RemoteCertificateChainErrors;
- }
+ sslPolicyErrors |= CertificateValidation.BuildChainAndVerifyProperties(chain, certificate, true, state.IsServer, state.TargetHost);
}
if (!state.RemoteCertificateRequired)
if (NetEventSource.Log.IsEnabled())
NetEventSource.Info(state, $"{state.TraceId} Certificate validation for '${certificate?.Subject}' finished with ${sslPolicyErrors}");
-// return (sslPolicyErrors == SslPolicyErrors.None) ? MsQuicStatusCodes.Success : MsQuicStatusCodes.HandshakeFailure;
if (sslPolicyErrors != SslPolicyErrors.None)
{
}
[Fact]
+ public async Task ConnectWithCertificateForDifferentName_Throws()
+ {
+ (X509Certificate2 certificate, _) = System.Net.Security.Tests.TestHelper.GenerateCertificates("localhost");
+
+ var quicOptions = new QuicListenerOptions();
+ quicOptions.ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0);
+ quicOptions.ServerAuthenticationOptions = GetSslServerAuthenticationOptions();
+ quicOptions.ServerAuthenticationOptions.ServerCertificate = certificate;
+
+ using QuicListener listener = new QuicListener(QuicImplementationProviders.MsQuic, quicOptions);
+
+ QuicClientConnectionOptions options = new QuicClientConnectionOptions()
+ {
+ RemoteEndPoint = listener.ListenEndPoint,
+ ClientAuthenticationOptions = GetSslClientAuthenticationOptions(),
+ };
+
+ // Use different target host on purpose to get RemoteCertificateNameMismatch ssl error.
+ options.ClientAuthenticationOptions.TargetHost = "loopback";
+ options.ClientAuthenticationOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) =>
+ {
+ Assert.Equal(certificate.Subject, cert.Subject);
+ Assert.Equal(certificate.Issuer, cert.Issuer);
+ Assert.Equal(SslPolicyErrors.RemoteCertificateNameMismatch, errors & SslPolicyErrors.RemoteCertificateNameMismatch);
+ return SslPolicyErrors.None == errors;
+ };
+
+ using QuicConnection clientConnection = new QuicConnection(QuicImplementationProviders.MsQuic, options);
+ ValueTask clientTask = clientConnection.ConnectAsync();
+
+ using QuicConnection serverConnection = await listener.AcceptConnectionAsync();
+ await Assert.ThrowsAsync<AuthenticationException>(async () => await clientTask);
+ }
+
+ [Theory]
+ [InlineData("127.0.0.1", true)]
+ [InlineData("::1", true)]
+ [InlineData("127.0.0.1", false)]
+ [InlineData("::1", false)]
+ public async Task ConnectWithCertificateForLoopbackIP_IndicatesExpectedError(string ipString, bool expectsError)
+ {
+ var ipAddress = IPAddress.Parse(ipString);
+ (X509Certificate2 certificate, _) = System.Net.Security.Tests.TestHelper.GenerateCertificates(expectsError ? "badhost" : "localhost");
+
+ var quicOptions = new QuicListenerOptions();
+ quicOptions.ListenEndPoint = new IPEndPoint(ipAddress, 0);
+ quicOptions.ServerAuthenticationOptions = GetSslServerAuthenticationOptions();
+ quicOptions.ServerAuthenticationOptions.ServerCertificate = certificate;
+
+ using QuicListener listener = new QuicListener(QuicImplementationProviders.MsQuic, quicOptions);
+
+ QuicClientConnectionOptions options = new QuicClientConnectionOptions()
+ {
+ RemoteEndPoint = new IPEndPoint(ipAddress, listener.ListenEndPoint.Port),
+ ClientAuthenticationOptions = GetSslClientAuthenticationOptions(),
+ };
+
+ options.ClientAuthenticationOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) =>
+ {
+ Assert.Equal(certificate.Subject, cert.Subject);
+ Assert.Equal(certificate.Issuer, cert.Issuer);
+ Assert.Equal(expectsError ? SslPolicyErrors.RemoteCertificateNameMismatch : SslPolicyErrors.None, errors & SslPolicyErrors.RemoteCertificateNameMismatch);
+ return true;
+ };
+
+ using QuicConnection clientConnection = new QuicConnection(QuicImplementationProviders.MsQuic, options);
+ ValueTask clientTask = clientConnection.ConnectAsync();
+
+ using QuicConnection serverConnection = await listener.AcceptConnectionAsync();
+ await clientTask;
+ }
+
+ [Fact]
[PlatformSpecific(TestPlatforms.Windows)]
[ActiveIssue("https://github.com/microsoft/msquic/pull/1728")]
public async Task ConnectWithClientCertificate()
<Compile Include="System\Net\Security\SslStreamPal.Windows.cs" />
<Compile Include="System\Net\Security\SslConnectionInfo.Windows.cs" />
<Compile Include="System\Net\Security\StreamSizes.Windows.cs" />
+ <Compile Include="$(CommonPath)System\Net\Security\CertificateValidation.Windows.cs"
+ Link="Common\System\Net\Security\CertificateValidation.Windows.cs" />
<Compile Include="$(CommonPath)System\Net\Security\SecurityBuffer.Windows.cs"
Link="Common\System\Net\Security\SecurityBuffer.Windows.cs" />
<Compile Include="$(CommonPath)System\Net\Security\SecurityBufferType.Windows.cs"
bool isServer,
string? hostName)
{
- return CertificateValidation.BuildChainAndVerifyProperties(chain, remoteCertificate, checkCertName, hostName);
+ return CertificateValidation.BuildChainAndVerifyProperties(chain, remoteCertificate, checkCertName, isServer, hostName);
}
//
bool isServer,
string? hostName)
{
- SslPolicyErrors sslPolicyErrors = SslPolicyErrors.None;
-
- bool chainBuildResult = chain.Build(remoteCertificate);
- if (!chainBuildResult // Build failed on handle or on policy.
- && chain.SafeHandle!.DangerousGetHandle() == IntPtr.Zero) // Build failed to generate a valid handle.
- {
- throw new CryptographicException(Marshal.GetLastPInvokeError());
- }
-
- if (checkCertName)
- {
- unsafe
- {
- uint status = 0;
-
- var eppStruct = new Interop.Crypt32.SSL_EXTRA_CERT_CHAIN_POLICY_PARA()
- {
- cbSize = (uint)sizeof(Interop.Crypt32.SSL_EXTRA_CERT_CHAIN_POLICY_PARA),
- // Authenticate the remote party: (e.g. when operating in server mode, authenticate the client).
- dwAuthType = isServer ? Interop.Crypt32.AuthType.AUTHTYPE_CLIENT : Interop.Crypt32.AuthType.AUTHTYPE_SERVER,
- fdwChecks = 0,
- pwszServerName = null
- };
-
- var cppStruct = new Interop.Crypt32.CERT_CHAIN_POLICY_PARA()
- {
- cbSize = (uint)sizeof(Interop.Crypt32.CERT_CHAIN_POLICY_PARA),
- dwFlags = 0,
- pvExtraPolicyPara = &eppStruct
- };
-
- fixed (char* namePtr = hostName)
- {
- eppStruct.pwszServerName = namePtr;
- cppStruct.dwFlags |=
- (Interop.Crypt32.CertChainPolicyIgnoreFlags.CERT_CHAIN_POLICY_IGNORE_ALL &
- ~Interop.Crypt32.CertChainPolicyIgnoreFlags.CERT_CHAIN_POLICY_IGNORE_INVALID_NAME_FLAG);
-
- SafeX509ChainHandle chainContext = chain.SafeHandle!;
- status = Verify(chainContext, ref cppStruct);
- if (status == Interop.Crypt32.CertChainPolicyErrors.CERT_E_CN_NO_MATCH)
- {
- sslPolicyErrors |= SslPolicyErrors.RemoteCertificateNameMismatch;
- }
- }
- }
- }
-
- if (!chainBuildResult)
- {
- sslPolicyErrors |= SslPolicyErrors.RemoteCertificateChainErrors;
- }
-
- return sslPolicyErrors;
+ return CertificateValidation.BuildChainAndVerifyProperties(chain, remoteCertificate, checkCertName, isServer, hostName);
}
//
return store;
}
-
- private static unsafe uint Verify(SafeX509ChainHandle chainContext, ref Interop.Crypt32.CERT_CHAIN_POLICY_PARA cpp)
- {
- Interop.Crypt32.CERT_CHAIN_POLICY_STATUS status = default;
- status.cbSize = (uint)sizeof(Interop.Crypt32.CERT_CHAIN_POLICY_STATUS);
-
- bool errorCode =
- Interop.Crypt32.CertVerifyCertificateChainPolicy(
- (IntPtr)Interop.Crypt32.CertChainPolicy.CERT_CHAIN_POLICY_SSL,
- chainContext,
- ref cpp,
- ref status);
-
- if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(chainContext, $"CertVerifyCertificateChainPolicy returned: {errorCode}. Status: {status.dwError}");
- return status.dwError;
- }
}
}