A time ago, Windows PKI team posted an article about a tool that allows you to check web server SSL certificate: Verifying The SSL Certificate Expiration with a tool. Unfortunately, the download link is broken. I have this tool and uploaded it to my weblog: VerifySSLCertificate.

  • ZIP archive SHA1 hash: C633A3DC3E8A3AA6BDD714EABB925429076A160A
  • Executable SHA1 hash: A13CE031F5C1331785E87B62D1464C6260549EC0

The tool is very good, but what if you want to run the test against a bulk of servers? Any sort of automation and batching means some PowerShell stuff :). To provide administrators with such tool I wrote a PowerShell script, where you can test web server SSL certificate and it's status. You can export required fields to XML or CSV for future examination/audit. Let's go:

function Test-WebServerSSL {
[CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0)]
        [string]$URL,
        [Parameter(Position = 1)]
        [ValidateRange(1,65535)]
        [int]$Port = 443,
        [Parameter(Position = 2)]
        [Net.WebProxy]$Proxy,
        [Parameter(Position = 3)]
        [int]$Timeout = 15000,
        [switch]$UseUserContext
    )
Add-Type @"
using System;
using System.Net;
using System.Security.Cryptography.X509Certificates;
namespace PKI {
    namespace Web {
        public class WebSSLTest {
            public Uri OriginalURi { get; set; }
            public Uri ReturnedURi { get; set; }
            public X509Certificate2 Certificate { get; set; }
            //public X500DistinguishedName Issuer;
            //public X500DistinguishedName Subject;
            public string Issuer { get; set; }
            public string Subject { get; set; }
            public string[] SubjectAlternativeNames { get; set; }
            public bool CertificateIsValid { get; set; }
            //public X509ChainStatus[] ErrorInformation;
            public string[] ErrorInformation { get; set; }
            public HttpWebResponse Response { get; set; }
        }
    }
}
"@
    $ConnectString = "https://$url`:$port"
    $WebRequest = [Net.WebRequest]::Create($ConnectString)
    $WebRequest.Proxy = $Proxy
    $WebRequest.Credentials = $null
    $WebRequest.Timeout = $Timeout
    $WebRequest.AllowAutoRedirect = $true
    [Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
    try {$Response = $WebRequest.GetResponse()}
    catch {}
    if ($WebRequest.ServicePoint.Certificate -ne $null) {
        $Cert = [Security.Cryptography.X509Certificates.X509Certificate2]$WebRequest.ServicePoint.Certificate.Handle
        try {$SAN = ($Cert.Extensions | Where-Object {$_.Oid.Value -eq "2.5.29.17"}).Format(0) -split ", "}
        catch {$SAN = $null}
        $chain = New-Object Security.Cryptography.X509Certificates.X509Chain -ArgumentList (!$UseUserContext)
        [void]$chain.ChainPolicy.ApplicationPolicy.Add("1.3.6.1.5.5.7.3.1")
        $Status = $chain.Build($Cert)
        New-Object PKI.Web.WebSSLTest -Property @{
            OriginalUri = $ConnectString;
            ReturnedUri = $Response.ResponseUri;
            Certificate = $WebRequest.ServicePoint.Certificate;
            Issuer = $WebRequest.ServicePoint.Certificate.Issuer;
            Subject = $WebRequest.ServicePoint.Certificate.Subject;
            SubjectAlternativeNames = $SAN;
            CertificateIsValid = $Status;
            Response = $Response;
            ErrorInformation = $chain.ChainStatus | ForEach-Object {$_.Status}
        }
        $chain.Reset()
        [Net.ServicePointManager]::ServerCertificateValidationCallback = $null
    } else {
        Write-Error $Error[0]
    }
}

The code connects to a server by using provided host name, instantiates a SSL connection, retrieves actual SSL certificate and tests, whether the certificate is valid (full tests are performed, including revocation checking). The following parameters can be used:

  • -URL — specifies the remote web server address. You must specify only host name, without any protocol prefixes. The correct host name is something like this: www.domain.com. This is the only mandatory parameter. All other parameters are optional.
  • -Port — if remote web server is configured to use a custom port (default is 443), you can specify a port number to connect.
  • -Proxy — use this parameter to specify a web proxy address if you wish to use proxy.
  • -Timeout — the default connection timeout is set to 15 seconds. You can override the value, by specifying another value. Timeout value must be set in milliseconds. Say, 1 second timeout will be 1000 milliseconds and 20 seconds — 20000 milliseconds.
  • -UseUserContext — by default, the certificate must chain up to a trusted CA, which certificate is trusted by the computer (installed in Local Machine\Trusted Root CAs). If the root CA is trusted by only current user — a test will fail. You can use this switch to override the default behavior, so the test will succeed if a root CA is trusted only by current user.

Here are some examples:

[↓] [vPodans] Test-WebServerSSL signin.ebay.com


OriginalURi             : https://signin.ebay.com/
ReturnedURi             : https://signin.ebay.com/
Certificate             : [Subject]
                            CN=signin.ebay.com, OU=Site Operations, O=eBay Inc., STREET=2145 Hamilton Ave, L=San Jose,
                          S=California, PostalCode=95125, C=US, SERIALNUMBER=2871352, OID.2.5.4.15=Private Organization
                          , OID.1.3.6.1.4.1.311.60.2.1.2=Delaware, OID.1.3.6.1.4.1.311.60.2.1.3=US

                          [Issuer]
                            CN=VeriSign Class 3 Extended Validation SSL CA, OU=Terms of use at https://www.verisign.com
                          /rpa (c)06, OU=VeriSign Trust Network, O="VeriSign, Inc.", C=US

                          [Serial Number]
                            3EA2CB6BAC5BDED626A5AEA6D914E30C

                          [Not Before]
                            19.01.2011 2:00:00

                          [Not After]
                            24.01.2013 1:59:59

                          [Thumbprint]
                            9DB60558622A1FA3EF5C0683671751CFDA8C7253

Issuer                  : CN=VeriSign Class 3 Extended Validation SSL CA, OU=Terms of use at https://www.verisign.com/r
                          pa (c)06, OU=VeriSign Trust Network, O="VeriSign, Inc.", C=US
Subject                 : CN=signin.ebay.com, OU=Site Operations, O=eBay Inc., STREET=2145 Hamilton Ave, L=San Jose, S=
                          California, PostalCode=95125, C=US, SERIALNUMBER=2871352, OID.2.5.4.15=Private Organization,
                          OID.1.3.6.1.4.1.311.60.2.1.2=Delaware, OID.1.3.6.1.4.1.311.60.2.1.3=US
SubjectAlternativeNames : {DNS Name=signin.ebay.com, DNS Name=signin.ebay.at, DNS Name=signin.ebay.be, DNS Name=signin.
                          ebay.ca...}
CertificateIsValid      : True
ErrorInformation        :
Response                : System.Net.HttpWebResponse



[↓] [vPodans] Test-WebServerSSL trust.beeline.ru


OriginalURi             : https://trust.beeline.ru/
ReturnedURi             : https://trust.beeline.ru/
Certificate             : [Subject]
                            CN=trust.beeline.ru, OU=DIT, O=Vimpelcom, L=Moscow, S=Moscow, C=RU

                          [Issuer]
                            CN=Vimpelcom ExternalCA, O=Vimpelcom, C=RU

                          [Serial Number]
                            40C9BF400000000341D9

                          [Not Before]
                            02.02.2012 8:32:51

                          [Not After]
                            02.02.2013 8:42:51

                          [Thumbprint]
                            58FEEEC8676F7E2D106DEA4235B22AFC09086AEB

Issuer                  : CN=Vimpelcom ExternalCA, O=Vimpelcom, C=RU
Subject                 : CN=trust.beeline.ru, OU=DIT, O=Vimpelcom, L=Moscow, S=Moscow, C=RU
SubjectAlternativeNames :
CertificateIsValid      : False
ErrorInformation        : {PartialChain, RevocationStatusUnknown, OfflineRevocation}
Response                : System.Net.HttpWebResponse



[↓] [vPodans]

In the output you will see some important details: certificate subject, issuer and Subject Alternative Name (SAN) extension names. The most important fields are bolded: CertificateIsValid displays whether the certificate has passed or failed all certificate checks. If at least one test fails — detailed error information will be set to ErrorInformation property. In the current example, the second certificate failed revocation checking (revocation information is unavailable) and has partial chain (the chain is missing one or more CA certificate and they are not downloadable/reachable). If the server has expired certificate, you will see the following information in these two properties:

CertificateIsValid      : False
ErrorInformation        : {NotTimeValid}

The tool will return all certificate errors (not throwing only the first). Hope, you find the script helpful.


Share this article:

Comments:

artem

Why not use something like �Get-Certificate | Test-Certicate�? I bet you (or other guys) already have those functions. And if they don't work this way for some reason, it seems like a good opportunity to improve them, not reinventing the wheel.

Vadims Podans

because the code runs against remote web servers. In many cases the certificate locally may look as valid, but from client perspective � not. For example, certificate name mismatch, trust issues, revocation checking and so on.

artem

So, why not make �Get-Certificate� so that it grabs it from those remove web servers? All as you did it here: Get-Certificate -Path "signin.ebay.com" -Port "443" -Proxy "someshithere.contoso.com" -ProxyPort "8080" -Timeout "100500" This should bring the remote certificate into the client-side perspective. Then pipe it to �Test-Certificate� which would obviously check it locally.

Vadims Podans

Currently I haven't a good design for universal Test-Certificate (it useless to have a separate Test-Certificate cmdlet for SSL certificates only). Probably, in future, when I will have a better solution.

artem

why do you need a separate one? It should just accept output from Get-Certificate.

Vadims Podans

Currently I have no idea how to create a universal Test-Certificate command.

artem

What is the input format your current implementation of Test-Certificate expects?

kalihto

Clear Explanation.

Michael Bradley

THANK YOU! I have to sift through lots and lots of nodes and this is a LIFE Saver.

Tom Davidson

I have my own personal CA whose cert I have installed into the Trusted Root CAs. This way I can simulate the signing process and test configurations without the need for expensive commercial CA signed certs all the time. However, when I run your function against a server configured in this way, I get the following: CertificateIsValid : False ErrorInformation : {RevocationStatusUnknown} Not knowing much about SSL certs & revocation, is there any way I can configure things up so your function will generate a CertificateIsValid status of True for my test environments?

Vadims Podans

This is a big question. Revocation checking is one of the most (and complicated) subject in PKI. The most likely issue is that URLs in the certificate's CDP extension are not accessible from your client. Open the target certificate, and examine URLs in CRL Distribution Points extension.

Messala

I wish output valid from and valid to to this script. Any Ideas?

Vadims Podans

The script already outputs these values (as a part of the certificate). But you can add the following properties to object: public DateTime ValidFrom; public DateTime ValidTo; and in the code (within 'New-Object PKI.Web.WebSSL' hashtable) add the following lines: ValidFrom = $Cert.NotBefore; ValidTo = $Cert.NotAfter;

Hank

I tried adding "pubic DateTime ValidTo;" and "ValidTo = $Cert.NotAfter;" and I'm getting this error: Add-Type : Cannot add type. The type name 'PKI.Web.WebSSL' already exists. At C:\Temp\PowerShell\Test-SSL.ps1:16 char:9 + Add-Type <<<< @" + CategoryInfo : InvalidOperation: (PKI.Web.WebSSL:String) [Add-Type], Exception + FullyQualifiedErrorId : TYPE_ALREADY_EXISTS,Microsoft.PowerShell.Commands.AddTypeCommand New-Object : Member "ValidTo" not found for the given .NET object. At C:\Temp\PowerShell\Test-SSL.ps1:56 char:19 + New-Object <<<< PKI.Web.WebSSL -Property @{ + CategoryInfo : InvalidOperation: (:) [New-Object], InvalidOperationException + FullyQualifiedErrorId : InvalidOperationException,Microsoft.PowerShell.Commands.NewObjectCommand FYI - renamed the function to Test-SSL. I'd like to be able to grab the URL, SANs, Issuer, and expiration. I can get all but the expiration.

Hank

I tried adding "pubic DateTime ValidTo;" and "ValidTo = $Cert.NotAfter;" and I'm getting this error: Add-Type : Cannot add type. The type name 'PKI.Web.WebSSL' already exists. At C:\Temp\PowerShell\Test-SSL.ps1:16 char:9 + Add-Type <<<< @" + CategoryInfo : InvalidOperation: (PKI.Web.WebSSL:String) [Add-Type], Exception + FullyQualifiedErrorId : TYPE_ALREADY_EXISTS,Microsoft.PowerShell.Commands.AddTypeCommand New-Object : Member "ValidTo" not found for the given .NET object. At C:\Temp\PowerShell\Test-SSL.ps1:56 char:19 + New-Object <<<< PKI.Web.WebSSL -Property @{ + CategoryInfo : InvalidOperation: (:) [New-Object], InvalidOperationException + FullyQualifiedErrorId : InvalidOperationException,Microsoft.PowerShell.Commands.NewObjectCommand FYI - renamed the function to Test-SSL. I'd like to be able to grab the URL, SANs, Issuer, and expiration. I can get all but the expiration.

Vadims Podans

When you modified C# signature, you need to restart PowerShell console.

Hank

Thanks that did the trick! I really appreciate this script, it will help me tremendously!

Vadims Podans

Also I would like to advice you to try the PSPKI module: http://pspki.codeplex.com/ The module has an improved version of this command.

Ravi

When I am trying to use it, it does not show anything on the screen, tried with and without -URL parameters

Vadims Podans

Any errors there? The function should return an exception or the formatted output.

Hank

Hi, I use this script to automatically verify certificate information in my environment. I've been able to use it to grab information in the certificate...but do you know how I might be able to get the OU value from the Subject string? Thanks in advance, Hank

Vadims Podans

You can use regular expressions to extract OU information. Though regex may be a bit complicated, because there may be multiple OU attributes. Or split subject name to tokens: $RDNs = (Test-WebServerSSL signin.ebay.com).Subject.Split(",").Trim() and index to $RDNs to get desired RDN attribute.

Scott

Seems the "https://$url`:$port" does not support the full uri path... For example... Redirects https:\\webserver - might redirect or produce a cert and https:\\webserver\folder - might redirect to a different place and produce a different cert in that case $ConnectString would produce https:\\webserver\folder:443 which produces a slew of errors... Exception calling "Create" with "1" argument(s): "Invalid URI: The hostname could not be parsed. At line:39 char:5 + $WebRequest = [Net.WebRequest]::Create($ConnectString) + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId : UriFormatException Not a show stopper and surely not the norm but thought I would let you know since I ran into it. Scott

Vadims Podans

> Seems the "https://$url`:$port" does not support the full uri path... yes, currently it is not supported. You can fill a bug here: https://pspki.codeplex.com/workitem/list/basic

Lance

Thanks for this script! I'm going to try and add some email functionality to it but it is awesome! Thanks again.

Bewq

This is awesome! Thank you!!!!!!!!

Derek Murawsky

I used your code to develop a tool which tests for the signature algorithm of a web server. Turns out SHA-1 is being phased out in November, so we needed to check approximately 500 websites. This made it much easier. Code available here if you want/need it. https://gist.github.com/derekmurawsky/1345e55699343ec4ea9c Thanks!

Mario

PS C:\> Test-WebServerSSL ebay.com Exception calling "SendRequest" with "0" argument(s): "Object reference not set to an instance of an object." At C:\Windows\system32\WindowsPowerShell\v1.0\Modules\PSPKI\Client\Test-WebServerSSL.ps1:26 char:2 + $Response.SendRequest() + ~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : NotSpecified: (:) [], MethodInvocationException + FullyQualifiedErrorId : NullReferenceException

Vadims Podans

I responded to you on CodePlex web site.

loler

Hi, thanks for you code, but i prefer to use the windows tool - VerifySSLCertificate, the download link is borken, could you renew it ?

Ken

Your link to VerifySSLCertificate.zip is broken (404 not found).

Marcel de Haas

Hi,

the script seems to be broken on Win10 1607 / Powershell5. I'n not familiar with using .NET Namespaces directly so I cannot fix this myself...

New-Object : The value supplied is not valid, or the property is read-only. Change the value, and then try again.
At line:54 char:9
+         New-Object PKI.Web.WebSSLTest -Property @{
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [New-Object], Exception
    + FullyQualifiedErrorId : SetValueException,Microsoft.PowerShell.Commands.NewObjectCommand

Vadims Podāns

Please, open a report on issue tracker: https://pspki.codeplex.com/workitem/list/basic

Pasc

For PowerShell 5 (and likely 3+) you could remove the add-type at the beginning and replace the new-object line by the following

<PoSHCode>

        $ConnectionInformation = New-Object PSObject -Property ([Ordered]@{ 
            OriginalUri = $ConnectString; 
            ReturnedUri = $Response.ResponseUri; 
            Certificate = [Security.Cryptography.X509Certificates.X509Certificate2]$WebRequest.ServicePoint.Certificate; 
            Issuer = $WebRequest.ServicePoint.Certificate.Issuer; 
            Subject = $WebRequest.ServicePoint.Certificate.Subject; 
            SubjectAlternativeNames = $SAN; 
            CertificateIsValid = $Status; 
            Response = $Response; 
            ErrorInformation = $chain.ChainStatus | ForEach-Object {$_.Status} 
        })
        $ConnectionInformation.PSObject.TypeNames.Add("Indented.LDAP.ConnectionInformation")
        $ConnectionInformation

</PoSHCode>

Don't yet understand how it works, just capturing out of http://www.indented.co.uk/2015/03/31/testing-ldaps

Bennett

It appears that when a wildcard certificate is presented it assumes all subdomains as covered.

Example: Test-WebServerSSL -URL wrong.host.badssl.com
CertificateIsValid : True (Should be false)

Elen

Hello,

I´m not a programmer unf. could you help me on a code for check ssl based url to have certification autority name, data created and will expire,please?

I appreciate your help!

om8000

Test-WebServerSSL www.google.com
Test-WebServerSSL : Exception calling "GetResponse" with "0" argument(s): "The operation has timed out"
At line:1 char:1
+ Test-WebServerSSL www.google.com
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Write-Error], WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Test-WebServerSSL

om8000

Why with some web pages this works ok, but with others I'm getting response (let's say for non-valid certificates).

How can I check for all sites with valid and non-valid certificates?

 

Test-WebServerSSL : Exception calling "GetResponse" with "0" argument(s): "The operation has timed out"
At line:1 char:1
+ Test-WebServerSSL www.google.com
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Write-Error], WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Test-WebServerSSL

-or-

New-Object : The value supplied is not valid, or the property is read-only. Change the value, and then try again.
At line:54 char:9
+         New-Object PKI.Web.WebSSLTest -Property @{
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [New-Object], Exception
    + FullyQualifiedErrorId : SetValueException,Microsoft.PowerShell.Commands.NewObjectCommand

 

 

William Doyle

Commenting out ErrorInformation solved the error:

New-Object : The value supplied is not valid, or the property is read-only. Change the value, and then try again.
At C:\Users\bdoyle1\Test-WebServerSSL.ps1:54 char:9
+         New-Object PKI.Web.WebSSLTest -Property @{
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidData: (:) [New-Object], Exception
    + FullyQualifiedErrorId : SetValueException,Microsoft.PowerShell.Commands.NewObjectCommand

 

Alternately removing the "| ForEach-Object {$_.ChainStatus}" loop did the same.   

The problem seems to be if the chain status is empty {} PS5 can't handle it.

David A. Stewart

To prevent throw error: "New-Object : The value supplied is not valid, or the property is read-only. Change the value, and then try again."

Change line:

ErrorInformation = $chain.ChainStatus | ForEach-Object {$_.Status}

To Line:

ErrorInformation = $(IF($chain.ChainStatus){$chain.ChainStatus | ForEach-Object {$_.Status}} Else {$NULL})

David

Is it possible to add a property to this that would show the thumbprint in SHA256 rather than just in SHA1?


Post your comment:

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