Skip to content

Commit 877af80

Browse files
authored
[QUIC] Certificate name validation (#56175)
* Shared CertificateValidation and used in S.N.Quic * adressed feedback * Post merge * Added tests
1 parent b12bfc5 commit 877af80

File tree

9 files changed

+215
-77
lines changed

9 files changed

+215
-77
lines changed

src/libraries/Common/src/System/Net/Security/CertificateValidation.Unix.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ internal static class CertificateValidation
1212
{
1313
private static readonly IdnMapping s_idnMapping = new IdnMapping();
1414

15-
internal static SslPolicyErrors BuildChainAndVerifyProperties(X509Chain chain, X509Certificate2 remoteCertificate, bool checkCertName, string? hostName)
15+
internal static SslPolicyErrors BuildChainAndVerifyProperties(X509Chain chain, X509Certificate2 remoteCertificate, bool checkCertName, bool isServer, string? hostName)
1616
{
1717
SslPolicyErrors errors = chain.Build(remoteCertificate) ?
1818
SslPolicyErrors.None :
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.Win32.SafeHandles;
5+
using System.Diagnostics;
6+
using System.Net.Security;
7+
using System.Runtime.InteropServices;
8+
using System.Security.Cryptography;
9+
using System.Security.Cryptography.X509Certificates;
10+
using System.Security.Principal;
11+
12+
namespace System.Net
13+
{
14+
internal static partial class CertificateValidation
15+
{
16+
internal static SslPolicyErrors BuildChainAndVerifyProperties(X509Chain chain, X509Certificate2 remoteCertificate, bool checkCertName, bool isServer, string? hostName)
17+
{
18+
SslPolicyErrors sslPolicyErrors = SslPolicyErrors.None;
19+
20+
bool chainBuildResult = chain.Build(remoteCertificate);
21+
if (!chainBuildResult // Build failed on handle or on policy.
22+
&& chain.SafeHandle!.DangerousGetHandle() == IntPtr.Zero) // Build failed to generate a valid handle.
23+
{
24+
throw new CryptographicException(Marshal.GetLastPInvokeError());
25+
}
26+
27+
if (checkCertName)
28+
{
29+
unsafe
30+
{
31+
uint status = 0;
32+
33+
var eppStruct = new Interop.Crypt32.SSL_EXTRA_CERT_CHAIN_POLICY_PARA()
34+
{
35+
cbSize = (uint)sizeof(Interop.Crypt32.SSL_EXTRA_CERT_CHAIN_POLICY_PARA),
36+
// Authenticate the remote party: (e.g. when operating in server mode, authenticate the client).
37+
dwAuthType = isServer ? Interop.Crypt32.AuthType.AUTHTYPE_CLIENT : Interop.Crypt32.AuthType.AUTHTYPE_SERVER,
38+
fdwChecks = 0,
39+
pwszServerName = null
40+
};
41+
42+
var cppStruct = new Interop.Crypt32.CERT_CHAIN_POLICY_PARA()
43+
{
44+
cbSize = (uint)sizeof(Interop.Crypt32.CERT_CHAIN_POLICY_PARA),
45+
dwFlags = 0,
46+
pvExtraPolicyPara = &eppStruct
47+
};
48+
49+
fixed (char* namePtr = hostName)
50+
{
51+
eppStruct.pwszServerName = namePtr;
52+
cppStruct.dwFlags |=
53+
(Interop.Crypt32.CertChainPolicyIgnoreFlags.CERT_CHAIN_POLICY_IGNORE_ALL &
54+
~Interop.Crypt32.CertChainPolicyIgnoreFlags.CERT_CHAIN_POLICY_IGNORE_INVALID_NAME_FLAG);
55+
56+
SafeX509ChainHandle chainContext = chain.SafeHandle!;
57+
status = Verify(chainContext, ref cppStruct);
58+
if (status == Interop.Crypt32.CertChainPolicyErrors.CERT_E_CN_NO_MATCH)
59+
{
60+
sslPolicyErrors |= SslPolicyErrors.RemoteCertificateNameMismatch;
61+
}
62+
}
63+
}
64+
}
65+
66+
if (!chainBuildResult)
67+
{
68+
sslPolicyErrors |= SslPolicyErrors.RemoteCertificateChainErrors;
69+
}
70+
71+
return sslPolicyErrors;
72+
}
73+
74+
private static unsafe uint Verify(SafeX509ChainHandle chainContext, ref Interop.Crypt32.CERT_CHAIN_POLICY_PARA cpp)
75+
{
76+
Interop.Crypt32.CERT_CHAIN_POLICY_STATUS status = default;
77+
status.cbSize = (uint)sizeof(Interop.Crypt32.CERT_CHAIN_POLICY_STATUS);
78+
79+
bool errorCode =
80+
Interop.Crypt32.CertVerifyCertificateChainPolicy(
81+
(IntPtr)Interop.Crypt32.CertChainPolicy.CERT_CHAIN_POLICY_SSL,
82+
chainContext,
83+
ref cpp,
84+
ref status);
85+
86+
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(chainContext, $"CertVerifyCertificateChainPolicy returned: {errorCode}. Status: {status.dwError}");
87+
return status.dwError;
88+
}
89+
}
90+
}

src/libraries/System.Net.Quic/src/Resources/Strings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,5 +159,8 @@
159159
<data name="net_quic_cert_chain_validation" xml:space="preserve">
160160
<value>The remote certificate is invalid because of errors in the certificate chain: {0}</value>
161161
</data>
162+
<data name="net_ssl_app_protocols_invalid" xml:space="preserve">
163+
<value>The application protocol list is invalid.</value>
164+
</data>
162165
</root>
163166

src/libraries/System.Net.Quic/src/System.Net.Quic.csproj

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,45 @@
3838
<!-- Windows specific files -->
3939
<ItemGroup Condition=" '$(TargetsWindows)' == 'true'">
4040
<Compile Include="$(CommonPath)Interop\Windows\Interop.Libraries.cs" Link="Common\Interop\Windows\Interop.Libraries.cs" />
41+
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CERT_CONTEXT.cs" Link="Common\Interop\Windows\Crypt32\Interop.CERT_CONTEXT.cs" />
42+
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CERT_INFO.cs" Link="Common\Interop\Windows\Crypt32\Interop.CERT_INFO.cs" />
43+
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CERT_PUBLIC_KEY_INFO.cs" Link="Common\Interop\Windows\Crypt32\Interop.CERT_PUBLIC_KEY_INFO.cs" />
44+
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CRYPT_ALGORITHM_IDENTIFIER.cs" Link="Common\Interop\Windows\Crypt32\Interop.Interop.CRYPT_ALGORITHM_IDENTIFIER.cs" />
45+
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CRYPT_BIT_BLOB.cs" Link="Common\Interop\Windows\Crypt32\Interop.Interop.CRYPT_BIT_BLOB.cs" />
46+
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.DATA_BLOB.cs" Link="Common\Interop\Windows\Crypt32\Interop.DATA_BLOB.cs" />
47+
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.certificates.cs" Link="Common\Interop\Windows\Crypt32\Interop.certificates.cs" />
48+
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.certificates_types.cs" Link="Common\Interop\Windows\Crypt32\Interop.certificates_types.cs" />
49+
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.CertEnumCertificatesInStore.cs" Link="Common\Interop\Windows\Crypt32\Interop.CertEnumCertificatesInStore.cs" />
50+
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.MsgEncodingType.cs" Link="Common\Interop\Windows\Crypt32\Interop.Interop.MsgEncodingType.cs" />
51+
<Compile Include="$(CommonPath)System\Net\Security\CertificateValidation.Windows.cs" Link="Common\System\Net\Security\CertificateValidation.Windows.cs" />
4152
<Compile Include="System\Net\Quic\Implementations\MsQuic\Interop\MsQuicStatusCodes.Windows.cs" />
4253
</ItemGroup>
54+
<!-- Unix (OSX + Linux) specific files -->
55+
<ItemGroup Condition="'$(TargetsUnix)' == 'true'">
56+
<Compile Include="$(CommonPath)Interop\Unix\Interop.Libraries.cs" Link="Common\Interop\Unix\Interop.Libraries.cs" />
57+
<Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.ASN1.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.ASN1.cs" />
58+
<Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.BIO.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.BIO.cs" />
59+
<Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.ERR.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.ERR.cs" />
60+
<Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.Initialization.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Initialization.cs" />
61+
<Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.Crypto.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Crypto.cs" />
62+
<Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.OpenSslVersion.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.OpenSslVersion.cs" />
63+
<Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.Ssl.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Ssl.cs" />
64+
<Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.SslCtx.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.SslCtx.cs" />
65+
<Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.SetProtocolOptions.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.SetProtocolOptions.cs" />
66+
<Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.X509.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.X509.cs" />
67+
<Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.X509Name.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.X509Name.cs" />
68+
<Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.X509Ext.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.X509Ext.cs" />
69+
<Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.X509Stack.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.X509Stack.cs" />
70+
<Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.X509StoreCtx.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.X509StoreCtx.cs" />
71+
<Compile Include="$(CommonPath)Interop\Unix\System.Net.Security.Native\Interop.Initialization.cs" Link="Common\Interop\Unix\System.Net.Security.Native\Interop.Initialization.cs" />
72+
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeX509Handles.Unix.cs" Link="Common\Microsoft\Win32\SafeHandles\SafeX509Handles.Unix.cs" />
73+
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\X509ExtensionSafeHandles.Unix.cs" Link="Common\Microsoft\Win32\SafeHandles\X509ExtensionSafeHandles.Unix.cs" />
74+
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeInteriorHandle.cs" Link="Common\Microsoft\Win32\SafeHandles\SafeInteriorHandle.cs" />
75+
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeBioHandle.Unix.cs" Link="Common\Microsoft\Win32\SafeHandles\SafeBioHandle.Unix.cs" />
76+
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\Asn1SafeHandles.Unix.cs" Link="Common\Microsoft\Win32\SafeHandles\Asn1SafeHandles.Unix.cs" />
77+
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeHandleCache.cs" Link="Common\Microsoft\Win32\SafeHandles\SafeHandleCache.cs" />
78+
<Compile Include="$(CommonPath)System\Net\Security\CertificateValidation.Unix.cs" Link="Common\System\Net\Security\CertificateValidation.Unix.cs" />
79+
</ItemGroup>
4380
<!-- Linux specific files -->
4481
<ItemGroup Condition="'$(TargetsLinux)' == 'true'">
4582
<Compile Include="$(CommonPath)Interop\Linux\Interop.Libraries.cs" Link="Common\Interop\Linux\Interop.Libraries.cs" />
@@ -56,6 +93,7 @@
5693
<Compile Include="$(CommonPath)Interop\OSX\Interop.Libraries.cs" Link="Common\Interop\OSX\Interop.Libraries.cs" />
5794
<Compile Include="System\Net\Quic\Implementations\MsQuic\Interop\MsQuicStatusCodes.OSX.cs" />
5895
</ItemGroup>
96+
5997
<!-- Project references -->
6098

6199
<ItemGroup>
@@ -83,6 +121,11 @@
83121
<Reference Include="System.Threading.Channels" />
84122
</ItemGroup>
85123

124+
<ItemGroup Condition="'$(TargetsUnix)' == 'true'">
125+
<ProjectReference Include="$(LibrariesProjectRoot)System.Security.Cryptography.OpenSsl\src\System.Security.Cryptography.OpenSsl.csproj" />
126+
<Reference Include="System.Diagnostics.StackTrace" Condition="'$(Configuration)' == 'Debug'" />
127+
</ItemGroup>
128+
86129
<!-- Support for deploying msquic -->
87130
<ItemGroup Condition="'$(TargetsWindows)' == 'true' and
88131
('$(TargetArchitecture)' == 'x64' or '$(TargetArchitecture)' == 'x86')">

src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -386,10 +386,7 @@ private static uint HandleEventPeerCertificateReceived(State state, ref Connecti
386386
chain.ChainPolicy.ExtraStore.AddRange(additionalCertificates);
387387
}
388388

389-
if (!chain.Build(certificate))
390-
{
391-
sslPolicyErrors |= SslPolicyErrors.RemoteCertificateChainErrors;
392-
}
389+
sslPolicyErrors |= CertificateValidation.BuildChainAndVerifyProperties(chain, certificate, true, state.IsServer, state.TargetHost);
393390
}
394391

395392
if (!state.RemoteCertificateRequired)
@@ -418,7 +415,6 @@ private static uint HandleEventPeerCertificateReceived(State state, ref Connecti
418415
if (NetEventSource.Log.IsEnabled())
419416
NetEventSource.Info(state, $"{state.TraceId} Certificate validation for '${certificate?.Subject}' finished with ${sslPolicyErrors}");
420417

421-
// return (sslPolicyErrors == SslPolicyErrors.None) ? MsQuicStatusCodes.Success : MsQuicStatusCodes.HandshakeFailure;
422418

423419
if (sslPolicyErrors != SslPolicyErrors.None)
424420
{

src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,79 @@ public async Task ConnectWithCertificateCallback()
238238
serverConnection.Dispose();
239239
}
240240

241+
[Fact]
242+
public async Task ConnectWithCertificateForDifferentName_Throws()
243+
{
244+
(X509Certificate2 certificate, _) = System.Net.Security.Tests.TestHelper.GenerateCertificates("localhost");
245+
246+
var quicOptions = new QuicListenerOptions();
247+
quicOptions.ListenEndPoint = new IPEndPoint(IPAddress.Loopback, 0);
248+
quicOptions.ServerAuthenticationOptions = GetSslServerAuthenticationOptions();
249+
quicOptions.ServerAuthenticationOptions.ServerCertificate = certificate;
250+
251+
using QuicListener listener = new QuicListener(QuicImplementationProviders.MsQuic, quicOptions);
252+
253+
QuicClientConnectionOptions options = new QuicClientConnectionOptions()
254+
{
255+
RemoteEndPoint = listener.ListenEndPoint,
256+
ClientAuthenticationOptions = GetSslClientAuthenticationOptions(),
257+
};
258+
259+
// Use different target host on purpose to get RemoteCertificateNameMismatch ssl error.
260+
options.ClientAuthenticationOptions.TargetHost = "loopback";
261+
options.ClientAuthenticationOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) =>
262+
{
263+
Assert.Equal(certificate.Subject, cert.Subject);
264+
Assert.Equal(certificate.Issuer, cert.Issuer);
265+
Assert.Equal(SslPolicyErrors.RemoteCertificateNameMismatch, errors & SslPolicyErrors.RemoteCertificateNameMismatch);
266+
return SslPolicyErrors.None == errors;
267+
};
268+
269+
using QuicConnection clientConnection = new QuicConnection(QuicImplementationProviders.MsQuic, options);
270+
ValueTask clientTask = clientConnection.ConnectAsync();
271+
272+
using QuicConnection serverConnection = await listener.AcceptConnectionAsync();
273+
await Assert.ThrowsAsync<AuthenticationException>(async () => await clientTask);
274+
}
275+
276+
[Theory]
277+
[InlineData("127.0.0.1", true)]
278+
[InlineData("::1", true)]
279+
[InlineData("127.0.0.1", false)]
280+
[InlineData("::1", false)]
281+
public async Task ConnectWithCertificateForLoopbackIP_IndicatesExpectedError(string ipString, bool expectsError)
282+
{
283+
var ipAddress = IPAddress.Parse(ipString);
284+
(X509Certificate2 certificate, _) = System.Net.Security.Tests.TestHelper.GenerateCertificates(expectsError ? "badhost" : "localhost");
285+
286+
var quicOptions = new QuicListenerOptions();
287+
quicOptions.ListenEndPoint = new IPEndPoint(ipAddress, 0);
288+
quicOptions.ServerAuthenticationOptions = GetSslServerAuthenticationOptions();
289+
quicOptions.ServerAuthenticationOptions.ServerCertificate = certificate;
290+
291+
using QuicListener listener = new QuicListener(QuicImplementationProviders.MsQuic, quicOptions);
292+
293+
QuicClientConnectionOptions options = new QuicClientConnectionOptions()
294+
{
295+
RemoteEndPoint = new IPEndPoint(ipAddress, listener.ListenEndPoint.Port),
296+
ClientAuthenticationOptions = GetSslClientAuthenticationOptions(),
297+
};
298+
299+
options.ClientAuthenticationOptions.RemoteCertificateValidationCallback = (sender, cert, chain, errors) =>
300+
{
301+
Assert.Equal(certificate.Subject, cert.Subject);
302+
Assert.Equal(certificate.Issuer, cert.Issuer);
303+
Assert.Equal(expectsError ? SslPolicyErrors.RemoteCertificateNameMismatch : SslPolicyErrors.None, errors & SslPolicyErrors.RemoteCertificateNameMismatch);
304+
return true;
305+
};
306+
307+
using QuicConnection clientConnection = new QuicConnection(QuicImplementationProviders.MsQuic, options);
308+
ValueTask clientTask = clientConnection.ConnectAsync();
309+
310+
using QuicConnection serverConnection = await listener.AcceptConnectionAsync();
311+
await clientTask;
312+
}
313+
241314
[Fact]
242315
[PlatformSpecific(TestPlatforms.Windows)]
243316
[ActiveIssue("https://github.com/microsoft/msquic/pull/1728")]

src/libraries/System.Net.Security/src/System.Net.Security.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,8 @@
144144
<Compile Include="System\Net\Security\SslStreamPal.Windows.cs" />
145145
<Compile Include="System\Net\Security\SslConnectionInfo.Windows.cs" />
146146
<Compile Include="System\Net\Security\StreamSizes.Windows.cs" />
147+
<Compile Include="$(CommonPath)System\Net\Security\CertificateValidation.Windows.cs"
148+
Link="Common\System\Net\Security\CertificateValidation.Windows.cs" />
147149
<Compile Include="$(CommonPath)System\Net\Security\SecurityBuffer.Windows.cs"
148150
Link="Common\System\Net\Security\SecurityBuffer.Windows.cs" />
149151
<Compile Include="$(CommonPath)System\Net\Security\SecurityBufferType.Windows.cs"

src/libraries/System.Net.Security/src/System/Net/CertificateValidationPal.Unix.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ internal static SslPolicyErrors VerifyCertificateProperties(
1818
bool isServer,
1919
string? hostName)
2020
{
21-
return CertificateValidation.BuildChainAndVerifyProperties(chain, remoteCertificate, checkCertName, hostName);
21+
return CertificateValidation.BuildChainAndVerifyProperties(chain, remoteCertificate, checkCertName, isServer, hostName);
2222
}
2323

2424
//

0 commit comments

Comments
 (0)