Get and Enumerate Certificate Chains Remotely Using PowerShell

Get and Enumerate Certificate Chains Remotely Using PowerShell

January 2, 2019 0 By Amir Joseph Sayes

In responding to the Certificate Trust Issue when using SSL relay with Citrix XML Service, I wrote a function that can get all the certificates in the certificate path (chain), and provide a better view of different attributes which makes reporting and comparing much easier.

The Function would use Authority Key Identifier and the Subject Key Identifier to determine the certificate path and fetch them, until reaching the Root Certificate.

How to Use

Get-CertificatePath -CertificateName "mywebsite*" -Recurse -CertificateStore ""

The above would search all certificates on the local machine and filter them out to find the certificate that matches the name passed (using the “Subject” property).

If more than one certificate matches, they will be looped into individually
The function will call itself recursively until the issuer and the subject are the same – which means we have reached the Root CA.

$Computername = "Computer1","Computer2","Computer3"
        $CertificateName = "Mywebsite.MyDomain.com" 
        
        $res = @()         
        $getVert_Def = "Function Get-CertificatePath { ${function:Get-CertificatePath}} "
        $Computername | % {
            $res += icm -ComputerName $_ -ArgumentList $getVert_Def -ScriptBlock {
            Param($getVert_Def)
            .([scriptblock]::Create($getVert_Def)) 
            Get-CertificatePath -CertificateName $using:CertificateName 
            } 
        }
$res 

 

The above code runs the Function on remote computers using invoke-command.
Starts by creating an array of computer names which you would like to remotely run the function against.
Creates a parameter to pass the certificate you are looking for
Create a definition to the function so we can pass it to each remote invoke-command.
Loop inside the array of computers and pass the function and run it against each one of them using invoke-command.

The Script

Function Get-CertificatePath {
    <#
    .SYNOPSIS
        Function to get all certificate in in a certificate path (chain)

    .DESCRIPTION
        Function to get and display all the properties of the certificates in a certificate path (chain) until the Root CA.

            The Function would use Authority Key Identifier and the Subject Key Identifier to determine the certificate path

    .PARAMETER CertificateName
        Pass the certificate name you are looking for. This can be a exact match with wildcard

    .PARAMETER ParentAKI
        Not available for the user to use via the pipeline, this parameter is used internally to look for the certificates in the validation chain recursively
    
    .PARAMETER Recurse
        If this switch is ON, the function will recusivley call itself through the certificate chain until it gets to the Root CA. 
    
    .PARAMETER CertificateStore
        Pass the certificateStore name to search for certificate only in certain Store. Default is "My" and you can pass "" to search in all stores. 
    
            
    .EXAMPLE
        Get-CertificatePath -CertificateName "mywebsite*" -Recurse -CertificateStore ""

            Searchs all certificates on the local machine and filter them out to find the certificate that matches the name passed (using the "Subject" property).
            If more than one certificate matches, they will be looped into individually 
            The function will call itself recursively until the issuer and the subject are the same - which means we have reached the Root CA.
    
    .EXAMPLE
        $Computername = "Computer1","Computer2","Computer3"
        $CertificateName = "Mywebsite.MyDomain.com" 
        
        $res = @()         
        $getVert_Def = "Function Get-CertificatePath { ${function:Get-CertificatePath}} "
        $Computername | % {
            $res += icm -ComputerName $_ -ArgumentList $getVert_Def -ScriptBlock {
            Param($getVert_Def)
            .([scriptblock]::Create($getVert_Def)) 
            Get-CertificatePath -CertificateName $using:CertificateName 
            } 
        }
        $res
    
        Runs the Function on remote computers using invoke-command 
        Starts by creating an array of computer names which you would like to remotely run the function against. 
        Creates a parameter to pass the certificate you are looking for
        Create a definition to the function so we can pass it to each remote invoke-command 
        Loop inside the array of computers and pass the function and run it against each one of them using invoke-command. 

   
    .FUNCTIONALITY
        PowerShell Language

    .NOTES
        Credit to Splunk Base for Certificate Authority Situational Awareness
            https://splunkbase.splunk.com/app/3113/       
            https://github.com/nsacyber/Certificate-Authority-Situational-Awareness    
        Author: Amir Joseph Sayes
                www.Ninaronline.com 
        Version: 1.0
        

    .LINK
        https://ninaronline.com/2019/01/01/certificate-trust-issue-when-setting-ssl-relay-with-citrix-xml-service/

    #>
    
    [cmdletbinding()]
    Param ( 
    [parameter(ValueFromPipeline = $True,ValueFromPipeLineByPropertyName = $True)] 
    [string]$CertificateName = "*", 

    [parameter(ValueFromPipeline = $False,ValueFromPipeLineByPropertyName = $True)] 
    [string]$ParentAKI,

    [switch]$Recurse,

    [parameter(ValueFromPipeline = $True,ValueFromPipeLineByPropertyName = $True)] 
    [ValidateSet("TrustedPublisher","Remote Desktop","Root","TrustedDevices","CA","REQUEST","AuthRoot","TrustedPeople","addressbook","My","SmartCardRoot","Trust","Disallowed","SMS","")][string]$CertificateStore = "My"
    ) 
    $result =@() 
    $Called_Cert = @() 
    $Called_Cert = Get-ChildItem -Path Cert:\LocalMachine\$CertificateStore -Recurse | Where {-not $_.PSIsContainer} | 
    Select PSParentPath,FriendlyName,
    @{Name='EnhancedKeyUsageList';Expression={$_.EnhancedKeyUsageList}},
    @{Name='ssl_issuer';Expression={$_.IssuerName.name}},
    @{Name='ssl_end_time';Expression={$_.NotAfter}},
    @{Name='ssl_start_time';Expression={$_.NotBefore}},
    @{Name='ssl_serial';Expression={$_.SerialNumber}},
    @{Name='ssl_publickey_algorithm';Expression={$_.PublicKey.EncodedKeyValue.Oid.FriendlyName}},
    @{N='Public_Key_Size';E={$_.PublicKey.key.keysize}},
    @{Name='Encoded_Key_Parameters';Expression={foreach($value in $_.PublicKey.EncodedParameters.RawData){$value.ToString('X2')}}},
    @{N='Public_Key_Algorithm';E={$_.PublicKey.Oid.FriendlyName}},
    @{Name='ssl_signature_algorithm';Expression={$_.SignatureAlgorithm.FriendlyName}},Thumbprint,
    @{Name='ssl_version';Expression={$_.Version}},
    @{Name='ssl_subject';Expression={$_.Subject}},
    @{Name='ssl_publickey';Expression={foreach($value in $_.PublicKey.EncodedKeyValue.RawData){$value.ToString('X2')}}},
    @{N='ssl_ext_Unique_Identifiers';E={($_.Extensions | Where-Object {$_.Oid.FriendlyName -eq 'Unique Identifiers'}).Format(0)}},
    @{N='ssl_ext_Authority_Key_Identifier';E={($_.Extensions | Where-Object {$_.Oid.FriendlyName -eq 'Authority Key Identifier'}).Format(0)}},
    @{N='ssl_ext_Subject_Key_Identifier';E={($_.Extensions | Where-Object {$_.Oid.FriendlyName -eq 'Subject Key Identifier'}).Format(0)}},
    @{N='ssl_ext_Key_Usage';E={($_.Extensions | Where-Object {$_.Oid.FriendlyName -eq 'Key Usage'}).Format(0)}},
    @{N='ssl_ext_Certificate_Policies';E={($_.Extensions | Where-Object {$_.Oid.FriendlyName -eq 'Certificate Policies'}).Format(0)}},
    @{N='ssl_ext_Policy_Mappings';E={($_.Extensions | Where-Object {$_.Oid.FriendlyName -eq 'Policy Mappings'}).Format(0)}},
    @{N='ssl_ext_Subject_Alternative_Name';E={($_.Extensions | Where-Object {$_.Oid.FriendlyName -eq 'Subject Alternate Name'}).Format(0)}},
    @{N='ssl_ext_Issuer_Alternate_Name';E={($_.Extensions | Where-Object {$_.Oid.FriendlyName -eq 'Issuer Alternate Name'}).Format(0)}},
    @{N='ssl_ext_Subject_Directory_Attributes';E={($_.Extensions | Where-Object {$_.Oid.FriendlyName -eq 'Subject Directory Attributes'}).Format(0)}},
    @{N='ssl_ext_Basic_Constraints';E={($_.Extensions | Where-Object {$_.Oid.FriendlyName -eq 'Basic Constraints'}).Format(0)}},
    @{N='ssl_ext_Name_Constraints';E={($_.Extensions | Where-Object {$_.Oid.FriendlyName -eq 'Name Constraints'}).Format(0)}},
    @{N='ssl_ext_Policy_Constraints';E={($_.Extensions | Where-Object {$_.Oid.FriendlyName -eq 'Policy Constraints'}).Format(0)}},
    @{N='ssl_ext_Extended_Key_Usage';E={($_.Extensions | Where-Object {$_.Oid.FriendlyName -eq 'Extended Key Usage'}).Format(0)}},
    @{N='ssl_ext_CRL_Distribution_Points';E={($_.Extensions | Where-Object {$_.Oid.FriendlyName -eq 'CRL Distribution Points'}).Format(0)}},
    @{N='ssl_ext_Inhibit_Policy';E={($_.Extensions | Where-Object {$_.Oid.FriendlyName -eq 'Inhibit Policy'}).Format(0)}},
    @{N='ssl_ext_Freshest_CRL';E={($_.Extensions | Where-Object {$_.Oid.FriendlyName -eq 'Freshest CRL'}).Format(0)}},
    @{N='ssl_pri_ext_Authority_Information_Access';E={($_.Extensions | Where-Object {$_.Oid.FriendlyName -eq 'Authority Information Access'}).Format(0)}},
    @{N='ssl_pri_ext_Subject_Information_Access';E={($_.Extensions | Where-Object {$_.Oid.FriendlyName -eq 'Subject Information Access'}).Format(0)}},
    @{N='CertShortName';E={($_.Subject.split(",")[0]).trimstart("CN=")}},
    @{N='IssuerShortName';E={($_.IssuerName.name.split(",")[0]).trimstart("CN=")}} | sort Thumbprint -Unique 

        if (!($ParentAKI)) { 
        $Called_Cert = $Called_Cert | where {$_.CertShortName -like "$CertificateName"} 
        }
        elseif ($ParentAKI) {
        $Called_Cert = $Called_Cert | where {$_.ssl_ext_Subject_Key_Identifier -eq $ParentAKI} | select -First 1
        }

        If ($Recurse) {
            #Loop and recursively retrieve the certificates in the chain until Root CA 
            $Called_Cert | % { 
                if ($_.ssl_ext_Authority_Key_Identifier -ne $null) {
                    $CertParentAKI = ($_.ssl_ext_Authority_Key_Identifier.split("=")[1])
                    #Adding the Cert name as a member in the original object 
                    $_ | Add-Member -MemberType NoteProperty -Name 'CertParentAKI' -Value $CertParentAKI 
                }
                else {
                    $CertParentAKI = 0 
                    $_ | Add-Member -MemberType NoteProperty -Name 'CertParentAKI' -Value $CertParentAKI 
                }
            #Output the results 
            $_ 
            #If recurse switch was On and we have not reached the Root CA then call the function and pass the AKI of the issure of the current certificate
            if ($_.CertParentAKI -ne $_.ssl_ext_Subject_Key_Identifier -and $_.CertParentAKI -ne 0) 
            {
                Get-CertificatePath -ParentAKI $_.CertParentAKI -Recurse -CertificateStore "" 
            } 

                } #End Loop
                } 
        else {
        # if no recurse was chosen then just show the results without looping
        $Called_Cert
        }# End If 
}

 

References

https://splunkbase.splunk.com/app/3113/
 https://github.com/nsacyber/Certificate-Authority-Situational-Awareness