diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs
index e522e7ee6dff62..52b1a5b1dbf474 100644
--- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs
+++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs
@@ -199,42 +199,11 @@ internal static SafeSslHandle AllocateSslContext(SslProtocols protocols, SafeX50
}
}
- if (hasCertificateAndKey)
+ if (sslAuthenticationOptions.CertificateContext != null && sslAuthenticationOptions.CertificateContext.IntermediateCertificates.Length > 0)
{
- bool hasCertReference = false;
- try
+ if (!Ssl.AddExtraChainCertificates(context, sslAuthenticationOptions.CertificateContext!.IntermediateCertificates))
{
- certHandle!.DangerousAddRef(ref hasCertReference);
- using (X509Certificate2 cert = new X509Certificate2(certHandle.DangerousGetHandle()))
- {
- X509Chain? chain = null;
- try
- {
- chain = TLSCertificateExtensions.BuildNewChain(cert, includeClientApplicationPolicy: false);
- if (chain != null && !Ssl.AddExtraChainCertificates(context, chain))
- {
- throw CreateSslException(SR.net_ssl_use_cert_failed);
- }
- }
- finally
- {
- if (chain != null)
- {
- int elementsCount = chain.ChainElements.Count;
- for (int i = 0; i < elementsCount; i++)
- {
- chain.ChainElements[i].Certificate!.Dispose();
- }
-
- chain.Dispose();
- }
- }
- }
- }
- finally
- {
- if (hasCertReference)
- certHandle!.DangerousRelease();
+ throw CreateSslException(SR.net_ssl_use_cert_failed);
}
}
diff --git a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Ssl.cs b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Ssl.cs
index b7edaf3919684b..81799ae6e0946e 100644
--- a/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Ssl.cs
+++ b/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.Ssl.cs
@@ -193,6 +193,25 @@ internal static bool AddExtraChainCertificates(SafeSslHandle sslContext, X509Cha
return true;
}
+ internal static bool AddExtraChainCertificates(SafeSslHandle sslContext, X509Certificate2[] chain)
+ {
+ // send pre-computed list of intermediates.
+ for (int i = 0; i < chain.Length; i++)
+ {
+ SafeX509Handle dupCertHandle = Crypto.X509UpRef(chain[i].Handle);
+ Crypto.CheckValidOpenSslHandle(dupCertHandle);
+ if (!SslAddExtraChainCert(sslContext, dupCertHandle))
+ {
+ Crypto.ErrClearError();
+ dupCertHandle.Dispose(); // we still own the safe handle; clean it up
+ return false;
+ }
+ dupCertHandle.SetHandleAsInvalid(); // ownership has been transferred to sslHandle; do not free via this safe handle
+ }
+
+ return true;
+ }
+
internal static class SslMethods
{
internal static readonly IntPtr SSLv23_method = SslV2_3Method();
diff --git a/src/libraries/System.Net.Security/ref/System.Net.Security.cs b/src/libraries/System.Net.Security/ref/System.Net.Security.cs
index d657bb1fd59d79..c47277547b74b3 100644
--- a/src/libraries/System.Net.Security/ref/System.Net.Security.cs
+++ b/src/libraries/System.Net.Security/ref/System.Net.Security.cs
@@ -124,6 +124,11 @@ public enum ProtectionLevel
public static bool operator !=(System.Net.Security.SslApplicationProtocol left, System.Net.Security.SslApplicationProtocol right) { throw null; }
public override string ToString() { throw null; }
}
+ public sealed partial class SslStreamCertificateContext
+ {
+ internal SslStreamCertificateContext() { throw null; }
+ public static SslStreamCertificateContext Create(System.Security.Cryptography.X509Certificates.X509Certificate2 target, System.Security.Cryptography.X509Certificates.X509Certificate2Collection? additionalCertificates, bool offline = false) { throw null; }
+ }
public partial class SslClientAuthenticationOptions
{
public SslClientAuthenticationOptions() { }
@@ -150,6 +155,7 @@ public SslServerAuthenticationOptions() { }
public System.Net.Security.EncryptionPolicy EncryptionPolicy { get { throw null; } set { } }
public System.Net.Security.RemoteCertificateValidationCallback? RemoteCertificateValidationCallback { get { throw null; } set { } }
public System.Security.Cryptography.X509Certificates.X509Certificate? ServerCertificate { get { throw null; } set { } }
+ public System.Net.Security.SslStreamCertificateContext? ServerCertificateContext { get { throw null; } set { } }
public System.Net.Security.ServerCertificateSelectionCallback? ServerCertificateSelectionCallback { get { throw null; } set { } }
}
public partial class SslStream : System.Net.Security.AuthenticatedStream
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 a4a3356a8d200e..7827fa79c8a194 100644
--- a/src/libraries/System.Net.Security/src/System.Net.Security.csproj
+++ b/src/libraries/System.Net.Security/src/System.Net.Security.csproj
@@ -28,6 +28,7 @@
+
@@ -125,6 +126,7 @@
+
@@ -247,6 +249,7 @@
+
@@ -328,6 +331,7 @@
+
diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NetEventSource.Security.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NetEventSource.Security.cs
index e21c04c8698acc..8de9ebe840c3e8 100644
--- a/src/libraries/System.Net.Security/src/System/Net/Security/NetEventSource.Security.cs
+++ b/src/libraries/System.Net.Security/src/System/Net/Security/NetEventSource.Security.cs
@@ -105,11 +105,11 @@ private unsafe void SecureChannelCtor(string sslStream, string hostname, int sec
WriteEvent(SecureChannelCtorId, sslStream, hostname, secureChannelHash, clientCertificatesCount, (int)encryptionPolicy);
[NonEvent]
- public void LocatingPrivateKey(X509Certificate x509Certificate, SecureChannel secureChannel)
+ public void LocatingPrivateKey(X509Certificate x509Certificate, object instance)
{
if (IsEnabled())
{
- LocatingPrivateKey(x509Certificate.ToString(true), GetHashCode(secureChannel));
+ LocatingPrivateKey(x509Certificate.ToString(true), GetHashCode(instance));
}
}
[Event(LocatingPrivateKeyId, Keywords = Keywords.Default, Level = EventLevel.Informational)]
@@ -117,11 +117,11 @@ private void LocatingPrivateKey(string x509Certificate, int secureChannelHash) =
WriteEvent(LocatingPrivateKeyId, x509Certificate, secureChannelHash);
[NonEvent]
- public void CertIsType2(SecureChannel secureChannel)
+ public void CertIsType2(object instance)
{
if (IsEnabled())
{
- CertIsType2(GetHashCode(secureChannel));
+ CertIsType2(GetHashCode(instance));
}
}
[Event(CertIsType2Id, Keywords = Keywords.Default, Level = EventLevel.Informational)]
@@ -129,11 +129,11 @@ private void CertIsType2(int secureChannelHash) =>
WriteEvent(CertIsType2Id, secureChannelHash);
[NonEvent]
- public void FoundCertInStore(bool serverMode, SecureChannel secureChannel)
+ public void FoundCertInStore(bool serverMode, object instance)
{
if (IsEnabled())
{
- FoundCertInStore(serverMode ? "LocalMachine" : "CurrentUser", GetHashCode(secureChannel));
+ FoundCertInStore(serverMode ? "LocalMachine" : "CurrentUser", GetHashCode(instance));
}
}
[Event(FoundCertInStoreId, Keywords = Keywords.Default, Level = EventLevel.Informational)]
@@ -141,11 +141,11 @@ private void FoundCertInStore(string store, int secureChannelHash) =>
WriteEvent(FoundCertInStoreId, store, secureChannelHash);
[NonEvent]
- public void NotFoundCertInStore(SecureChannel secureChannel)
+ public void NotFoundCertInStore(object instance)
{
if (IsEnabled())
{
- NotFoundCertInStore(GetHashCode(secureChannel));
+ NotFoundCertInStore(GetHashCode(instance));
}
}
[Event(NotFoundCertInStoreId, Keywords = Keywords.Default, Level = EventLevel.Informational)]
diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SecureChannel.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SecureChannel.cs
index 8b7e39f46c3c24..01064218ec9c54 100644
--- a/src/libraries/System.Net.Security/src/System/Net/Security/SecureChannel.cs
+++ b/src/libraries/System.Net.Security/src/System/Net/Security/SecureChannel.cs
@@ -77,7 +77,7 @@ internal X509Certificate? LocalServerCertificate
{
get
{
- return _sslAuthenticationOptions.ServerCertificate;
+ return _sslAuthenticationOptions.CertificateContext?.Certificate;
}
}
@@ -186,7 +186,7 @@ internal void Close()
// SECURITY: we open a private key container on behalf of the caller
// and we require the caller to have permission associated with that operation.
//
- private X509Certificate2? EnsurePrivateKey(X509Certificate certificate)
+ internal static X509Certificate2? FindCertificateWithPrivateKey(object instance, bool isServer, X509Certificate certificate)
{
if (certificate == null)
{
@@ -194,7 +194,7 @@ internal void Close()
}
if (NetEventSource.IsEnabled)
- NetEventSource.Log.LocatingPrivateKey(certificate, this);
+ NetEventSource.Log.LocatingPrivateKey(certificate, instance);
try
{
@@ -206,12 +206,12 @@ internal void Close()
if (certEx.HasPrivateKey)
{
if (NetEventSource.IsEnabled)
- NetEventSource.Log.CertIsType2(this);
+ NetEventSource.Log.CertIsType2(instance);
return certEx;
}
- if ((object)certificate != (object)certEx)
+ if (!object.ReferenceEquals(certificate, certEx))
{
certEx.Dispose();
}
@@ -222,26 +222,26 @@ internal void Close()
// ELSE Try the MY user and machine stores for private key check.
// For server side mode MY machine store takes priority.
- X509Store? store = CertificateValidationPal.EnsureStoreOpened(_sslAuthenticationOptions.IsServer);
+ X509Store? store = CertificateValidationPal.EnsureStoreOpened(isServer);
if (store != null)
{
collectionEx = store.Certificates.Find(X509FindType.FindByThumbprint, certHash, false);
if (collectionEx.Count > 0 && collectionEx[0].HasPrivateKey)
{
if (NetEventSource.IsEnabled)
- NetEventSource.Log.FoundCertInStore(_sslAuthenticationOptions.IsServer, this);
+ NetEventSource.Log.FoundCertInStore(isServer, instance);
return collectionEx[0];
}
}
- store = CertificateValidationPal.EnsureStoreOpened(!_sslAuthenticationOptions.IsServer);
+ store = CertificateValidationPal.EnsureStoreOpened(!isServer);
if (store != null)
{
collectionEx = store.Certificates.Find(X509FindType.FindByThumbprint, certHash, false);
if (collectionEx.Count > 0 && collectionEx[0].HasPrivateKey)
{
if (NetEventSource.IsEnabled)
- NetEventSource.Log.FoundCertInStore(_sslAuthenticationOptions.IsServer, this);
+ NetEventSource.Log.FoundCertInStore(!isServer, instance);
return collectionEx[0];
}
}
@@ -251,7 +251,7 @@ internal void Close()
}
if (NetEventSource.IsEnabled)
- NetEventSource.Log.NotFoundCertInStore(this);
+ NetEventSource.Log.NotFoundCertInStore(instance);
return null;
}
@@ -531,13 +531,13 @@ private bool AcquireClientCredentials(ref byte[]? thumbPrint)
//
// SECURITY: Accessing X509 cert Credential is disabled for semitrust.
// We no longer need to demand for unmanaged code permissions.
- // EnsurePrivateKey should do the right demand for us.
+ // FindCertificateWithPrivateKey should do the right demand for us.
if (filteredCerts != null)
{
for (int i = 0; i < filteredCerts.Count; ++i)
{
clientCertificate = filteredCerts[i];
- if ((selectedCert = EnsurePrivateKey(clientCertificate)) != null)
+ if ((selectedCert = FindCertificateWithPrivateKey(this, _sslAuthenticationOptions.IsServer, clientCertificate)) != null)
{
break;
}
@@ -608,10 +608,9 @@ private bool AcquireClientCredentials(ref byte[]? thumbPrint)
}
finally
{
- // An extra cert could have been created, dispose it now.
- if (selectedCert != null && (object?)clientCertificate != (object?)selectedCert)
+ if (selectedCert != null)
{
- selectedCert.Dispose();
+ _sslAuthenticationOptions.CertificateContext = SslStreamCertificateContext.Create(selectedCert);
}
}
@@ -631,84 +630,91 @@ private bool AcquireServerCredentials(ref byte[]? thumbPrint)
NetEventSource.Enter(this);
X509Certificate? localCertificate = null;
+ X509Certificate2? selectedCert = null;
bool cachedCred = false;
// There are three options for selecting the server certificate. When
// selecting which to use, we prioritize the new ServerCertSelectionDelegate
// API. If the new API isn't used we call LocalCertSelectionCallback (for compat
- // with .NET Framework), and if neither is set we fall back to using ServerCertificate.
+ // with .NET Framework), and if neither is set we fall back to using CertificateContext.
if (_sslAuthenticationOptions.ServerCertSelectionDelegate != null)
{
localCertificate = _sslAuthenticationOptions.ServerCertSelectionDelegate(_sslAuthenticationOptions.TargetHost);
if (localCertificate == null)
{
+ if (NetEventSource.IsEnabled)
+ NetEventSource.Error(this, $"ServerCertSelectionDelegate returned no certificaete for '{_sslAuthenticationOptions.TargetHost}'.");
throw new AuthenticationException(SR.net_ssl_io_no_server_cert);
}
+
if (NetEventSource.IsEnabled)
- NetEventSource.Info(this, "Use delegate selected Cert");
+ NetEventSource.Info(this, "ServerCertSelectionDelegate selected Cert");
}
else if (_sslAuthenticationOptions.CertSelectionDelegate != null)
{
X509CertificateCollection tempCollection = new X509CertificateCollection();
- tempCollection.Add(_sslAuthenticationOptions.ServerCertificate!);
+ tempCollection.Add(_sslAuthenticationOptions.CertificateContext!.Certificate!);
// We pass string.Empty here to maintain strict compatability with .NET Framework.
localCertificate = _sslAuthenticationOptions.CertSelectionDelegate(string.Empty, tempCollection, null, Array.Empty());
+ if (localCertificate == null)
+ {
+ if (NetEventSource.IsEnabled)
+ NetEventSource.Error(this, $"CertSelectionDelegate returned no certificaete for '{_sslAuthenticationOptions.TargetHost}'.");
+ throw new NotSupportedException(SR.net_ssl_io_no_server_cert);
+ }
+
if (NetEventSource.IsEnabled)
- NetEventSource.Info(this, "Use delegate selected Cert");
+ NetEventSource.Info(this, "CertSelectionDelegate selected Cert");
}
- else
+ else if (_sslAuthenticationOptions.CertificateContext != null)
{
- localCertificate = _sslAuthenticationOptions.ServerCertificate;
+ selectedCert = _sslAuthenticationOptions.CertificateContext.Certificate;
}
- if (localCertificate == null)
+ if (selectedCert == null)
{
- throw new NotSupportedException(SR.net_ssl_io_no_server_cert);
- }
+ // We will get here if vertificate was slected via legacy callback using X509Certificate
+ // Fail immediately if no certificate was given.
+ if (localCertificate == null)
+ {
+ if (NetEventSource.IsEnabled)
+ NetEventSource.Error(this, "Certiticate callback returned no certificaete.");
+ throw new NotSupportedException(SR.net_ssl_io_no_server_cert);
+ }
- // SECURITY: Accessing X509 cert Credential is disabled for semitrust.
- // We no longer need to demand for unmanaged code permissions.
- // EnsurePrivateKey should do the right demand for us.
- X509Certificate2? selectedCert = EnsurePrivateKey(localCertificate);
+ // SECURITY: Accessing X509 cert Credential is disabled for semitrust.
+ // We no longer need to demand for unmanaged code permissions.
+ // EnsurePrivateKey should do the right demand for us.
+ selectedCert = FindCertificateWithPrivateKey(this, _sslAuthenticationOptions.IsServer, localCertificate);
- if (selectedCert == null)
- {
- throw new NotSupportedException(SR.net_ssl_io_no_server_cert);
- }
+ if (selectedCert == null)
+ {
+ throw new NotSupportedException(SR.net_ssl_io_no_server_cert);
+ }
- if (!localCertificate.Equals(selectedCert))
- {
- NetEventSource.Fail(this, "'selectedCert' does not match 'localCertificate'.");
+ if (!localCertificate.Equals(selectedCert))
+ {
+ NetEventSource.Fail(this, "'selectedCert' does not match 'localCertificate'.");
+ }
+
+ _sslAuthenticationOptions.CertificateContext = SslStreamCertificateContext.Create(selectedCert);
}
//
// Note selectedCert is a safe ref possibly cloned from the user passed Cert object
//
byte[] guessedThumbPrint = selectedCert.GetCertHash();
- try
- {
- SafeFreeCredentials? cachedCredentialHandle = SslSessionsCache.TryCachedCredential(guessedThumbPrint, _sslAuthenticationOptions.EnabledSslProtocols, _sslAuthenticationOptions.IsServer, _sslAuthenticationOptions.EncryptionPolicy);
+ SafeFreeCredentials? cachedCredentialHandle = SslSessionsCache.TryCachedCredential(guessedThumbPrint, _sslAuthenticationOptions.EnabledSslProtocols, _sslAuthenticationOptions.IsServer, _sslAuthenticationOptions.EncryptionPolicy);
- if (cachedCredentialHandle != null)
- {
- _credentialsHandle = cachedCredentialHandle;
- _sslAuthenticationOptions.ServerCertificate = localCertificate;
- cachedCred = true;
- }
- else
- {
- _credentialsHandle = SslStreamPal.AcquireCredentialsHandle(selectedCert, _sslAuthenticationOptions.EnabledSslProtocols, _sslAuthenticationOptions.EncryptionPolicy, _sslAuthenticationOptions.IsServer);
- thumbPrint = guessedThumbPrint;
- _sslAuthenticationOptions.ServerCertificate = localCertificate;
- }
+ if (cachedCredentialHandle != null)
+ {
+ _credentialsHandle = cachedCredentialHandle;
+ cachedCred = true;
}
- finally
+ else
{
- // An extra cert could have been created, dispose it now.
- if ((object)localCertificate != (object)selectedCert)
- {
- selectedCert.Dispose();
- }
+ _credentialsHandle = SslStreamPal.AcquireCredentialsHandle(selectedCert, _sslAuthenticationOptions.EnabledSslProtocols, _sslAuthenticationOptions.EncryptionPolicy, _sslAuthenticationOptions.IsServer);
+ thumbPrint = guessedThumbPrint;
}
if (NetEventSource.IsEnabled)
diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs
index 4895d91c2e3967..90d5acfb9fd6f6 100644
--- a/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs
+++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslAuthenticationOptions.cs
@@ -47,9 +47,35 @@ internal SslAuthenticationOptions(SslServerAuthenticationOptions sslServerAuthen
TargetHost = string.Empty;
// Server specific options.
- CertificateRevocationCheckMode = sslServerAuthenticationOptions.CertificateRevocationCheckMode;
- ServerCertificate = sslServerAuthenticationOptions.ServerCertificate;
CipherSuitesPolicy = sslServerAuthenticationOptions.CipherSuitesPolicy;
+ CertificateRevocationCheckMode = sslServerAuthenticationOptions.CertificateRevocationCheckMode;
+
+ if (sslServerAuthenticationOptions.ServerCertificateContext != null)
+ {
+ CertificateContext = sslServerAuthenticationOptions.ServerCertificateContext;
+ }
+ else if (sslServerAuthenticationOptions.ServerCertificate != null)
+ {
+ X509Certificate2? certificateWithKey = sslServerAuthenticationOptions.ServerCertificate as X509Certificate2;
+
+ if (certificateWithKey != null && certificateWithKey.HasPrivateKey)
+ {
+ // given cert is X509Certificate2 with key. We can use it directly.
+ CertificateContext = SslStreamCertificateContext.Create(certificateWithKey, null);
+ }
+ else
+ {
+ // This is legacy fix-up. If the Certificate did not have key, we will search stores and we
+ // will try to find one with matching hash.
+ certificateWithKey = SecureChannel.FindCertificateWithPrivateKey(this, true, sslServerAuthenticationOptions.ServerCertificate);
+ if (certificateWithKey == null)
+ {
+ throw new AuthenticationException(SR.net_ssl_io_no_server_cert);
+ }
+
+ CertificateContext = SslStreamCertificateContext.Create(certificateWithKey);
+ }
+ }
}
private static SslProtocols FilterOutIncompatibleSslProtocols(SslProtocols protocols)
@@ -72,7 +98,7 @@ private static SslProtocols FilterOutIncompatibleSslProtocols(SslProtocols proto
internal X509CertificateCollection? ClientCertificates { get; set; }
internal List? ApplicationProtocols { get; }
internal bool IsServer { get; set; }
- internal X509Certificate? ServerCertificate { get; set; }
+ internal SslStreamCertificateContext? CertificateContext { get; set; }
internal SslProtocols EnabledSslProtocols { get; set; }
internal X509RevocationMode CertificateRevocationCheckMode { get; set; }
internal EncryptionPolicy EncryptionPolicy { get; set; }
diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslServerAuthenticationOptions.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslServerAuthenticationOptions.cs
index ca345ad4fe0355..756f9f45ba05b7 100644
--- a/src/libraries/System.Net.Security/src/System/Net/Security/SslServerAuthenticationOptions.cs
+++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslServerAuthenticationOptions.cs
@@ -31,6 +31,8 @@ public bool AllowRenegotiation
public X509Certificate? ServerCertificate { get; set; }
+ public SslStreamCertificateContext? ServerCertificateContext { get; set; }
+
public SslProtocols EnabledSslProtocols
{
get => _enabledSslProtocols;
diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.cs
index 61011fde371c8e..ca04e414735ee0 100644
--- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.cs
+++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStream.cs
@@ -177,12 +177,13 @@ private X509Certificate UserCertSelectionCallbackWrapper(string targetHost, X509
private SslAuthenticationOptions CreateAuthenticationOptions(SslServerAuthenticationOptions sslServerAuthenticationOptions)
{
- if (sslServerAuthenticationOptions.ServerCertificate == null && sslServerAuthenticationOptions.ServerCertificateSelectionCallback == null && _certSelectionDelegate == null)
+ if (sslServerAuthenticationOptions.ServerCertificate == null && sslServerAuthenticationOptions.ServerCertificateContext == null &&
+ sslServerAuthenticationOptions.ServerCertificateSelectionCallback == null && _certSelectionDelegate == null)
{
throw new ArgumentNullException(nameof(sslServerAuthenticationOptions.ServerCertificate));
}
- if ((sslServerAuthenticationOptions.ServerCertificate != null || _certSelectionDelegate != null) && sslServerAuthenticationOptions.ServerCertificateSelectionCallback != null)
+ if ((sslServerAuthenticationOptions.ServerCertificate != null || sslServerAuthenticationOptions.ServerCertificateContext != null || _certSelectionDelegate != null) && sslServerAuthenticationOptions.ServerCertificateSelectionCallback != null)
{
throw new InvalidOperationException(SR.Format(SR.net_conflicting_options, nameof(ServerCertificateSelectionCallback)));
}
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
new file mode 100644
index 00000000000000..f7fbe01ca03a6b
--- /dev/null
+++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.Linux.cs
@@ -0,0 +1,13 @@
+// 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.Security.Cryptography.X509Certificates;
+
+namespace System.Net.Security
+{
+ public partial class SslStreamCertificateContext
+ {
+ 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
new file mode 100644
index 00000000000000..9ab01c90397dc0
--- /dev/null
+++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.OSX.cs
@@ -0,0 +1,17 @@
+// 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.Security.Cryptography.X509Certificates;
+
+namespace System.Net.Security
+{
+ public partial class SslStreamCertificateContext
+ {
+ 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());
+ }
+ }
+}
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
new file mode 100644
index 00000000000000..6438f38da38e51
--- /dev/null
+++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.Windows.cs
@@ -0,0 +1,17 @@
+// 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.Security.Cryptography.X509Certificates;
+
+namespace System.Net.Security
+{
+ 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());
+ }
+ }
+}
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
new file mode 100644
index 00000000000000..f110c55afb694b
--- /dev/null
+++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamCertificateContext.cs
@@ -0,0 +1,80 @@
+// 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.Security.Cryptography.X509Certificates;
+
+namespace System.Net.Security
+{
+ public partial class SslStreamCertificateContext
+ {
+ internal readonly X509Certificate2 Certificate;
+ internal readonly X509Certificate2[] IntermediateCertificates;
+
+ public static SslStreamCertificateContext Create(X509Certificate2 target, X509Certificate2Collection? additionalCertificates, bool offline = false)
+ {
+ if (!target.HasPrivateKey)
+ {
+ throw new NotSupportedException(SR.net_ssl_io_no_server_cert);
+ }
+
+ X509Certificate2[] intermediates = Array.Empty();
+
+ using (X509Chain chain = new X509Chain())
+ {
+ if (additionalCertificates != null)
+ {
+ foreach (X509Certificate cert in additionalCertificates)
+ {
+ chain.ChainPolicy.ExtraStore.Add(cert);
+ }
+ }
+
+ chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllFlags;
+ chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
+ chain.ChainPolicy.DisableCertificateDownloads = offline;
+ chain.Build(target);
+
+ // No leaf, no root.
+ int count = chain.ChainElements.Count - 2;
+
+ foreach (X509ChainStatus status in chain.ChainStatus)
+ {
+ if (status.Status.HasFlag(X509ChainStatusFlags.PartialChain))
+ {
+ // The last cert isn't a root cert
+ count++;
+ break;
+ }
+ }
+
+ // Count can be zero for a self-signed certificate, or a cert issued directly from a root.
+ if (count > 0)
+ {
+ intermediates = new X509Certificate2[count];
+ for (int i = 0; i < count; i++)
+ {
+ intermediates[i] = chain.ChainElements[i + 1].Certificate;
+ }
+ }
+
+ // Dispose the copy of the target cert.
+ chain.ChainElements[0].Certificate.Dispose();
+
+ // Dispose the last cert, if we didn't include it.
+ for (int i = count + 1; i < chain.ChainElements.Count; i++)
+ {
+ chain.ChainElements[i].Certificate.Dispose();
+ }
+ }
+
+ return new SslStreamCertificateContext(target, intermediates);
+ }
+
+ private SslStreamCertificateContext(X509Certificate2 target, X509Certificate2[] intermediates)
+ {
+ Certificate = target;
+ IntermediateCertificates = intermediates;
+ }
+ }
+}
diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslAuthenticationOptionsTest.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslAuthenticationOptionsTest.cs
index af9e02ad03ed60..c9a0350d8d9a72 100644
--- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslAuthenticationOptionsTest.cs
+++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslAuthenticationOptionsTest.cs
@@ -43,6 +43,7 @@ public async Task ClientOptions_ServerOptions_NotMutatedDuringAuthentication()
SslProtocols serverSslProtocols = SslProtocols.Tls11 | SslProtocols.Tls12;
EncryptionPolicy serverEncryption = EncryptionPolicy.AllowNoEncryption;
RemoteCertificateValidationCallback serverRemoteCallback = new RemoteCertificateValidationCallback(delegate { return true; });
+ SslStreamCertificateContext certificateContext = SslStreamCertificateContext.Create(serverCert, null, false);
var network = new VirtualNetwork();
using (var client = new SslStream(new VirtualNetworkStream(network, isServer: false)))
@@ -72,7 +73,8 @@ public async Task ClientOptions_ServerOptions_NotMutatedDuringAuthentication()
EnabledSslProtocols = serverSslProtocols,
EncryptionPolicy = serverEncryption,
RemoteCertificateValidationCallback = serverRemoteCallback,
- ServerCertificate = serverCert
+ ServerCertificate = serverCert,
+ ServerCertificateContext = certificateContext,
};
// Authenticate
@@ -103,6 +105,7 @@ public async Task ClientOptions_ServerOptions_NotMutatedDuringAuthentication()
Assert.Equal(serverEncryption, serverOptions.EncryptionPolicy);
Assert.Same(serverRemoteCallback, serverOptions.RemoteCertificateValidationCallback);
Assert.Same(serverCert, serverOptions.ServerCertificate);
+ Assert.Same(certificateContext, serverOptions.ServerCertificateContext);
}
}
}
diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamAlpnTests.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamAlpnTests.cs
index c5284653585b78..bdbe2c40cc507d 100644
--- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamAlpnTests.cs
+++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamAlpnTests.cs
@@ -42,7 +42,7 @@ private async Task DoHandshakeWithOptions(SslStream clientSslStream, SslStream s
{
clientOptions.RemoteCertificateValidationCallback = AllowAnyServerCertificate;
clientOptions.TargetHost = certificate.GetNameInfo(X509NameType.SimpleName, false);
- serverOptions.ServerCertificate = certificate;
+ serverOptions.ServerCertificateContext = SslStreamCertificateContext.Create(certificate, null);
Task t1 = clientSslStream.AuthenticateAsClientAsync(TestAuthenticateAsync, clientOptions);
Task t2 = serverSslStream.AuthenticateAsServerAsync(TestAuthenticateAsync, serverOptions);
diff --git a/src/libraries/System.Net.Security/tests/UnitTests/Fakes/FakeSslStream.Implementation.cs b/src/libraries/System.Net.Security/tests/UnitTests/Fakes/FakeSslStream.Implementation.cs
index ccd294beb54ddd..65314424280d13 100644
--- a/src/libraries/System.Net.Security/tests/UnitTests/Fakes/FakeSslStream.Implementation.cs
+++ b/src/libraries/System.Net.Security/tests/UnitTests/Fakes/FakeSslStream.Implementation.cs
@@ -87,6 +87,11 @@ internal class SecureChannel
internal X509Certificate LocalClientCertificate => default;
internal X509RevocationMode CheckCertRevocationStatus => default;
internal ProtocolToken CreateShutdownToken() => default;
+
+ internal static X509Certificate2? FindCertificateWithPrivateKey(object instance, bool isServer, X509Certificate certificate)
+ {
+ return certificate as X509Certificate2;
+ }
}
internal class ProtocolToken
diff --git a/src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj b/src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj
index fd76bb91ceb23a..1afe1fcf5829c5 100644
--- a/src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj
+++ b/src/libraries/System.Net.Security/tests/UnitTests/System.Net.Security.Unit.Tests.csproj
@@ -46,6 +46,10 @@
Link="ProductionCode\System\Net\Security\SslApplicationProtocol.cs" />
+
+