未验证 提交 388dd6c5 编写于 作者: T Tomas Weinfurt 提交者: GitHub

Add CertificateChainPolicy to ssl options

Co-authored-by: NJeremy Barton <jbarton@microsoft.com>
上级 5615b56d
......@@ -207,6 +207,7 @@ public partial class SslClientAuthenticationOptions
public System.Net.Security.LocalCertificateSelectionCallback? LocalCertificateSelectionCallback { get { throw null; } set { } }
public System.Net.Security.RemoteCertificateValidationCallback? RemoteCertificateValidationCallback { get { throw null; } set { } }
public string? TargetHost { get { throw null; } set { } }
public System.Security.Cryptography.X509Certificates.X509ChainPolicy? CertificateChainPolicy { get { throw null; } set { } }
}
public readonly partial struct SslClientHelloInfo
{
......@@ -229,6 +230,7 @@ public partial class SslServerAuthenticationOptions
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 System.Security.Cryptography.X509Certificates.X509ChainPolicy? CertificateChainPolicy { get { throw null; } set { } }
}
public partial class SslStream : System.Net.Security.AuthenticatedStream
{
......
......@@ -44,7 +44,8 @@ internal static partial class CertificateValidationPal
private static X509Certificate2? GetRemoteCertificate(
SafeDeleteContext? securityContext,
bool retrieveChainCertificates,
ref X509Chain? chain)
ref X509Chain? chain,
X509ChainPolicy? chainPolicy)
{
SafeSslHandle? sslContext = ((SafeDeleteSslContext?)securityContext)?.SslContext;
if (sslContext == null)
......@@ -65,6 +66,10 @@ internal static partial class CertificateValidationPal
else
{
chain ??= new X509Chain();
if (chainPolicy != null)
{
chain.ChainPolicy = chainPolicy;
}
IntPtr[]? ptrs = Interop.AndroidCrypto.SSLStreamGetPeerCertificates(sslContext);
if (ptrs != null && ptrs.Length > 0)
{
......
......@@ -50,7 +50,8 @@ internal static partial class CertificateValidationPal
private static X509Certificate2? GetRemoteCertificate(
SafeDeleteContext? securityContext,
bool retrieveChainCertificates,
ref X509Chain? chain)
ref X509Chain? chain,
X509ChainPolicy? chainPolicy)
{
if (securityContext == null)
{
......@@ -73,6 +74,10 @@ internal static partial class CertificateValidationPal
if (retrieveChainCertificates)
{
chain ??= new X509Chain();
if (chainPolicy != null)
{
chain.ChainPolicy = chainPolicy;
}
for (int i = 0; i < chainSize; i++)
{
......
......@@ -24,7 +24,11 @@ internal static partial class CertificateValidationPal
//
// Extracts a remote certificate upon request.
//
private static X509Certificate2? GetRemoteCertificate(SafeDeleteContext? securityContext, bool retrieveChainCertificates, ref X509Chain? chain)
private static X509Certificate2? GetRemoteCertificate(
SafeDeleteContext? securityContext,
bool retrieveChainCertificates,
ref X509Chain? chain,
X509ChainPolicy? chainPolicy)
{
bool gotReference = false;
......@@ -48,6 +52,10 @@ internal static partial class CertificateValidationPal
if (retrieveChainCertificates)
{
chain ??= new X509Chain();
if (chainPolicy != null)
{
chain.ChainPolicy = chainPolicy;
}
using (SafeSharedX509StackHandle chainStack =
Interop.OpenSsl.GetPeerCertificateChain(((SafeDeleteSslContext)securityContext).SslContext))
......
......@@ -29,7 +29,10 @@ internal static partial class CertificateValidationPal
//
private static X509Certificate2? GetRemoteCertificate(
SafeDeleteContext? securityContext, bool retrieveChainCertificates, ref X509Chain? chain)
SafeDeleteContext? securityContext,
bool retrieveChainCertificates,
ref X509Chain? chain,
X509ChainPolicy? chainPolicy)
{
if (securityContext == null)
{
......@@ -67,6 +70,10 @@ internal static partial class CertificateValidationPal
if (retrieveChainCertificates)
{
chain ??= new X509Chain();
if (chainPolicy != null)
{
chain.ChainPolicy = chainPolicy;
}
UnmanagedCertificateContext.GetRemoteCertificatesFromStoreContext(remoteContext, chain.ChainPolicy.ExtraStore);
}
......
......@@ -19,10 +19,10 @@ internal static partial class CertificateValidationPal
private static X509Chain? s_chain;
internal static X509Certificate2? GetRemoteCertificate(SafeDeleteContext? securityContext) =>
GetRemoteCertificate(securityContext, retrieveChainCertificates: false, ref s_chain);
GetRemoteCertificate(securityContext, retrieveChainCertificates: false, ref s_chain, null);
internal static X509Certificate2? GetRemoteCertificate(SafeDeleteContext? securityContext, ref X509Chain? chain) =>
GetRemoteCertificate(securityContext, retrieveChainCertificates: true, ref chain);
internal static X509Certificate2? GetRemoteCertificate(SafeDeleteContext? securityContext, ref X509Chain? chain, X509ChainPolicy? chainPolicy) =>
GetRemoteCertificate(securityContext, retrieveChainCertificates: true, ref chain, chainPolicy);
static partial void CheckSupportsStore(StoreLocation storeLocation, ref bool hasSupport);
......
......@@ -56,6 +56,11 @@ internal void UpdateOptions(SslClientAuthenticationOptions sslClientAuthenticati
CertificateRevocationCheckMode = sslClientAuthenticationOptions.CertificateRevocationCheckMode;
ClientCertificates = sslClientAuthenticationOptions.ClientCertificates;
CipherSuitesPolicy = sslClientAuthenticationOptions.CipherSuitesPolicy;
if (sslClientAuthenticationOptions.CertificateChainPolicy != null)
{
CertificateChainPolicy = sslClientAuthenticationOptions.CertificateChainPolicy.Clone();
}
}
internal void UpdateOptions(ServerOptionsSelectionCallback optionCallback, object? state)
......@@ -135,6 +140,11 @@ internal void UpdateOptions(SslServerAuthenticationOptions sslServerAuthenticati
{
ServerCertSelectionDelegate = sslServerAuthenticationOptions.ServerCertificateSelectionCallback;
}
if (sslServerAuthenticationOptions.CertificateChainPolicy != null)
{
CertificateChainPolicy = sslServerAuthenticationOptions.CertificateChainPolicy.Clone();
}
}
private static SslProtocols FilterOutIncompatibleSslProtocols(SslProtocols protocols)
......@@ -170,5 +180,6 @@ private static SslProtocols FilterOutIncompatibleSslProtocols(SslProtocols proto
internal CipherSuitesPolicy? CipherSuitesPolicy { get; set; }
internal object? UserState { get; set; }
internal ServerOptionsSelectionCallback? ServerOptionDelegate { get; set; }
internal X509ChainPolicy? CertificateChainPolicy { get; set; }
}
}
......@@ -73,5 +73,13 @@ public SslProtocols EnabledSslProtocols
/// Use extreme caution when changing this setting.
/// </summary>
public CipherSuitesPolicy? CipherSuitesPolicy { get; set; }
/// <summary>
/// Gets or sets an optional customized policy for remote certificate
/// validation. If not <see langword="null"/>,
/// <see cref="CertificateRevocationCheckMode"/> and <see cref="SslCertificateTrust"/>
/// are ignored.
/// </summary>
public X509ChainPolicy? CertificateChainPolicy { get; set; }
}
}
......@@ -74,5 +74,13 @@ public EncryptionPolicy EncryptionPolicy
/// Use extreme caution when changing this setting.
/// </summary>
public CipherSuitesPolicy? CipherSuitesPolicy { get; set; }
/// <summary>
/// Gets or sets an optional customized policy for remote certificate
/// validation. If not <see langword="null"/>,
/// <see cref="CertificateRevocationCheckMode"/> and <see cref="SslCertificateTrust"/>
/// are ignored.
/// </summary>
public X509ChainPolicy? CertificateChainPolicy { get; set; }
}
}
......@@ -936,7 +936,7 @@ internal bool VerifyRemoteCertificate(RemoteCertificateValidationCallback? remot
try
{
X509Certificate2? certificate = CertificateValidationPal.GetRemoteCertificate(_securityContext, ref chain);
X509Certificate2? certificate = CertificateValidationPal.GetRemoteCertificate(_securityContext, ref chain, _sslAuthenticationOptions.CertificateChainPolicy);
if (_remoteCertificate != null && certificate != null &&
certificate.RawDataMemory.Span.SequenceEqual(_remoteCertificate.RawDataMemory.Span))
{
......@@ -955,25 +955,37 @@ internal bool VerifyRemoteCertificate(RemoteCertificateValidationCallback? remot
else
{
chain ??= new X509Chain();
chain.ChainPolicy.RevocationMode = _sslAuthenticationOptions.CertificateRevocationCheckMode;
chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot;
// Authenticate the remote party: (e.g. when operating in server mode, authenticate the client).
chain.ChainPolicy.ApplicationPolicy.Add(_sslAuthenticationOptions.IsServer ? s_clientAuthOid : s_serverAuthOid);
if (trust != null)
if (_sslAuthenticationOptions.CertificateChainPolicy != null)
{
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
if (trust._store != null)
{
chain.ChainPolicy.CustomTrustStore.AddRange(trust._store.Certificates);
}
if (trust._trustList != null)
chain.ChainPolicy = _sslAuthenticationOptions.CertificateChainPolicy;
}
else
{
chain.ChainPolicy.RevocationMode = _sslAuthenticationOptions.CertificateRevocationCheckMode;
chain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot;
if (trust != null)
{
chain.ChainPolicy.CustomTrustStore.AddRange(trust._trustList);
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);
}
}
}
// set ApplicationPolicy unless already provided.
if (chain.ChainPolicy.ApplicationPolicy.Count == 0)
{
// Authenticate the remote party: (e.g. when operating in server mode, authenticate the client).
chain.ChainPolicy.ApplicationPolicy.Add(_sslAuthenticationOptions.IsServer ? s_clientAuthOid : s_serverAuthOid);
}
sslPolicyErrors |= CertificateValidationPal.VerifyCertificateProperties(
_securityContext!,
chain,
......
......@@ -49,6 +49,7 @@ public async Task ClientOptions_ServerOptions_NotMutatedDuringAuthentication()
#pragma warning restore SYSLIB0040
RemoteCertificateValidationCallback serverRemoteCallback = new RemoteCertificateValidationCallback(delegate { return true; });
SslStreamCertificateContext certificateContext = SslStreamCertificateContext.Create(serverCert, null, false);
X509ChainPolicy policy = new X509ChainPolicy();
(Stream stream1, Stream stream2) = TestHelper.GetConnectedStreams();
using (var client = new SslStream(stream1))
......@@ -65,7 +66,8 @@ public async Task ClientOptions_ServerOptions_NotMutatedDuringAuthentication()
EncryptionPolicy = clientEncryption,
LocalCertificateSelectionCallback = clientLocalCallback,
RemoteCertificateValidationCallback = clientRemoteCallback,
TargetHost = clientHost
TargetHost = clientHost,
CertificateChainPolicy = policy,
};
// Create server options
......@@ -80,6 +82,7 @@ public async Task ClientOptions_ServerOptions_NotMutatedDuringAuthentication()
RemoteCertificateValidationCallback = serverRemoteCallback,
ServerCertificate = serverCert,
ServerCertificateContext = certificateContext,
CertificateChainPolicy = policy,
};
// Authenticate
......@@ -99,6 +102,8 @@ public async Task ClientOptions_ServerOptions_NotMutatedDuringAuthentication()
Assert.Same(clientLocalCallback, clientOptions.LocalCertificateSelectionCallback);
Assert.Same(clientRemoteCallback, clientOptions.RemoteCertificateValidationCallback);
Assert.Same(clientHost, clientOptions.TargetHost);
Assert.Same(clientHost, clientOptions.TargetHost);
Assert.Same(policy, clientOptions.CertificateChainPolicy);
// Validate that server options are unchanged
Assert.Equal(serverAllowRenegotiation, serverOptions.AllowRenegotiation);
......@@ -111,6 +116,7 @@ public async Task ClientOptions_ServerOptions_NotMutatedDuringAuthentication()
Assert.Same(serverRemoteCallback, serverOptions.RemoteCertificateValidationCallback);
Assert.Same(serverCert, serverOptions.ServerCertificate);
Assert.Same(certificateContext, serverOptions.ServerCertificateContext);
Assert.Same(policy, serverOptions.CertificateChainPolicy);
}
}
}
......
......@@ -745,32 +745,23 @@ public async Task SslStream_TargetHostName_Succeeds(bool useEmptyName, bool useC
[InlineData(true)]
[InlineData(false)]
[ActiveIssue("https://github.com/dotnet/runtime/issues/68206", TestPlatforms.Android)]
public async Task SslStream_UntrustedCaWithCustomCallback_OK(bool usePartialChain)
public async Task SslStream_UntrustedCaWithCustomTrust_OK(bool usePartialChain)
{
int split = Random.Shared.Next(0, certificates.serverChain.Count - 1);
var clientOptions = new SslClientAuthenticationOptions() { TargetHost = "localhost" };
clientOptions.RemoteCertificateValidationCallback =
(sender, certificate, chain, sslPolicyErrors) =>
clientOptions.CertificateChainPolicy = new X509ChainPolicy() { RevocationMode = X509RevocationMode.NoCheck,
TrustMode = X509ChainTrustMode.CustomRootTrust };
clientOptions.CertificateChainPolicy.CustomTrustStore.Add(certificates.serverChain[certificates.serverChain.Count - 1]);
// Add only one CA to verify that peer did send intermediate CA cert.
// In case of partial chain, we need to make missing certs available.
if (usePartialChain)
{
for (int i = split; i < certificates.serverChain.Count - 1; i++)
{
// add our custom root CA
chain.ChainPolicy.CustomTrustStore.Add(certificates.serverChain[certificates.serverChain.Count - 1]);
chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
// Add only one CA to verify that peer did send intermediate CA cert.
// In case of partial chain, we need to make missing certs available.
if (usePartialChain)
{
for (int i = split; i < certificates.serverChain.Count - 1; i++)
{
chain.ChainPolicy.ExtraStore.Add(certificates.serverChain[i]);
}
}
bool result = chain.Build((X509Certificate2)certificate);
Assert.True(result);
return result;
};
clientOptions.CertificateChainPolicy.ExtraStore.Add(certificates.serverChain[i]);
}
}
var serverOptions = new SslServerAuthenticationOptions();
X509Certificate2Collection serverChain;
......
......@@ -2916,6 +2916,8 @@ public sealed partial class X509ChainPolicy
public System.TimeSpan UrlRetrievalTimeout { get { throw null; } set { } }
public System.Security.Cryptography.X509Certificates.X509VerificationFlags VerificationFlags { get { throw null; } set { } }
public System.DateTime VerificationTime { get { throw null; } set { } }
public bool VerificationTimeIgnored { get { throw null; } set { } }
public System.Security.Cryptography.X509Certificates.X509ChainPolicy Clone() { throw null; }
public void Reset() { }
}
public partial struct X509ChainStatus
......
......@@ -133,7 +133,7 @@ internal bool Build(X509Certificate2 certificate, bool throwOnException)
chainPolicy.RevocationFlag,
chainPolicy._customTrustStore,
chainPolicy.TrustMode,
chainPolicy.VerificationTime,
chainPolicy.VerificationTimeIgnored ? DateTime.Now : chainPolicy.VerificationTime,
chainPolicy.UrlRetrievalTimeout,
chainPolicy.DisableCertificateDownloads);
......
......@@ -9,6 +9,7 @@ public sealed class X509ChainPolicy
private X509RevocationFlag _revocationFlag;
private X509VerificationFlags _verificationFlags;
private X509ChainTrustMode _trustMode;
private DateTime _verificationTime;
internal OidCollection? _applicationPolicy;
internal OidCollection? _certificatePolicy;
internal X509Certificate2Collection? _extraStore;
......@@ -30,6 +31,17 @@ public X509ChainPolicy()
/// </value>
public bool DisableCertificateDownloads { get; set; }
/// <summary>
/// Gets or sets a value that indicates whether the chain validation should use
/// <see cref="VerificationTime" /> or the current system time when building
/// an X.509 certificate chain.
/// </summary>
/// <value>
/// <see langword="true" /> to ignore <see cref="VerificationTime"/> and use the current system time; otherwise <see langword="false"/>.
/// The default is <see langword="true" />.
/// </value>
public bool VerificationTimeIgnored { get; set; }
public OidCollection ApplicationPolicy => _applicationPolicy ??= new OidCollection();
public OidCollection CertificatePolicy => _certificatePolicy ??= new OidCollection();
......@@ -94,7 +106,15 @@ public X509ChainTrustMode TrustMode
}
}
public DateTime VerificationTime { get; set; }
public DateTime VerificationTime
{
get => _verificationTime;
set
{
_verificationTime = value;
VerificationTimeIgnored = false;
}
}
public TimeSpan UrlRetrievalTimeout { get; set; }
......@@ -109,8 +129,52 @@ public void Reset()
_revocationFlag = X509RevocationFlag.ExcludeRoot;
_verificationFlags = X509VerificationFlags.NoFlag;
_trustMode = X509ChainTrustMode.System;
VerificationTime = DateTime.Now;
_verificationTime = DateTime.Now;
VerificationTimeIgnored = true;
UrlRetrievalTimeout = TimeSpan.Zero; // default timeout
}
public X509ChainPolicy Clone()
{
X509ChainPolicy clone = new X509ChainPolicy
{
DisableCertificateDownloads = DisableCertificateDownloads,
_revocationMode = _revocationMode,
_revocationFlag = _revocationFlag,
_verificationFlags = _verificationFlags,
_trustMode = _trustMode,
_verificationTime = _verificationTime,
VerificationTimeIgnored = VerificationTimeIgnored,
UrlRetrievalTimeout = UrlRetrievalTimeout,
};
if (_applicationPolicy?.Count > 0)
{
foreach (var item in _applicationPolicy)
{
clone.ApplicationPolicy.Add(item);
}
}
if (_certificatePolicy?.Count > 0)
{
foreach (var item in _certificatePolicy)
{
clone.CertificatePolicy.Add(item);
}
}
if (_customTrustStore?.Count > 0)
{
clone.CustomTrustStore.AddRange(_customTrustStore);
}
if (_extraStore?.Count > 0)
{
clone.ExtraStore.AddRange(_extraStore);
}
return clone;
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册