Powershell : Certificate Chain Checker

When you deploy certificates you need to ensure you trust the "chain" of the certificate authority this will usually look like this:

Certificate > Intermediate Certificate Authority > Root Certificate Authority

It is best practice for in this example, where we are using internal Certificate Authority servers that all the chain is trusted on devices that cannot receive the certificates, this should be outlined and provided in the certificate being used, but you should not be trusting this path:

Certificate > Root Certificate Authority

If this is the approach then you will get trust issues the certificate at the section is red below as it is not trusted implicitly: 

Certificate > Intermediate Certificate Authority > Root Certificate Authority

If this rule is not enforced on the client then this error will be visible in the browser, in this case Chrome:


This means we are aware of the Root Certificate Authority but not the Intermediate Authority meaning that the chain is broken and that will give you the problem shown above.

I therefore created a script to check for this and this will check that the certificate is valid and that the remote server is presenting the certificates correctly to the client as well, this is an example of this:


In the above example you can see that the we have 3 x certificates which are all correctly being presented by the server and not relying to the local store, this means this certificate is healthy and client that visit this site will not get the error outlined above.

Script : Check-CertChain.ps1

# PowerShell Certificate Chain Checker with Chain Presentation Verification
# This script verifies both the chain validity and how certificates are obtained

function Test-CertificateChainPS {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]
        [string]$FQDN
    )
   Write-Host "`nChecking certificate chain for: $FQDN" -ForegroundColor Cyan   
    try {
        Write-Verbose "Creating TCP Client..."
        $port = 443
        $tcpClient = New-Object System.Net.Sockets.TcpClient       
        Write-Host "Connecting to $FQDN..." -ForegroundColor Yellow
        $tcpClient.Connect($FQDN, $port)
        if (-not $tcpClient.Connected) {
            Write-Host "Failed to connect to $FQDN on port $port" -ForegroundColor Red
            return
        }
        Write-Verbose "Creating SSL Stream..."       
        # Create callback to capture server-provided certificates
        $serverCertificates = New-Object System.Collections.Generic.List[System.Security.Cryptography.X509Certificates.X509Certificate2]
        $remoteCertificateCallback = {
            param($sender, $certificate, $chain, $errors)           

            # Capture each certificate presented by the server
            if ($chain) {
                foreach ($element in $chain.ChainElements) {
                    $serverCertificates.Add($element.Certificate)
                }
            }
            return $true
        }
        $sslStream = New-Object System.Net.Security.SslStream(
            $tcpClient.GetStream(),
            $false,
            $remoteCertificateCallback
        )
        Write-Verbose "Authenticating as client..."
        $sslStream.AuthenticateAsClient($FQDN)
        Write-Host "-------------------------------------" -ForegroundColor Green
        Write-Host "Certificates directly presented by server: $($serverCertificates.Count)" -ForegroundColor White
        Write-Host "`nComplete Chain Analysis:" -ForegroundColor Green
        Write-Host "----------------------" -ForegroundColor Green
        $remoteCert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($sslStream.RemoteCertificate)
        $chain = New-Object System.Security.Cryptography.X509Certificates.X509Chain       

        # Configure chain building options
        $chain.ChainPolicy.RevocationFlag = [System.Security.Cryptography.X509Certificates.X509RevocationFlag]::EntireChain
        $chain.ChainPolicy.RevocationMode = [System.Security.Cryptography.X509Certificates.X509RevocationMode]::Online
        $chain.ChainPolicy.UrlRetrievalTimeout = New-TimeSpan -Seconds 30       

        # Configure verification flags to allow unknown certificate authorities
        $chain.ChainPolicy.VerificationFlags = [System.Security.Cryptography.X509Certificates.X509VerificationFlags]::AllowUnknownCertificateAuthority
        Write-Verbose "Building complete certificate chain..."
        $chainBuilt = $chain.Build($remoteCert)

        # Track which certificates were downloaded vs presented
        $downloadedCerts = New-Object System.Collections.Generic.List[string]       

        # Display each certificate in the chain
        for ($i = 0; $i -lt $chain.ChainElements.Count; $i++) {
            $cert = $chain.ChainElements[$i].Certificate           
            Write-Host "`nCertificate $($i + 1):" -ForegroundColor Yellow
            Write-Host "Subject:  $($cert.Subject)" -ForegroundColor White
            Write-Host "Issuer:   $($cert.Issuer)" -ForegroundColor White
            Write-Host "Valid From: $($cert.NotBefore)" -ForegroundColor White
            Write-Host "Valid To:   $($cert.NotAfter)" -ForegroundColor White            

            # Determine certificate type and source
            if ($i -eq 0) {
                $certType = "End-Entity Certificate"
            }
            elseif ($i -eq $chain.ChainElements.Count - 1) {
                $certType = "Root CA Certificate"
            }
            else {
                $certType = "Intermediate Certificate"
            }            

            # Check if this certificate was presented by server
            $wasPresented = $false
            foreach ($serverCert in $serverCertificates) {
                if ($cert.Thumbprint -eq $serverCert.Thumbprint) {
                    $wasPresented = $true
                    break
                }
            }           
            Write-Host "Type:     $certType" -ForegroundColor Cyan
            if ($wasPresented) {
                Write-Host "Source:   Presented by Server" -ForegroundColor Green
            }
            else {
                Write-Host "Source:   Retrieved from Network/Local Store" -ForegroundColor Yellow
                $downloadedCerts.Add($cert.Subject)
            }

            # Check for any status flags
            if ($chain.ChainElements[$i].ChainElementStatus.Length -gt 0) {
                Write-Host "Status Flags:" -ForegroundColor Yellow
                foreach ($status in $chain.ChainElements[$i].ChainElementStatus) {
                    Write-Host "- $($status.StatusInformation)" -ForegroundColor Gray
                }
            }
        }

        # Display chain validation result and missing certificates
        Write-Host "`nChain Analysis Summary:" -ForegroundColor Green
        Write-Host "---------------------" -ForegroundColor Green
        Write-Host "Total certificates in chain: $($chain.ChainElements.Count)" -ForegroundColor White
        Write-Host "Certificates presented by server: $($serverCertificates.Count)" -ForegroundColor White
        Write-Host "Certificates retrieved from other sources: $($downloadedCerts.Count)" -ForegroundColor White       
        if ($downloadedCerts.Count -gt 0) {
            Write-Host "`nCertificates that had to be retrieved:" -ForegroundColor Yellow
            foreach ($cert in $downloadedCerts) {
                Write-Host "- $cert" -ForegroundColor Gray
            }
        }       
        if ($chainBuilt) {
            Write-Host "`n✓ Certificate chain is valid" -ForegroundColor Green
            if ($serverCertificates.Count -lt $chain.ChainElements.Count) {
                Write-Host "! Warning: Not all certificates are being presented by the server" -ForegroundColor Yellow
                Write-Host "  This may cause issues with clients that cannot download missing certificates" -ForegroundColor Yellow
            }
        }
        else {
            Write-Host "`n✗ Certificate chain validation failed" -ForegroundColor Red
            Write-Host "Chain Status Details:" -ForegroundColor Yellow
            foreach ($element in $chain.ChainElements) {
                foreach ($status in $element.ChainElementStatus) {
                    Write-Host "- $($status.StatusInformation)" -ForegroundColor Red
                }
            }
        }
    }
    catch {
        Write-Host "`nError: An unexpected error occurred" -ForegroundColor Red
        Write-Host "Error details: $_" -ForegroundColor Red
        Write-Host "Stack trace:" -ForegroundColor Red
        Write-Host $_.ScriptStackTrace -ForegroundColor Gray
    }
    finally {
        if ($sslStream) { $sslStream.Dispose() }
        if ($tcpClient) { $tcpClient.Dispose() }
    }
}

# Main script execution
Clear-Host
Write-Host "Certificate Chain Checker (Skeletor)" -ForegroundColor Cyan
Write-Host "-------------------------------------" -ForegroundColor Cyan
$FQDN = Read-Host "`nPlease enter the FQDN to check"
Test-CertificateChainPS -FQDN $FQDN -Verbose

Previous Post Next Post

نموذج الاتصال