Sasha
Sasha

Reputation: 8850

ADFS token encryption certificate chain validation fails

I have ASP.NET MVC web site which I configured to authenticate through Active Directory Federation Service. Everything worked fine until I tried to enable token encryption. As usual, I created one more self-signed certificate on IIS, added it to Trusted Root authorities on my web server and ADFS server and run application to veryfy how it works.

My application correctly redirected me to ADFS service page to enter credentials. But when I submit my login and password, I immediately get "An error occured" message on the same login page with not very useful details section:

Activity ID: 00000000-0000-0000-b039-0080010000e4
Relying party: [My relying party name]
Error time: Fri, 21 Oct 2016 18:48:24 GMT
Cookie: enabled
User agent string: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.59 Safari/537.36

I don't get redirected to my web site after that and Network panel doesn't contain any requests.

But I discovered, that if I add the following setting into my web site's web.config, it starts working again:

<certificateValidation certificateValidationMode="None" />

So the error must be related to the fact that my certificate is self-signed. But I have added it to trusted root authorities both on web server and ADFS server (as well as few other "suspicious" certificates).

Does anybody have an idea what could be missing and what can I do to make my test environment work with self-signed certificates, while validating certificate chain?

Upvotes: 0

Views: 2342

Answers (3)

Sasha
Sasha

Reputation: 8850

It appeared that to resolve an error it was enough to add ADFS Token Signing certificate as Trusted Root Certification Authority on my web server.

PS: I'm not sure why token signing certificate chain validation didn't raise errors when encryption was disabled and what relation does it have to encryption at all, but the fact is that it helped for both environments we've used for testing.

Upvotes: 1

Thuan
Thuan

Reputation: 1628

Adding certificates to your CA trusted store only mean you trust the issuer of the certificate, which is the certificate itself in this case because it is a self-signed certificate. What is missing is that certificate validation performs chain-check and revocation check and either one of the two check failed for you. Please note that even if you trust a certificate, it still could have been revoked recently and thus shouldn't no longer be trusted. Therefore, revocation check is always necessary. For testing, disabling revocation check is one way. One ADFS side, you can disable revocation check per relying party. If the check happens on your own code, you can either disable check totally or use Stinky Towel's code to selectively allow some certificates only.

Upvotes: 0

Stinky Towel
Stinky Towel

Reputation: 778

I do something similar with an api handler that acts as a pass through and must interrogate certs.

Something that may help your troubleshooting.

Set the cert validation callback to something like:

// validate server cert
ServicePointManager.ServerCertificateValidationCallback += ValidateServerCertificate;

Then in the validation method you can interrogate the chain:

private static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            // default validation bool to false
            var isValid = false;

            // If the certificate is a valid, signed certificate, return true to short circuit any add'l processing.
            if (sslPolicyErrors == SslPolicyErrors.None)
            {
                return true;
            }
            else
            {
                // cast cert as v2 in order to expose thumbprint prop
                var requestCertificate = (X509Certificate2)certificate;

                // init string builder for creating a long log entry
                var logEntry = new StringBuilder();

                // capture initial info for the log entry
                logEntry.AppendFormat("Certificate Validation Error - SSL Policy Error: {0} - Cert Issuer: {1} - SubjectName: {2}",
                   sslPolicyErrors.ToString(),
                   requestCertificate.Issuer,
                   requestCertificate.SubjectName.Name);

                //init special builder for thumprint mismatches
                var thumbprintMismatches = new StringBuilder();

                // load valid certificate thumbs for comparison later
                var validThumbprints = new string[] { "thumbprint A", "thumbprint N" };

                // else if a cert name mismatch then assume api pass thru issue and verify thumb print
                if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateNameMismatch) 
                {
                    // compare thumbprints
                    var hasMatch = validThumbprints.Contains(requestCertificate.Thumbprint, StringComparer.OrdinalIgnoreCase);

                    // if match found then we're valid so clear builder and set global valid bool to true
                    if (hasMatch)
                    {
                        thumbprintMismatches.Clear();
                        isValid = true;
                    }
                    // else thumbprint did not match so append to the builder
                    else
                    {
                        thumbprintMismatches.AppendFormat("|Thumbprint mismatch - Issuer: {0} - SubjectName: {1} - Thumbprint: {2}",
                             requestCertificate.Issuer,
                             requestCertificate.SubjectName.Name,
                             requestCertificate.Thumbprint);
                    }
                }
                // else if chain issue, then iterate over the chain and attempt find a matching thumbprint
                else if (sslPolicyErrors == SslPolicyErrors.RemoteCertificateChainErrors) //Root CA problem
                {
                    // check chain status and log
                    if (chain != null && chain.ChainStatus != null)
                    {
                        // check errors in chain and add to log entry
                        foreach (var chainStatus in chain.ChainStatus)
                        {
                            logEntry.AppendFormat("|Chain Status: {0} - {1}", chainStatus.Status.ToString(), chainStatus.StatusInformation.Trim());
                        }

                        // check for thumbprint mismatches
                        foreach (var chainElement in chain.ChainElements)
                        {
                            // compare thumbprints
                            var hasMatch = validThumbprints.Contains(chainElement.Certificate.Thumbprint, StringComparer.OrdinalIgnoreCase);

                            // if match found then we're valid so break, clear builder and set global valid bool to true
                            if (hasMatch)
                            {
                                thumbprintMismatches.Clear();
                                isValid = true;
                                break;
                            }
                            // else thumbprint did not match so append to the builder
                            else
                            {
                                thumbprintMismatches.AppendFormat("|Thumbprint mismatch - Issuer: {0} - SubjectName: {1} - Thumbprint: {2}",
                                     chainElement.Certificate.Issuer,
                                     chainElement.Certificate.SubjectName.Name,
                                     chainElement.Certificate.Thumbprint);
                            }
                        }
                    }
                }

                // if still invalid and thumbprint builder has items, then continue 
                if (!isValid && thumbprintMismatches != null && thumbprintMismatches.Length > 0)
                {
                    // append thumbprint entries to the logentry as well
                    logEntry.Append(thumbprintMismatches.ToString());
                }

                // log as WARN here and not ERROR - if method ends up returning false then it will bubble up and get logged as an ERROR
                LogHelper.Instance.Warning((int)ErrorCode.CertificateValidation, logEntry.ToString().Trim());
            }

            // determine env
            var isDev = EnvironmentHelper.IsDevelopment();
            var isTest = EnvironmentHelper.IsTest();

            // if env is dev or test then ignore cert errors and return true (reference any log entries created from logic above for troubleshooting)
            if (isDev || isTest)
                isValid = true;

            return isValid;
        }

NOTE: you'll need to disable/change some of the custom code - thumbprint stuff, logging etc.

Upvotes: 0

Related Questions