diff --git a/src/libraries/Common/src/Interop/Windows/SspiCli/Interop.SSPI.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/Interop.SSPI.cs
index b675f7f022473..1694ca539a86a 100644
--- a/src/libraries/Common/src/Interop/Windows/SspiCli/Interop.SSPI.cs
+++ b/src/libraries/Common/src/Interop/Windows/SspiCli/Interop.SSPI.cs
@@ -65,6 +65,7 @@ internal enum ContextAttribute
SECPKG_ATTR_LOCAL_CERT_CONTEXT = 0x54, // returns PCCERT_CONTEXT
SECPKG_ATTR_ROOT_STORE = 0x55, // returns HCERTCONTEXT to the root store
SECPKG_ATTR_ISSUER_LIST_EX = 0x59, // returns SecPkgContext_IssuerListInfoEx
+ SECPKG_ATTR_CLIENT_CERT_POLICY = 0x60, // sets SecPkgCred_ClientCertCtlPolicy
SECPKG_ATTR_CONNECTION_INFO = 0x5A, // returns SecPkgContext_ConnectionInfo
SECPKG_ATTR_CIPHER_INFO = 0x64, // returns SecPkgContext_CipherInfo
SECPKG_ATTR_UI_INFO = 0x68, // sets SEcPkgContext_UiInfo
@@ -315,6 +316,20 @@ public SecBufferDesc(int count)
}
}
+ [StructLayout(LayoutKind.Sequential)]
+ internal unsafe struct SecPkgCred_ClientCertPolicy
+ {
+ public uint dwFlags;
+ public Guid guidPolicyId;
+ public uint dwCertFlags;
+ public uint dwUrlRetrievalTimeout;
+ public BOOL fCheckRevocationFreshnessTime;
+ public uint dwRevocationFreshnessTime;
+ public BOOL fOmitUsageCheck;
+ public char* pwszSslCtlStoreName;
+ public char* pwszSslCtlIdentifier;
+ }
+
[DllImport(Interop.Libraries.SspiCli, ExactSpelling = true, SetLastError = true)]
internal static extern int EncryptMessage(
ref CredHandle contextHandle,
@@ -472,5 +487,12 @@ internal static extern SECURITY_STATUS SspiEncodeStringsAsAuthIdentity(
[In] string domainName,
[In] string password,
[Out] out SafeSspiAuthDataHandle authData);
+
+ [DllImport(Interop.Libraries.SspiCli, ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)]
+ internal static extern SECURITY_STATUS SetCredentialsAttributesW(
+ [In] ref CredHandle handlePtr,
+ [In] long ulAttribute,
+ [In] ref SecPkgCred_ClientCertPolicy pBuffer,
+ [In] long cbBuffer);
}
}
diff --git a/src/libraries/System.Net.Http/src/System.Net.Http.csproj b/src/libraries/System.Net.Http/src/System.Net.Http.csproj
index 0fee14684e2ea..dc5b421e19cad 100644
--- a/src/libraries/System.Net.Http/src/System.Net.Http.csproj
+++ b/src/libraries/System.Net.Http/src/System.Net.Http.csproj
@@ -386,10 +386,12 @@
+
+ Link="Common\Interop\Windows\Interop.UNICODE_STRING.cs" />
+
+
Setting an SNI hostname is not supported on this API level.
+
+ Only LocalMachine stores are supported on Windows.
+
+
+ Sending trust from collection is not supported on Windows.
+
diff --git a/src/libraries/System.Net.Security/src/System.Net.Security.csproj b/src/libraries/System.Net.Security/src/System.Net.Security.csproj
index 100115a0920a2..ca89fca29fa5b 100644
--- a/src/libraries/System.Net.Security/src/System.Net.Security.csproj
+++ b/src/libraries/System.Net.Security/src/System.Net.Security.csproj
@@ -4,6 +4,7 @@
$(NetCoreAppCurrent)-windows;$(NetCoreAppCurrent)-Unix;$(NetCoreAppCurrent)-Android;$(NetCoreAppCurrent)-OSX;$(NetCoreAppCurrent)-iOS;$(NetCoreAppCurrent)-tvOS;$(NetCoreAppCurrent)
$(DefineConstants);PRODUCT
+ $(DefineConstants);TARGET_WINDOWS
enable
@@ -29,6 +30,7 @@
+
@@ -156,6 +158,8 @@
+
buffer, out int outputOffset, out
--*/
//This method validates a remote certificate.
- internal bool VerifyRemoteCertificate(RemoteCertificateValidationCallback? remoteCertValidationCallback, ref ProtocolToken? alertToken, out SslPolicyErrors sslPolicyErrors, out X509ChainStatusFlags chainStatus)
+ internal bool VerifyRemoteCertificate(RemoteCertificateValidationCallback? remoteCertValidationCallback, SslCertificateTrust? trust, ref ProtocolToken? alertToken, out SslPolicyErrors sslPolicyErrors, out X509ChainStatusFlags chainStatus)
{
sslPolicyErrors = SslPolicyErrors.None;
chainStatus = X509ChainStatusFlags.NoError;
@@ -965,6 +965,19 @@ internal bool VerifyRemoteCertificate(RemoteCertificateValidationCallback? remot
chain.ChainPolicy.ExtraStore.AddRange(remoteCertificateStore);
}
+ if (trust != null)
+ {
+ chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
+ if (trust._store != null)
+ {
+ chain.ChainPolicy.CustomTrustStore.AddRange(trust._store.Certificates);
+ }
+ if (trust._trustList != null)
+ {
+ chain.ChainPolicy.CustomTrustStore.AddRange(trust._trustList);
+ }
+ }
+
sslPolicyErrors |= CertificateValidationPal.VerifyCertificateProperties(
_securityContext!,
chain,
diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslCertificateTrust.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslCertificateTrust.cs
new file mode 100644
index 0000000000000..982d206c85935
--- /dev/null
+++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslCertificateTrust.cs
@@ -0,0 +1,64 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Runtime.Versioning;
+using System.Security.Cryptography.X509Certificates;
+
+namespace System.Net.Security
+{
+ public sealed class SslCertificateTrust
+ {
+ internal X509Store? _store;
+ internal X509Certificate2Collection? _trustList;
+ internal bool _sendTrustInHandshake;
+
+ public static SslCertificateTrust CreateForX509Store(X509Store store, bool sendTrustInHandshake = false)
+ {
+
+#if TARGET_WINDOWS
+ if (sendTrustInHandshake && store.Location != StoreLocation.LocalMachine)
+ {
+ throw new PlatformNotSupportedException(SR.net_ssl_trust_store);
+ }
+#else
+ if (sendTrustInHandshake)
+ {
+ // to be removed when implemented.
+ throw new PlatformNotSupportedException("Not supported yet.");
+ }
+#endif
+ if (!store.IsOpen)
+ {
+ store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
+ }
+
+ var trust = new SslCertificateTrust();
+ trust._store = store;
+ trust._sendTrustInHandshake = sendTrustInHandshake;
+ return trust;
+ }
+
+ [UnsupportedOSPlatform("windows")]
+ public static SslCertificateTrust CreateForX509Collection(X509Certificate2Collection trustList, bool sendTrustInHandshake = false)
+ {
+ if (sendTrustInHandshake)
+ {
+ // to be removed when implemented.
+ throw new PlatformNotSupportedException("Not supported yet.");
+ }
+
+#if TARGET_WINDOWS
+ if (sendTrustInHandshake)
+ {
+ throw new PlatformNotSupportedException(SR.net_ssl_trust_collection);
+ }
+#endif
+ var trust = new SslCertificateTrust();
+ trust._trustList = trustList;
+ trust._sendTrustInHandshake = sendTrustInHandshake;
+ return trust;
+ }
+
+ private SslCertificateTrust() { }
+ }
+}
diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs
index 17858bb0e68b6..2ecc2a3932abf 100644
--- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs
+++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.Implementation.cs
@@ -664,7 +664,7 @@ private bool CompleteHandshake(ref ProtocolToken? alertToken, out SslPolicyError
return true;
}
- if (!_context.VerifyRemoteCertificate(_sslAuthenticationOptions!.CertValidationDelegate, ref alertToken, out sslPolicyErrors, out chainStatus))
+ if (!_context.VerifyRemoteCertificate(_sslAuthenticationOptions!.CertValidationDelegate, _sslAuthenticationOptions!.CertificateContext?.Trust, ref alertToken, out sslPolicyErrors, out chainStatus))
{
_handshakeCompleted = false;
return false;
diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.Linux.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.Linux.cs
index f5e9dd2114d61..facb437f02b42 100644
--- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.Linux.cs
+++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.Linux.cs
@@ -9,10 +9,11 @@ public partial class SslStreamCertificateContext
{
private const bool TrimRootCertificate = true;
- private SslStreamCertificateContext(X509Certificate2 target, X509Certificate2[] intermediates)
+ private SslStreamCertificateContext(X509Certificate2 target, X509Certificate2[] intermediates, SslCertificateTrust? trust)
{
Certificate = target;
IntermediateCertificates = intermediates;
+ Trust = trust;
}
internal static SslStreamCertificateContext Create(X509Certificate2 target) => Create(target, null);
diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.OSX.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.OSX.cs
index 4579f15407fde..9cef66806ac06 100644
--- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.OSX.cs
+++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.OSX.cs
@@ -10,16 +10,17 @@ public partial class SslStreamCertificateContext
// No leaf, no root.
private const bool TrimRootCertificate = true;
- private SslStreamCertificateContext(X509Certificate2 target, X509Certificate2[] intermediates)
+ private SslStreamCertificateContext(X509Certificate2 target, X509Certificate2[] intermediates, SslCertificateTrust? trust)
{
Certificate = target;
IntermediateCertificates = intermediates;
+ Trust = trust;
}
internal static SslStreamCertificateContext Create(X509Certificate2 target)
{
// On OSX we do not need to build chain unless we are asked for it.
- return new SslStreamCertificateContext(target, Array.Empty());
+ return new SslStreamCertificateContext(target, Array.Empty(), null);
}
}
}
diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.Windows.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.Windows.cs
index fcb84b2dda6a0..699a5c8c01239 100644
--- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.Windows.cs
+++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.Windows.cs
@@ -13,10 +13,10 @@ public partial class SslStreamCertificateContext
internal static SslStreamCertificateContext Create(X509Certificate2 target)
{
// On Windows we do not need to build chain unless we are asked for it.
- return new SslStreamCertificateContext(target, Array.Empty());
+ return new SslStreamCertificateContext(target, Array.Empty(), null);
}
- private SslStreamCertificateContext(X509Certificate2 target, X509Certificate2[] intermediates)
+ private SslStreamCertificateContext(X509Certificate2 target, X509Certificate2[] intermediates, SslCertificateTrust? trust)
{
if (intermediates.Length > 0)
{
@@ -103,6 +103,7 @@ private SslStreamCertificateContext(X509Certificate2 target, X509Certificate2[]
Certificate = target;
IntermediateCertificates = intermediates;
+ Trust = trust;
}
}
}
diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.cs
index 4ae127aadcc15..c585d7e033de0 100644
--- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.cs
+++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.ComponentModel;
using System.Security.Cryptography.X509Certificates;
namespace System.Net.Security
@@ -9,8 +10,15 @@ public partial class SslStreamCertificateContext
{
internal readonly X509Certificate2 Certificate;
internal readonly X509Certificate2[] IntermediateCertificates;
+ internal readonly SslCertificateTrust? Trust;
- public static SslStreamCertificateContext Create(X509Certificate2 target, X509Certificate2Collection? additionalCertificates, bool offline = false)
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static SslStreamCertificateContext Create(X509Certificate2 target, X509Certificate2Collection? additionalCertificates, bool offline)
+ {
+ return Create(target, additionalCertificates, offline, null);
+ }
+
+ public static SslStreamCertificateContext Create(X509Certificate2 target, X509Certificate2Collection? additionalCertificates, bool offline = false, SslCertificateTrust? trust = null)
{
if (!target.HasPrivateKey)
{
@@ -81,13 +89,12 @@ public static SslStreamCertificateContext Create(X509Certificate2 target, X509Ce
}
}
- return new SslStreamCertificateContext(target, intermediates);
+ return new SslStreamCertificateContext(target, intermediates, trust);
}
internal SslStreamCertificateContext Duplicate()
{
- return new SslStreamCertificateContext(new X509Certificate2(Certificate), IntermediateCertificates);
-
+ return new SslStreamCertificateContext(new X509Certificate2(Certificate), IntermediateCertificates, Trust);
}
}
}
diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs
index 4060296c4ed87..29ab3bed0ee8c 100644
--- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs
+++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs
@@ -4,11 +4,13 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
+using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Authentication;
using System.Security.Authentication.ExtendedProtection;
using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
+using System.Text;
using Microsoft.Win32.SafeHandles;
namespace System.Net.Security
@@ -121,14 +123,41 @@ public static SecurityStatusPal Renegotiate(ref SafeFreeCredentials? credentials
public static SafeFreeCredentials AcquireCredentialsHandle(SslStreamCertificateContext? certificateContext, SslProtocols protocols, EncryptionPolicy policy, bool isServer)
{
// New crypto API supports TLS1.3 but it does not allow to force NULL encryption.
- return !UseNewCryptoApi || policy == EncryptionPolicy.NoEncryption ?
+ SafeFreeCredentials cred = !UseNewCryptoApi || policy == EncryptionPolicy.NoEncryption ?
AcquireCredentialsHandleSchannelCred(certificateContext?.Certificate, protocols, policy, isServer) :
AcquireCredentialsHandleSchCredentials(certificateContext?.Certificate, protocols, policy, isServer);
+ if (certificateContext != null && certificateContext.Trust != null && certificateContext.Trust._sendTrustInHandshake)
+ {
+ AttachCertificateStore(cred, certificateContext.Trust._store!);
+ }
+
+ return cred;
+ }
+
+ private static unsafe void AttachCertificateStore(SafeFreeCredentials cred, X509Store store)
+ {
+ Interop.SspiCli.SecPkgCred_ClientCertPolicy clientCertPolicy = default;
+ fixed (char* ptr = store.Name)
+ {
+ clientCertPolicy.pwszSslCtlStoreName = ptr;
+ Interop.SECURITY_STATUS errorCode = Interop.SspiCli.SetCredentialsAttributesW(
+ ref cred._handle,
+ (long)Interop.SspiCli.ContextAttribute.SECPKG_ATTR_CLIENT_CERT_POLICY,
+ ref clientCertPolicy,
+ sizeof(Interop.SspiCli.SecPkgCred_ClientCertPolicy));
+
+ if (errorCode != Interop.SECURITY_STATUS.OK)
+ {
+ throw new Win32Exception((int)errorCode);
+ }
+ }
+
+ return;
}
// This is legacy crypto API used on .NET Framework and older Windows versions.
// It only supports TLS up to 1.2
- public static unsafe SafeFreeCredentials AcquireCredentialsHandleSchannelCred(X509Certificate? certificate, SslProtocols protocols, EncryptionPolicy policy, bool isServer)
+ public static unsafe SafeFreeCredentials AcquireCredentialsHandleSchannelCred(X509Certificate2? certificate, SslProtocols protocols, EncryptionPolicy policy, bool isServer)
{
int protocolFlags = GetProtocolFlagsFromSslProtocols(protocols, isServer);
Interop.SspiCli.SCHANNEL_CRED.Flags flags;
@@ -174,12 +203,11 @@ public static unsafe SafeFreeCredentials AcquireCredentialsHandleSchannelCred(X5
}
// This function uses new crypto API to support TLS 1.3 and beyond.
- public static unsafe SafeFreeCredentials AcquireCredentialsHandleSchCredentials(X509Certificate? certificate, SslProtocols protocols, EncryptionPolicy policy, bool isServer)
+ public static unsafe SafeFreeCredentials AcquireCredentialsHandleSchCredentials(X509Certificate2? certificate, SslProtocols protocols, EncryptionPolicy policy, bool isServer)
{
int protocolFlags = GetProtocolFlagsFromSslProtocols(protocols, isServer);
Interop.SspiCli.SCH_CREDENTIALS.Flags flags;
Interop.SspiCli.CredentialUse direction;
-
if (isServer)
{
direction = Interop.SspiCli.CredentialUse.SECPKG_CRED_INBOUND;
@@ -215,7 +243,6 @@ public static unsafe SafeFreeCredentials AcquireCredentialsHandleSchCredentials(
Interop.SspiCli.SCH_CREDENTIALS credential = default;
credential.dwVersion = Interop.SspiCli.SCH_CREDENTIALS.CurrentVersion;
credential.dwFlags = flags;
-
Interop.Crypt32.CERT_CONTEXT *certificateHandle = null;
if (certificate != null)
{
diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamCredentialCacheTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamCredentialCacheTest.cs
index e2e8314249d36..f135095476320 100644
--- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamCredentialCacheTest.cs
+++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamCredentialCacheTest.cs
@@ -28,15 +28,13 @@ public async Task SslStream_SameCertUsedForClientAndServer_Ok()
X509Certificate2Collection clientCertificateCollection =
new X509Certificate2Collection(certificate);
- var tasks = new Task[2];
-
- tasks[0] = server.AuthenticateAsServerAsync(certificate, true, false);
- tasks[1] = client.AuthenticateAsClientAsync(
+ Task t1 = server.AuthenticateAsServerAsync(certificate, true, false);
+ Task t2 = client.AuthenticateAsClientAsync(
certificate.GetNameInfo(X509NameType.SimpleName, false),
clientCertificateCollection, false);
- await Task.WhenAll(tasks).WaitAsync(TestConfiguration.PassingTestTimeout);
+ await TestConfiguration.WhenAllOrAnyFailedWithTimeout(t1, t2);
if (!PlatformDetection.IsWindows7 ||
Capability.IsTrustedRootCertificateInstalled())