Hi again! Recently I faced an issue with my Test-WebServerSSL function which is also available in my Powershell PKI module.

Issue

In certain cases, the function returns certificate chain errors, while Internet Explorer (as well as other web browsers) works normally and do not report anything wrong. For example, you can open https://www.nic.lv/ web site without any issues in your web browser. When you run Test-WebServerSSL function against this web site, you get the following:

[↓] [vPodans] Test-WebServerSSL -URL www.nic.lv


OriginalURi             : https://www.nic.lv/
ReturnedURi             : https://www.nic.lv/
Certificate             : [Subject]
                            CN=www.nic.lv, OU=NIC, O=LATVIJAS UNIVERSITATES MATEMATIKAS UN INFORMATIKAS INSTITUTS, L=Ri
                          ga, S=Riga, C=LV

                          [Issuer]
                            CN=Thawte SSL CA, O="Thawte, Inc.", C=US

                          [Serial Number]
                            0A476A0ED5869DE8A7EDADCC6F061B76

                          [Not Before]
                            03.12.2010. 2:00:00

                          [Not After]
                            09.12.2012. 1:59:59

                          [Thumbprint]
                            D2A82175226167F46DEA22F534857072C9780F1A

Issuer                  : CN=Thawte SSL CA, O="Thawte, Inc.", C=US
Subject                 : CN=www.nic.lv, OU=NIC, O=LATVIJAS UNIVERSITATES MATEMATIKAS UN INFORMATIKAS INSTITUTS, L=Riga
                          , S=Riga, C=LV
SubjectAlternativeNames :
CertificateIsValid      : False
ErrorInformation        : {PartialChain, RevocationStatusUnknown, OfflineRevocation}
Response                : System.Net.HttpWebResponse



[↓] [vPodans]

Look at the ErrorInformation property, it reports partial chain and offline revocation problem. This function falls into already discussed issue: The case of SSTP VPN with public certificate and error 0x80092013. SSL certificate is issued by Thawte SSL CA that do not include URLs for CA certificate retrieval.

Issue investigation

SSL certificate is stored in the ServicePoint.Certificate property of the HttpWebRequest class:

New-Object PKI.Web.WebSSL -Property @{
    OriginalUri = $ConnectString;
    ReturnedUri = $Response.ResponseUri;
    Certificate = $WebRequest.ServicePoint.Certificate;
<...>

The property contains only SSL certificate and other certificates returned from web server are not available. I spend some time on issue investigation and found this article: Certificate Validation Differences Between HTTPS, SSL over TCP, and SOAP Security. In the function definition you can find the following line:

[Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}

This static property accepts a delegate name (or inline delegate) for custom certificate validation and expects Boolean return value. Each time WebClient attempts to validate SSL certificate a delegate is called. Here is delegate definition in C#:

public static bool ValidateServerCertificate(
  object sender,
  X509Certificate certificate,
  X509Chain chain,
  SslPolicyErrors sslPolicyErrors)

“chain” argument contains all certificates returned by web server. Therefore, we have to write custom delegate to access these certificates. Once we get them, we can pass them to X509ChainPolicy.ExtraStore property. This trick allows us to correctly process SSL certificate like it does web browser.

Solution

Unfortunately, I’m not familiar with C#-style delegates in PowerShell and have a workaround for C# (Test-WebServerSSL function was moved to C# code). Here is updated WebSSL class source code:

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using PKI.Utils;


namespace PKI.Web {
    /// 
    /// The WebSSL class is used to verify remote server SSL certificate.
    /// 
    public class WebSSL {

        /// an instance of  that contains remote server connection properties.
        /// If request parameter is null or empty.
        /// If connection URL scheme is not 'HTTPS'.
        public WebSSL(WebRequest request) {
            if (request != null) {
                if (request.RequestUri.Scheme.ToLower() == "https") {
                    m_initialize(request);
                } else { throw new ArgumentException("Only 'HTTPS' URL scheme is supported."); }
            } else { throw new ArgumentNullException("request"); }
        }

        /// 
        /// Gets original connection URL.
        /// 
        public Uri OriginalUri { get; private set; }
        /// 
        /// Gets returned connection URL.
        /// 
        public Uri ReturnedUri { get; private set; }
        /// 
        /// Gets server SSL certificate.
        /// 
        public X509Certificate2 Certificate { get; private set; }
        /// 
        /// Gets entire SSL certificate chain returned by web server.
        /// 
        public X509Certificate2Collection Pkcs7Chain { get; private set; }
        /// 
        /// Gets certificate subject name.
        /// 
        public X500DistinguishedName Subject { get; private set; }
        /// 
        /// Gets certificate issuer.
        /// 
        public X500DistinguishedName Issuer { get; private set; }
        /// 
        /// Gets an array of Subject Alternative Names (SAN) if they are configured.
        /// 
        public String[] SubjectAlternativeNames { get; private set; }
        /// 
        /// Gets the status of the certificate. True if the certificate is valid, otherwise False.
        /// 
        public Boolean CertificateIsValid { get; private set; }
        /// 
        /// Gets certificate chain error information.
        /// 
        public X509ChainStatus[] ErrorInformation { get; private set; }
        /// 
        /// Gets original HTTP response.
        /// 
        public HttpWebResponse Response { get; private set; }
        internal HttpWebRequest Request { get; private set; }

        void m_initialize(WebRequest request) {
            OriginalUri = request.RequestUri;
            Request = (HttpWebRequest)request;
        }
        /// 
        /// Submits HTTP request to a remote server and updates current object instance.
        /// 
        public void SendRequest() {
            if (Request != null) {
                List san = new List();
                X509Chain Chain = new X509Chain();
                Pkcs7Chain = new X509Certificate2Collection();
                // add inline delegate definition
                ServicePointManager.ServerCertificateValidationCallback =
                    delegate (Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) {
                    // check if web server returns multiple certificates
                    if (chain.ChainElements.Count > 1) {
                        foreach (X509ChainElement item in chain.ChainElements) {
                            Pkcs7Chain.Add(item.Certificate);
                            // add each certificate to X509Chain.ExtraStore, so certificate chaining engine
                            // will be able to use them for chain building
                            Chain.ChainPolicy.ExtraStore.Add(item.Certificate);
                        }
                    }
                    return true;
                };
                try {
                    Response = (HttpWebResponse)Request.GetResponse();
                    ReturnedUri = Response.ResponseUri;
                    if (Request.ServicePoint.Certificate != null) {
                        Certificate = new X509Certificate2(Request.ServicePoint.Certificate);

                        Subject = Certificate.SubjectName;
                        Issuer = Certificate.IssuerName;
                        if (Certificate.Extensions.Count > 0) {
                            foreach (X509Extension item in Certificate.Extensions) {
                                if (item.Oid.Value == "2.5.29.17") {
                                    san.AddRange(item.Format(false).Split(new [] { ", " }, StringSplitOptions.RemoveEmptyEntries));
                                }
                            }
                        }
                        SubjectAlternativeNames = san.ToArray();
                        // add other settings for X509Chain and validate server certificate.
                        Chain.ChainPolicy.ApplicationPolicy.Add(new Oid("1.3.6.1.5.5.7.3.1"));
                        CertificateIsValid = Chain.Build(Certificate);
                        ErrorInformation = Chain.ChainStatus;
                    }
                } catch {
                    if (!Request.HaveResponse) { throw; }
                } finally {
                    Chain.Reset();
                    if (Response.ContentLength > 0) { Response.Close(); }
                    ServicePointManager.ServerCertificateValidationCallback = null;
                }
            } else { throw new UninitializedObjectException(); }
        }
    }
}

If you want to use this code in PowerShell, just pass it to Add-Type cmdlet and use this class as any other .NET object:

[↓] [vPodans] $ConnectString = "https://www.nic.lv"
[↓] [vPodans] $WebRequest = [Net.WebRequest]::Create($ConnectString)
[↓] [vPodans] $WebRequest.AllowAutoRedirect = $true
[↓] [vPodans] $a = New-Object pki.web.webssl $WebRequest
[↓] [vPodans] $a.SendRequest()
[↓] [vPodans] $a


OriginalUri             : https://www.nic.lv/
ReturnedUri             : https://www.nic.lv/
Certificate             : [Subject]
                            CN=www.nic.lv, OU=NIC, O=LATVIJAS UNIVERSITATES MATEMATIKAS UN INFORMATIKAS INSTITUTS, L=Ri
                          ga, S=Riga, C=LV

                          [Issuer]
                            CN=Thawte SSL CA, O="Thawte, Inc.", C=US

                          [Serial Number]
                            0A476A0ED5869DE8A7EDADCC6F061B76

                          [Not Before]
                            03.12.2010. 2:00:00

                          [Not After]
                            09.12.2012. 1:59:59

                          [Thumbprint]
                            D2A82175226167F46DEA22F534857072C9780F1A

Pkcs7Chain              : {[Subject]
                            CN=www.nic.lv, OU=NIC, O=LATVIJAS UNIVERSITATES MATEMATIKAS UN INFORMATIKAS INSTITUTS, L=Ri
                          ga, S=Riga, C=LV

                          [Issuer]
                            CN=Thawte SSL CA, O="Thawte, Inc.", C=US

                          [Serial Number]
                            0A476A0ED5869DE8A7EDADCC6F061B76

                          [Not Before]
                            03.12.2010. 2:00:00

                          [Not After]
                            09.12.2012. 1:59:59

                          [Thumbprint]
                            D2A82175226167F46DEA22F534857072C9780F1A
                          , [Subject]
                            CN=Thawte SSL CA, O="Thawte, Inc.", C=US

                          [Issuer]
                            CN=thawte Primary Root CA, OU="(c) 2006 thawte, Inc. - For authorized use only", OU=Certifi
                          cation Services Division, O="thawte, Inc.", C=US

                          [Serial Number]
                            4D5F2C3408B24C20CD6D507E244DC9EC

                          [Not Before]
                            08.02.2010. 2:00:00

                          [Not After]
                            08.02.2020. 1:59:59

                          [Thumbprint]
                            73E42686657AECE354FBF685712361658F2F4357
                          , [Subject]
                            CN=thawte Primary Root CA, OU="(c) 2006 thawte, Inc. - For authorized use only", OU=Certifi
                          cation Services Division, O="thawte, Inc.", C=US

                          [Issuer]
                            CN=thawte Primary Root CA, OU="(c) 2006 thawte, Inc. - For authorized use only", OU=Certifi
                          cation Services Division, O="thawte, Inc.", C=US

                          [Serial Number]
                            344ED55720D5EDEC49F42FCE37DB2B6D

                          [Not Before]
                            17.11.2006. 2:00:00

                          [Not After]
                            17.07.2036. 2:59:59

                          [Thumbprint]
                            91C6D6EE3E8AC86384E548C299295C756C817B81
                          }
Subject                 : System.Security.Cryptography.X509Certificates.X500DistinguishedName
Issuer                  : System.Security.Cryptography.X509Certificates.X500DistinguishedName
SubjectAlternativeNames : {}
CertificateIsValid      : True
ErrorInformation        : {}
Response                : System.Net.HttpWebResponse



[↓] [vPodans]

Now you see all certificates returned by web server in the Pkcs7Chain (selected in bold) property and ErrorInformation property is empty.

Tip

To use this C# code in PowerShell, do the following:

Add-Type @"
// your C# code goes here
"@

the only thing you have change is the following line:

throw new UninitializedObjectException();

to something like this:

throw new Exception("An attempt was made to access an uninitialized object.");

 

As another option is to do nothing and wait for PowerShell PKI module next update.


Share this article:

Comments:


Post your comment:

Please, solve this little equation and enter result below. Captcha