diff --git a/src/Security/Authentication/Certificate/src/CertificateAuthenticationHandler.cs b/src/Security/Authentication/Certificate/src/CertificateAuthenticationHandler.cs
index 0acb8b70c49c..8d5281847077 100644
--- a/src/Security/Authentication/Certificate/src/CertificateAuthenticationHandler.cs
+++ b/src/Security/Authentication/Certificate/src/CertificateAuthenticationHandler.cs
@@ -212,6 +212,8 @@ private X509ChainPolicy BuildChainPolicy(X509Certificate2 certificate)
chainPolicy.TrustMode = Options.ChainTrustValidationMode;
}
+ chainPolicy.ExtraStore.AddRange(Options.AdditionalChainCertificates);
+
if (!Options.ValidateValidityPeriod)
{
chainPolicy.VerificationFlags |= X509VerificationFlags.IgnoreNotTimeValid;
diff --git a/src/Security/Authentication/Certificate/src/CertificateAuthenticationOptions.cs b/src/Security/Authentication/Certificate/src/CertificateAuthenticationOptions.cs
index d533e5a978f0..417ae86e669a 100644
--- a/src/Security/Authentication/Certificate/src/CertificateAuthenticationOptions.cs
+++ b/src/Security/Authentication/Certificate/src/CertificateAuthenticationOptions.cs
@@ -22,6 +22,11 @@ public class CertificateAuthenticationOptions : AuthenticationSchemeOptions
/// Collection of X509 certificates which are trusted components of the certificate chain.
///
public X509Certificate2Collection CustomTrustStore { get; set; } = new X509Certificate2Collection();
+
+ ///
+ /// Collection of X509 certificates which are added to the X509Chain.ChainPolicy.ExtraStore of the certificate chain.
+ ///
+ public X509Certificate2Collection AdditionalChainCertificates { get; set; } = new X509Certificate2Collection();
///
/// Method used to validate certificate chains against .
diff --git a/src/Security/Authentication/Certificate/src/PublicAPI.Unshipped.txt b/src/Security/Authentication/Certificate/src/PublicAPI.Unshipped.txt
index 7dc5c58110bf..ee3ca4344c56 100644
--- a/src/Security/Authentication/Certificate/src/PublicAPI.Unshipped.txt
+++ b/src/Security/Authentication/Certificate/src/PublicAPI.Unshipped.txt
@@ -1 +1,3 @@
#nullable enable
+Microsoft.AspNetCore.Authentication.Certificate.CertificateAuthenticationOptions.AdditionalChainCertificates.get -> System.Security.Cryptography.X509Certificates.X509Certificate2Collection!
+Microsoft.AspNetCore.Authentication.Certificate.CertificateAuthenticationOptions.AdditionalChainCertificates.set -> void
diff --git a/src/Security/Authentication/test/CertificateTests.cs b/src/Security/Authentication/test/CertificateTests.cs
index 7ece7586b8d4..fc1dd46e0b1c 100644
--- a/src/Security/Authentication/test/CertificateTests.cs
+++ b/src/Security/Authentication/test/CertificateTests.cs
@@ -319,6 +319,41 @@ public async Task VerifyValidClientCertWithTrustedChainAuthenticates()
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
+ [Fact]
+ public async Task VerifyValidClientCertWithAdditionalCertificatesAuthenticates()
+ {
+ using var host = await CreateHost(
+ new CertificateAuthenticationOptions
+ {
+ Events = successfulValidationEvents,
+ ChainTrustValidationMode = X509ChainTrustMode.CustomRootTrust,
+ CustomTrustStore = new X509Certificate2Collection() { Certificates.SelfSignedPrimaryRoot, },
+ AdditionalChainCertificates = new X509Certificate2Collection() { Certificates.SignedSecondaryRoot },
+ RevocationMode = X509RevocationMode.NoCheck
+ }, Certificates.SignedClient);
+
+ using var server = host.GetTestServer();
+ var response = await server.CreateClient().GetAsync("https://example.com/");
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+ }
+
+ [Fact]
+ public async Task VerifyValidClientCertFailsWithoutAdditionalCertificatesAuthenticates()
+ {
+ using var host = await CreateHost(
+ new CertificateAuthenticationOptions
+ {
+ Events = successfulValidationEvents,
+ ChainTrustValidationMode = X509ChainTrustMode.CustomRootTrust,
+ CustomTrustStore = new X509Certificate2Collection() { Certificates.SelfSignedPrimaryRoot, },
+ RevocationMode = X509RevocationMode.NoCheck
+ }, Certificates.SignedClient);
+
+ using var server = host.GetTestServer();
+ var response = await server.CreateClient().GetAsync("https://example.com/");
+ Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
+ }
+
[Fact]
public async Task VerifyHeaderIsUsedIfCertIsNotPresent()
{
@@ -570,7 +605,7 @@ public async Task VerifyValidationResultCanBeCached(bool cache)
Assert.Equal(Expected, name.First().Value);
count = responseAsXml.Elements("claim").Where(claim => claim.Attribute("Type").Value == "ValidationCount");
Assert.Single(count);
- var expected = cache ? "1" : "2";
+ var expected = cache ? "1" : "2";
Assert.Equal(expected, count.First().Value);
}
@@ -693,6 +728,7 @@ private static async Task CreateHost(
options.RevocationFlag = configureOptions.RevocationFlag;
options.RevocationMode = configureOptions.RevocationMode;
options.ValidateValidityPeriod = configureOptions.ValidateValidityPeriod;
+ options.AdditionalChainCertificates = configureOptions.AdditionalChainCertificates;
});
}
else