JohnLBevan
JohnLBevan

Reputation: 24410

How to check which certificates are included in the certificate chain deployed to a website

A number of sites offer tools to help check whether a certificate deployed to a site is valid; including checking that the full chain is installed, rather than just the client certificate. Some examples below:

I'd like to mimic this check via a PowerShell script (note: the aim is to check third party sites; so we don't have access to anything server side), so came up with the below:

Function Get-CertificateChain {
    [OutputType([System.Security.Cryptography.X509Certificates.X509ChainElement])]
    [CmdletBinding()]
    Param (
        [Parameter(Mandatory)]
        [string]$ComputerName
        ,
        [Parameter()]
        [Int32]$Port = 443
        ,
        [Parameter()]
        [System.Security.Authentication.SslProtocols]$SslProtocol = [System.Security.Authentication.SslProtocols]::Tls12 # NB: The enum value Default is considered deprecated
        ,
        [Parameter()]
        [Switch]$CertificateInfoOnly
    )
    [System.Net.Sockets.Socket]$socket = [System.Net.Sockets.Socket]::new([System.Net.Sockets.SocketType]::Stream, [System.Net.Sockets.ProtocolType]::Tcp)
    $socket.Connect($ComputerName, $Port)
    try {
        [System.Net.Sockets.NetworkStream]$networkStream = [System.Net.Sockets.NetworkStream]::new($socket, $true)
        [System.Net.Security.SslStream]$sslStream = [System.Net.Security.SslStream]::new($networkStream, $true)
        $sslStream.AuthenticateAsClient( $ComputerName,  $null, $SslProtocol, $false )
        [System.Security.Cryptography.X509Certificates.X509Certificate2]$remoteCertificate = [System.Security.Cryptography.X509Certificates.X509Certificate2]($sslStream.RemoteCertificate)
        [System.Security.Cryptography.X509Certificates.X509Chain]$chain = [System.Security.Cryptography.X509Certificates.X509Chain]::new()
        $chain.Build($remoteCertificate) | Out-Null
        Write-Verbose "Chain status length is: $($chain.ChainStatus.Length)" # Gives 0 every time :/
        foreach ($chainElement in $chain.ChainElements) {
            #[System.Security.Cryptography.X509Certificates.X509ChainElement]
            if ($CertificateInfoOnly.IsPresent) {
                $chainElement | Select-Object -ExpandProperty Certificate
            } else {
                $chainElement
            }
            
        }
    } finally {
        $socket.Close()
    }
}

However, this retrieves the full chain even on sites which are missing the full chain; I guess because BUILD is overly helpful and fetches any missing intermediate or root certificates that it can find.

I found info on ChainStatus in a similar answer, which seems a better solution; however, when I call $chain.ChainStatus.Length after Build is called (see Write-Verbose call) I get a result of 0 .

What's the correct way to see whether a certificate chain being returned by a third party is valid (i.e. including on clients which don't automatically resolve missing or out of order certificate chains), or to get the information on which certificates are actually returned, using the .net framework?

Upvotes: 2

Views: 753

Answers (1)

bartonjs
bartonjs

Reputation: 33088

Unfortunately, you can't (with SslStream). .NET will use a combination of local caches and network retrieval to try to complete the chain, and there's not a place you can really stop it (plus, you generally want it to put the root on top, since the normally-considered-correct TLS Server configuration is the entire chain except for the root authority).

Since .NET has TCP socket support you could do it in .NET by writing the TLS handshake code yourself, to then see what was actually on the wire... but I don't really advise that.

Upvotes: 1

Related Questions