Recently I wrote a sample function which allows security administrators to dump trusted root certificates from Microsoft web site.

Microsoft maintains a number of predefined trusted root CAs which are members of Microsoft Root Certificate Program. Here are several useful links on TechNet wiki:

In addition, update mechanism is described here: KB931125.

Generally speaking, Microsoft maintains a special certificate trust list (CTL) which is located here. This CTL contains hashes and extended properties for each member of the root certificate program. Also, each certificate is downloadable at:

http://www.download.windowsupdate.com/msdownload/update/v3/static/trustedr/en/<certThumbprint>.crt

When downloading, client extracts CTL (from CAB) and uses it’s information to reflect changes in the program member lists. For example, if local store contains a trusted root CA (not that are installed manually or via group policies) that is not listed in the CTL, the certificate is removed from the store. In the same way, if CTL contains a certificate that is not installed in the system store, it is fetched and installed on first use. Plain and simple.

Recently (several months ago) Microsoft started CTL usage for untrusted certificates. The process is similar, client installs CTL with explicitly revoked cert hashes to Untrusted Certificates store. When a certificate with a corresponding hash is presented to a system, it is permanently rejected. Once again, plain and simple. The only difference is that untrusted certificates are not stored anywhere, Microsoft just maintains their hashes.

I heard some requests from security administrators that they would love to have an ability to download all trusted certs for further inspection. With Windows 8 you can use certutil.exe to download all trusted root certificates, but there is no way for previous versions. Therefore I wrote a script that will work even in Windows XP!

Note: there is no built-in support for CTL neither in .NET or PowerShell, therefore the script relies on a PKI.Core.dll which contains a set of underlying APIs for PowerShell PKI Module and exposes a basic class for CTL. If you have module installed, just import it to a current PS session. Alternatively, you can download this DLL from PSPKI project home page (in the Downloads section, select PSPKI sources) and use Add-Type cmdlet to load it to current PS session.

Here is the code with my own comments:

#####################################################################
# Get-AuthRoot.ps1
# Version 1.0
#
# Downloads trusted root or explicitly disallowed certificates
#
# Vadims Podans (c) 2013
# http://en-us.sysadmins.lv/
#####################################################################
#requires -Version 2.0

function Get-AuthRoot {
<#
.Synopsis
    Downloads and exports trusted root or explicitly diallowed certificates from Microsoft web site.
.Description
    Downloads and exports trusted root or explicitly diallowed certificates from Microsoft web site.
    
    The function connects to Microsoft web site, downloads appropriate certificate trust list (CTL)
    and downloads appropriate certificates (available only for trusted root certificates).
.Parameter Type
    Specifies the type of CTL to download. The value can be either:
    AuthRoot -- to download trusted root certificates.
    Disallowed -- to download a CTL that contains explicitly disallowed/revoked certs (hashes).
.Path
    Specifies the directory path to save downloaded CTL file. This parameter is optional.
.PathToSST
    Specifies the path to a SST file where all trusted root certificates with their
    properties are saved in a serialized store (SST) format.
.Example
    Get-AuthRoot
    
    with no parameters, just returns a X509CTL object.
.Example
    Get-AuthRoot -Type Disallowed
    
    returns a X509CTL object that contains hashes of explicitly revoked certificates.
.Example
    Get-AuthRoot -Path c:\temp -PathToSST c:\temp\authroot.sst
    
    returns a X509CTL object that contains trusted root certificates information, also
    the command saves CTL to 'c:\temp' folder and dumps all certificates defined in CTL
    and saves them to a SST file in 'c:temp\authroot.sst'.
#>
[OutputType('Security.Cryptography.X509Certificates.X509CTL')]
[CmdletBinding()]
    param(
        [ValidateSet('AuthRoot','Disallowed')]
        [string]$Type = "AuthRoot",
        [IO.DirectoryInfo]$Path,
        [IO.FileInfo]$PathToSST
    )
$signature = @"
[DllImport("CRYPT32.DLL", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool CertSetCertificateContextProperty(
    IntPtr pCertContext,
    uint dwPropId,
    uint dwFlags,
    IntPtr pvData
);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct CRYPTOAPI_BLOB
{
    public int cbData;
    public IntPtr pbData;
}
"@
    $ErrorActionPreference = "Stop"
    Add-Type -MemberDefinition $signature -Namespace PKI -Name Cert
    $BaseURL = "http://www.download.windowsupdate.com/msdownload/update/v3/static/trustedr/en/"
    $certs = New-Object Security.Cryptography.X509Certificates.X509Certificate2Collection
    $ptrs = New-Object Collections.ArrayList
    # the function is used just to extract CTL file from a CAB.
    function Export-CabinetFile ($in) {
        $Shell = New-Object -ComObject Shell.Application
        $CabFiles = $Shell.Namespace($in).Items()
        $DestinationFolder = $Shell.Namespace([IO.Path]::GetTempPath())
        $DestinationFolder.CopyHere($CabFiles)
    }
    # We cannot use Invoke-RestMethod since this command should support PowerShell V2.
    function Invoke-RestMethodV2 ($Uri,$OutFile) {
        (New-Object Net.WebClient).DownloadFile($Uri, $OutFile)
    }
    # the function reads certificate properties from CTL and assigns them to a downloaded certificate.
    function Add-Property ($entry) {
        Write-Verbose "Processing entry: $($entry.Thumbprint)"
        # download certificate from Microsoft web site
        $bytes = (New-Object Net.WebClient).DownloadData($BaseURL + $entry.Thumbprint + ".crt")
        $cert = New-Object Security.Cryptography.X509Certificates.X509Certificate2 @(,$bytes)
        # process attributes
        foreach ($attrib in $entry.Attributes) {
            Write-Verbose "Processing property: $($attrib.OID.Value)"
            $fail = $false
            # CertSetCertificateContextProperty function in the dwPropId accepts only last OID octet.
            # The "1.3.6.1.4.1.311.10.11." is assumed as a prefix.
            $id = $attrib.OID.Value.Split(".")[-1]
            # Attribute value in CTL is encoded as e OCTET_STRING, while the CertSetCertificateContextProperty function
            # uses only octet string's inner payload value.
            $asn = New-Object PKI.ASN.ASN1 @(,$attrib.RawData)
            # allocate a buffer in unmanaged memory to store property value and copy property value there.
            # The value will be stored in a CRYPTOAPI_BLOB structure.
            $pbData = [Runtime.InteropServices.Marshal]::AllocHGlobal($asn.Payload.Length)
            [void]$ptrs.Add($pbData)
            [Runtime.InteropServices.Marshal]::Copy($asn.Payload,0,$pbData,$asn.Payload.Length)
            $cb = New-Object PKI.Cert+CRYPTOAPI_BLOB -Property @{
                cbData = $asn.Payload.Length;
                pbData = $pbData
            }
            # allocate a buffer in unmanaged memory to store CRYPTOAPI_BLOB structure and copy the structure there.
            $pvData = [Runtime.InteropServices.Marshal]::AllocHGlobal([Runtime.InteropServices.Marshal]::SizeOf([PKI.Cert+CRYPTOAPI_BLOB]))
            [void]$ptrs.Add($pvData)
            [Runtime.InteropServices.Marshal]::StructureToPtr($cb,$pvData,$true)
            # if the function succeeds, then everything is ok.
            if (![PKI.Cert]::CertSetCertificateContextProperty($cert.handle,$id,0,$pvData)) {
                $exitCode = [Runtime.InteropServices.Marshal]::GetLastWin32Error()
                $fail = $true
                Write-Warning "Failed to set attribute '$($attrib.OID.Value)' on cert '$($cert.Thumbprint)' with error '$exitCode'."
            }
        }
        [void]$certs.Add($cert)
    }
    # we will download and extract CAB file in user's temp folder. Select hardcoded CAB and CTL file names
    # for appropriate type.
    $tmp = [IO.Path]::GetTempPath()
    if ($Type -eq "Disallowed") {
        $CABName = "disallowedcertstl.cab"
        $CTLName = "disallowedcert.stl"
    } else {
        $CABName = "authrootstl.cab"
        $CTLName = "authroot.stl"
    }
    # download CAB, extract containing CTL and instantiate a CTL object.
    Write-Verbose "Downloading '$CABName' file..."
    Invoke-RestMethodV2 -Uri ($BaseURL + $CABName) -OutFile $tmp\$CABName
    Write-Verbose "Extracting CAB..."
    Export-CabinetFile (Join-Path $tmp $CABName)
    Write-Verbose "Instantiating CTL..."
    $ctl = New-Object Security.Cryptography.X509Certificates.X509CTL (Join-Path $tmp $CTLName)
    # remove downloaded data if the previous code succeeds.
    if ($?) {Remove-Item (Join-Path $tmp $CABName), (Join-Path $tmp $CTLName)}
    # process CTL. If selected, save CTL to a specified directory.
    if ($Path) {
        Set-Content -Path $Path\$CTLName -Value $ctl.RawData -Encoding Byte
    }
    # we can download and generate serialized store (SST) only for AuthRoot CTL.
    if ($Type -eq "AuthRoot" -and ![string]::IsNullOrEmpty($PathToSST)) {
        Write-Verbose "Starting CTL entry processing..."
        # pass each entry to Add-Property function to attach properties from CTL to real
        # certificates in the generated serialized store.
        $ctl.Entries | ForEach-Object {Add-Property $_}
        # save generated SST to a file.
        Set-Content -Path $PathToSST -Value $certs.Export("SerializedStore") -Encoding Byte
        Write-Verbose "Phinal!"
        # release allocated unmanaged buffers
        $ptrs | ForEach-Object {[Runtime.InteropServices.Marshal]::FreeHGlobal($_)}
    }
    # output CTL object to pipeline. Just for lulz :)
    $ctl
}

The script may look a bit complex, but the most complex thing there may be only CertSetCertificateContextProperty function usage. Attached help contains several useful examples how to use it. For advanced users there is a documentation for X509CTL class: X509CTL Class.

HTH.


Share this article:

Comments:

Carl Reid

This is really interesting to me. Can you help me understand why the root Certificates are stored in two places?

If you open certificate manager MMC and look in the Trusted Root CA there are aroudn 20 roots. However looking in the CTL store there are hundreds.

Why does the Certificate Manager MMC not show the roots stored in CTL?

Vadims Podans

There are two kind of roots:

  1. trusted root CAs which are used by operating system components (to validate system drivers, for example)
  2. trusted roots who are members of the Microsoft Root Certification programs. They are commercial CAs that issue common certificates, like SSL, email, document and code signing.

In the MMC, they are logically collected (or merged) in the Trusted Root CAs. Starting with Windows Vista, Microsoft reduced the amount of visible roots. They are installed on the system, but not displayed until they are used. It was made for performance purposes, as Windows Certificate Store has performance issues when there are too much certificate. Check the "Crypt32.dll" section in my article: Certificate Chaining Engine, there is explanation about authroot.stl behavior.

Carl Reid

Thanks for the link, that explans a lot. I find high quality and detailed information on this area very hard to find therefore I am very appreciative of the effort you put into documenting your knowledge.

Do you have an index of articles you have written elsewhere that I can refer to ? 

Perhaps you could cross-post the articles here on your blog, at least that way you are in control of them. The link you gave me has CSS issues and I wonder whether it may dissapear at some point?

 

One other minor point, this comment system needs some kind of notification feature when someone has replied to your comment. Perhaps you coudl use Disqus?

Vadims Podans

> The link you gave me has CSS issues and I wonder whether it may dissapear at some point?

it is because TechNet wiki uses SSL, while some links inside (including images and CSS) refer to non-SSL URLs. This is why they are not loaded by default.

> Perhaps you could cross-post the articles here on your blog, at least that way you are in control of them

An original, but not yet updated, article is publushed here: Certificate Chaining Engine — how this works

> One other minor point, this comment system needs some kind of notification feature when someone has replied to your comment. Perhaps you coudl use Disqus?

no chance to use Disqus or any other 3rd party comment board, because they are out of my control. However, I agree that this functionality might be helpful. I haven't implemented this because of low comment flow in my weblog, so it was not in my top prority list. When I will have more spare time, I will implement this.

BTW, if you have any comments/suggestions/etc. about the site itself, please, use Contact form.

 

Carl Reid

One last thought, I find it curious that the certificates are downloaded from a HTTP link: 
http://www.download.windowsupdate.com/msdownload/update/v3/static/trustedr/en/

 

I know that using HTTPS could end up in a paradox where the client cannot download the new roots because the certificate used is not trusted however is there not a risk that these root certicates are being downlaoded over an insecure link which in theory could be spoofed. Are there additional security measures in place to protect the roots?

Vadims Podans

> Are there additional security measures in place to protect the roots?

of course. Like everything else in the cryptography, trust lists are digitally signed, therefore there is no way to spoof the CTL.

Noel Tetreault

I have a requirement to find older certificates and place them in untrusted certificates.  Where could I find the old dell untrusted certs for instance?  It looks like Microsoft no longer includes them in their untrusted ctl download.  Thanks!

Jegan

Is there an updated way to extract certificates from disallowed.sst? I tried running the script but it doesn't run in Powershell nor can I load PKI.Core.dll into my powershell instance. I need to extract the certificates themselves for research purposes.


Post your comment:

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