I'm using following code to configure Saml2
public static void ConfigureSaml2(this IServiceCollection services, IConfiguration configuration)
{
services.Configure<Saml2Configuration>(configuration.GetSection(Saml2Section));
services.Configure<Saml2Configuration>(saml2Configuration =>
{
var signingCertificateName = configuration.GetValue<string>($"{Saml2Section}:{SigningCertificateName}");
var signatureCertificateName = configuration.GetValue<string>($"{Saml2Section}:{SignatureCertificateName}");
Configure(saml2Configuration, signingCertificateName, signatureCertificateName);
});
services.AddSaml2(slidingExpiration: true);
}
private static void Configure(Saml2Configuration saml2Configuration, string signingCertificateName, string signatureCertificateName)
{
saml2Configuration.SignAuthnRequest = true;
saml2Configuration.AllowedIssuer = saml2Configuration.SingleSignOnDestination.ToString();
saml2Configuration.SigningCertificate = CertificateUtil.Load(
StoreName.My, StoreLocation.CurrentUser, X509FindType.FindBySubjectDistinguishedName, signingCertificateName);
saml2Configuration.SignatureValidationCertificates.Add(
CertificateUtil.Load(StoreName.My, StoreLocation.CurrentUser, X509FindType.FindBySubjectDistinguishedName, signatureCertificateName));
saml2Configuration.AllowedAudienceUris.Add(saml2Configuration.Issuer);
saml2Configuration.CustomCertificateValidator = new Saml2CertificateValidator
{
CertificateValidationMode = saml2Configuration.CertificateValidationMode,
RevocationMode = saml2Configuration.RevocationMode,
TrustedStoreLocation = StoreLocation.CurrentUser
};
saml2Configuration.CertificateValidationMode = X509CertificateValidationMode.Custom;
}
Application is hosted on Azure. While this code worked without any problem in .NET 6, after upgrading my app to .NET8 I'm getting following error:
SecurityTokenValidationException: Invalid X509 certificate chain. Certificate name:'CN=xxx, O=xxx' and thumbprint:'xxx'. Chain Status:'A certificate chain could not be built to a trusted root authority.'..
ITfoxtec.Identity.Saml2.Util.Saml2CertificateValidator.ValidateChainTrust(X509Certificate2 certificate)
ITfoxtec.Identity.Saml2.Util.Saml2CertificateValidator.Validate(X509Certificate2 certificate)
ITfoxtec.Identity.Saml2.Saml2Request.ValidateXmlSignature(XmlElement xmlElement)
ITfoxtec.Identity.Saml2.Saml2Request.ValidateXmlSignature(SignatureValidation documentValidationResult)
ITfoxtec.Identity.Saml2.Saml2Request.Read(string xml, bool validate, bool detectReplayedTokens)
ITfoxtec.Identity.Saml2.Saml2Response.Read(string xml, bool validate, bool detectReplayedTokens)
ITfoxtec.Identity.Saml2.Saml2AuthnResponse.Read(string xml, bool validate, bool detectReplayedTokens)
ITfoxtec.Identity.Saml2.Saml2PostBinding.Read(HttpRequest request, Saml2Request saml2RequestResponse, string messageName, bool validate, bool detectReplayedTokens)
ITfoxtec.Identity.Saml2.Saml2PostBinding.UnbindInternal(HttpRequest request, Saml2Request saml2RequestResponse, string messageName)
ITfoxtec.Identity.Saml2.Saml2Binding.Unbind(HttpRequest request, Saml2Response saml2Response)
I've found out, that Saml2CertificateValidator uses StoreLocation.LocalMachine by default and it cannot be changed in the configuration. So I've tried to add following code to setup custom validator:
saml2Configuration.CustomCertificateValidator = new Saml2CertificateValidator
{
CertificateValidationMode = saml2Configuration.CertificateValidationMode,
RevocationMode = saml2Configuration.RevocationMode,
TrustedStoreLocation = StoreLocation.CurrentUser
};
saml2Configuration.CertificateValidationMode = X509CertificateValidationMode.Custom;
Nothing has changed. I'm still getting the same error.
Could anybody help? Why the same code works with .NET6, but not with .NET8?
Finally, I figured out that the reason was indeed a bug in the ITfoxtec library that was causing ChainTrust certificate validation was not performed at all. After upgrading the library, certificates are validated and the validation fails. The reason for this is that ITfoxtec validates the certificate against the Trust Store exclusively. The problem is that the Azure App Service does not allow you to upload certificates to the Trusted Root Store and therefore the validation always fails. The solution is to use the Custom Trust Store, but that is not supported in the ITfoxtec library. The solution is to implement a custom validator that will set the Custom Thrust Store. Something like this:
public class CustomSaml2CertificateValidator : X509CertificateValidator
{
private X509Certificate2Collection _customTrustStore;
public X509CertificateValidationMode CertificateValidationMode { get; set; } = X509CertificateValidationMode.ChainTrust;
public X509Certificate2Collection CustomTrustStore => _customTrustStore ??= new X509Certificate2Collection();
public X509RevocationMode RevocationMode { get; set; } = X509RevocationMode.Online;
public StoreLocation TrustedStoreLocation { get; set; } = StoreLocation.CurrentUser;
public override void Validate(X509Certificate2 certificate)
{
if (certificate == null) throw new ArgumentNullException(nameof(certificate));
switch (CertificateValidationMode)
{
case X509CertificateValidationMode.None:
case X509CertificateValidationMode.PeerTrust:
break;
case X509CertificateValidationMode.ChainTrust:
ValidateChainTrust(certificate);
break;
case X509CertificateValidationMode.Custom:
default:
throw new NotSupportedException("X509 certificate validation mode not supported.");
}
}
private void ValidateChainTrust(X509Certificate2 certificate)
{
bool useMachineContext = TrustedStoreLocation == StoreLocation.LocalMachine;
using (var chain = new X509Chain(useMachineContext))
{
chain.ChainPolicy = new X509ChainPolicy
{
VerificationTime = DateTimeOffset.UtcNow.UtcDateTime,
RevocationMode = RevocationMode,
TrustMode = X509ChainTrustMode.CustomRootTrust,
};
chain.ChainPolicy.CustomTrustStore.AddRange(this.CustomTrustStore);
if (chain.Build(certificate))
{
return;
}
throw new SecurityTokenValidationException($"Invalid X509 certificate chain.{GetCertificateInformation(certificate)}{GetChainStatusInformation(chain.ChainStatus)}.");
}
}
}