* Fix WinHttpHandler when building certificate chain
Unlike SslStream, WinHttpHandler was not using all the certificates
obtained from the TLS/SSL handshake when building the chain. In
addition to the end-entity server certificate, there were other certs
available in the native cert handle that could be passed along
to the chain build operation.
As part of fixing this, I refactored the Common schannel and crypto
interop code so that it can be used in both SslStream and
WinHttpHandler.
I tested this manually because this cannot be easily tested in the CI
system. Added traces in WinHttpHandler to support this.
Fixes dotnet/corefx#23827
Fixes dotnet/corefx#4473
* Address PR feedback
Check for null IntPtr.
Commit migrated from https://github.com/dotnet/corefx/commit/
dcc29528c9c4633e81b1937e7dd9fde7115a938c
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Net.Security;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography.X509Certificates;
+
+namespace System.Net
+{
+ internal static unsafe partial class UnmanagedCertificateContext
+ {
+ internal static X509Certificate2Collection GetRemoteCertificatesFromStoreContext(IntPtr certContext)
+ {
+ X509Certificate2Collection result = new X509Certificate2Collection();
+
+ if (certContext == IntPtr.Zero)
+ {
+ return result;
+ }
+
+ Interop.Crypt32.CERT_CONTEXT context =
+ Marshal.PtrToStructure<Interop.Crypt32.CERT_CONTEXT>(certContext);
+
+ if (context.hCertStore != IntPtr.Zero)
+ {
+ Interop.Crypt32.CERT_CONTEXT* last = null;
+
+ while (true)
+ {
+ Interop.Crypt32.CERT_CONTEXT* next =
+ Interop.Crypt32.CertEnumCertificatesInStore(context.hCertStore, last);
+
+ if (next == null)
+ {
+ break;
+ }
+
+ var cert = new X509Certificate2(new IntPtr(next));
+ if (NetEventSource.IsEnabled) NetEventSource.Info(certContext, $"Adding remote certificate:{cert}");
+
+ result.Add(cert);
+ last = next;
+ }
+ }
+
+ return result;
+ }
+ }
+}
namespace System.Net
{
- internal static unsafe class UnmanagedCertificateContext
+ internal static unsafe partial class UnmanagedCertificateContext
{
internal static X509Certificate2Collection GetRemoteCertificatesFromStoreContext(SafeFreeCertContext certContext)
{
- X509Certificate2Collection result = new X509Certificate2Collection();
-
if (certContext.IsInvalid)
{
- return result;
- }
-
- Interop.Crypt32.CERT_CONTEXT context =
- Marshal.PtrToStructure<Interop.Crypt32.CERT_CONTEXT>(certContext.DangerousGetHandle());
-
- if (context.hCertStore != IntPtr.Zero)
- {
- Interop.Crypt32.CERT_CONTEXT* last = null;
-
- while (true)
- {
- Interop.Crypt32.CERT_CONTEXT* next =
- Interop.Crypt32.CertEnumCertificatesInStore(context.hCertStore, last);
-
- if (next == null)
- {
- break;
- }
-
- var cert = new X509Certificate2(new IntPtr(next));
- if (NetEventSource.IsEnabled) NetEventSource.Info(certContext, $"Adding remote certificate:{cert}");
-
- result.Add(cert);
- last = next;
- }
+ return new X509Certificate2Collection();
}
- return result;
+ return GetRemoteCertificatesFromStoreContext(certContext.DangerousGetHandle());
}
}
}
<Compile Include="$(CommonPath)\System\Net\HttpVersionInternal.cs">
<Link>Common\System\Net\HttpVersionInternal.cs</Link>
</Compile>
+ <Compile Include="$(CommonPath)\System\Net\Logging\NetEventSource.Common.cs">
+ <Link>Common\System\Net\Logging\NetEventSource.Common.cs</Link>
+ </Compile>
</ItemGroup>
<ItemGroup>
<Reference Include="System.Buffers" />
</PropertyGroup>
<ItemGroup>
<CompileItem Include="$(CommonPath)\Interop\Windows\Interop.Libraries.cs" />
+ <CompileItem Include="$(CommonPath)\Interop\Windows\Crypt32\Interop.CertEnumCertificatesInStore.cs" />
<CompileItem Include="$(CommonPath)\Interop\Windows\Crypt32\Interop.certificates_types.cs" />
<CompileItem Include="$(CommonPath)\Interop\Windows\Crypt32\Interop.certificates.cs" />
<CompileItem Include="$(CommonPath)\Interop\Windows\kernel32\Interop.FormatMessage.cs" />
<CompileItem Include="$(CommonPath)\Interop\Windows\kernel32\Interop.GetModuleHandle.cs" />
<CompileItem Include="$(CommonPath)\Interop\Windows\Interop.HRESULT_FROM_WIN32.cs" />
+ <CompileItem Include="$(CommonPath)\Interop\Windows\SChannel\UnmanagedCertificateContext.IntPtr.cs" />
<CompileItem Include="$(CommonPath)\Interop\Windows\winhttp\Interop.SafeWinHttpHandle.cs" />
<CompileItem Include="$(CommonPath)\Interop\Windows\winhttp\Interop.winhttp_types.cs" />
<CompileItem Include="$(CommonPath)\Interop\Windows\winhttp\Interop.winhttp.cs" />
<CompileItem Include="$(CommonPath)\System\StringExtensions.cs" />
<CompileItem Include="$(CommonPath)\System\Net\HttpKnownHeaderNames.cs" />
<CompileItem Include="$(CommonPath)\System\Net\HttpKnownHeaderNames.TryGetHeaderName.cs" />
- <CompileItem Include="$(CommonPath)\System\Net\UriScheme.cs" />
<CompileItem Include="$(CommonPath)\System\Net\SecurityProtocol.cs" />
+ <CompileItem Include="$(CommonPath)\System\Net\UriScheme.cs" />
<CompileItem Include="$(CommonPath)\System\Net\Http\HttpHandlerDefaults.cs" />
<CompileItem Include="$(CommonPath)\System\Net\Http\NoWriteNoSeekStreamContent.cs" />
<CompileItem Include="$(CommonPath)\System\Net\Http\WinHttpException.cs" />
// TODO: Issue #2165. Merge with similar code used in System.Net.Security move to Common/src//System/Net.
public static void BuildChain(
X509Certificate2 certificate,
+ X509Certificate2Collection remoteCertificateStore,
string hostName,
bool checkCertificateRevocationList,
out X509Chain chain,
// Authenticate the remote party: (e.g. when operating in client mode, authenticate the server).
chain.ChainPolicy.ApplicationPolicy.Add(s_serverAuthOid);
+ if (remoteCertificateStore.Count > 0)
+ {
+ if (WinHttpTraceHelper.IsTraceEnabled())
+ {
+ foreach (X509Certificate cert in remoteCertificateStore)
+ {
+ WinHttpTraceHelper.Trace("WinHttpCertificateHelper.BuildChain: adding cert to ExtraStore: {0}", cert.Subject);
+ }
+ }
+
+ chain.ChainPolicy.ExtraStore.AddRange(remoteCertificateStore);
+ }
+
if (!chain.Build(certificate))
{
sslPolicyErrors |= SslPolicyErrors.RemoteCertificateChainErrors;
throw WinHttpException.CreateExceptionUsingError(lastError);
}
-
+
+ // Get any additional certificates sent from the remote server during the TLS/SSL handshake.
+ X509Certificate2Collection remoteCertificateStore =
+ UnmanagedCertificateContext.GetRemoteCertificatesFromStoreContext(certHandle);
+
// Create a managed wrapper around the certificate handle. Since this results in duplicating
// the handle, we will close the original handle after creating the wrapper.
var serverCertificate = new X509Certificate2(certHandle);
{
WinHttpCertificateHelper.BuildChain(
serverCertificate,
+ remoteCertificateStore,
state.RequestMessage.RequestUri.Host,
state.CheckCertificateRevocationList,
out chain,
<Compile Include="$(CommonPath)\Interop\Windows\Interop.Libraries.cs">
<Link>Common\Interop\Windows\Interop.Libraries.cs</Link>
</Compile>
+ <Compile Include="$(CommonPath)\Interop\Windows\Crypt32\Interop.CertEnumCertificatesInStore.cs">
+ <Link>Common\Interop\Windows\Crypt32\Interop.CertEnumCertificatesInStore.cs</Link>
+ </Compile>
<Compile Include="$(CommonPath)\Interop\Windows\Crypt32\Interop.certificates_types.cs">
<Link>Common\Interop\Windows\Crypt32\Interop.certificates_types.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\Interop\Windows\Interop.HRESULT_FROM_WIN32.cs">
<Link>Common\Interop\Windows\Interop.HRESULT_FROM_WIN32.cs</Link>
</Compile>
+ <Compile Include="$(CommonPath)\Interop\Windows\SChannel\UnmanagedCertificateContext.IntPtr.cs">
+ <Link>Common\Interop\Windows\SChannel\UnmanagedCertificateContext.IntPtr.cs</Link>
+ </Compile>
<Compile Include="$(CommonPath)\Interop\Windows\winhttp\Interop.SafeWinHttpHandle.cs">
<Link>Common\Interop\Windows\winhttp\Interop.SafeWinHttpHandle.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\System\Net\HttpVersionInternal.cs">
<Link>Common\System\Net\HttpVersionInternal.cs</Link>
</Compile>
+ <Compile Include="$(CommonPath)\System\Net\Logging\NetEventSource.Common.cs">
+ <Link>Common\System\Net\Logging\NetEventSource.Common.cs</Link>
+ </Compile>
<Compile Include="$(CommonPath)\System\Net\UriScheme.cs">
<Link>Common\System\Net\UriScheme.cs</Link>
</Compile>
<Compile Include="$(CommonPath)\Interop\Windows\SChannel\UnmanagedCertificateContext.cs">
<Link>Common\Interop\Windows\SChannel\UnmanagedCertificateContext.cs</Link>
</Compile>
+ <Compile Include="$(CommonPath)\Interop\Windows\SChannel\UnmanagedCertificateContext.IntPtr.cs">
+ <Link>Common\Interop\Windows\SChannel\UnmanagedCertificateContext.IntPtr.cs</Link>
+ </Compile>
<Compile Include="$(CommonPath)\Interop\Windows\sspicli\SecPkgContext_Bindings.cs">
<Link>Common\Interop\Windows\sspicli\SecPkgContext_Bindings.cs</Link>
</Compile>