Windows automatic Root CA updates

Microsoft runs its own Trusted Root Program, defining which Root CAs are considered trusted in Microsoft products. The list of trusted Root CAs and/or the corresponding metadata is updated regularly (updates are published here and made available for download in the form of a Certificate Trust List (CTL) here.

Windows systems can be configured to automatically update their list of trusted Root CAs, via "Automatic Root Certificates Update", a setting that can be configured via policy:

gpedit.msc > Local Computer Policy > Computer Configuration > System > Internet Communication Management > Internet Communication Settings > Turn off Automatic Root Certificates Update

According to the description of the policy setting, having it disabled or not configured will trigger automatic updates via the Windows Update website. The policy editor, especially when it comes to policies not configured, is not always a trustworthy source, so in case of doubt check the corresponding registry entry:

regedit.exe > HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\SystemCertificates\AuthRoot

The DisableRootAutoUpdate value must be 0 for automatic updates to be enabled.


Once enabled, the result can be found in the registry within HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates\AuthRoot. Within the AutoUpdate key, there are a couple of interesting values. The LastSyncTime is the time when the CTL was last updated, and can be read e.g. via PowerShell running:

$regKey = Get-ItemProperty -Path Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates\AuthRoot\AutoUpdate
$lastsync = $regKey.LastSyncTime
$Int64Value = [System.BitConverter]::ToInt64($lastsync, 0)
$date = [DateTime]::FromFileTime($Int64Value)
$date

The above script does not require Administrator rights and will return the friendly formatted date of the last update.

Another interesting values is the EncodedCtl, which includes the last CTL downloaded; in order to read it follow the following steps:

1. Dump the CTL from the registry:

[IO.File]::WriteAllBytes('authroot-local.stl',(Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\SystemCertificates\AuthRoot\AutoUpdate').EncodedCTL)

Tha will generate a filed called authroot-local.stl, the downloaded CTL.

2. Parse the CTL and dump the output to a text file:

certutil -verifyCTL authroot-local.stl > authroot-local.stl.txt

That will generate a text file called authroot-local.stl.txt.

3. Check the text dump, the first lines provide information about the CTL version:

[AuthRootCTL]

SequenceNumber = 1401da11ba19606c20

ThisUpdate = "07.11.2023 21:36"

NextUpdate = EMPTY

SubjectAlgorithm = 1.3.14.3.2.26, "sha1"

  Extensions: 0

ERROR = "Strong signatures: 0"

SignerExpiration = "14.03.2024 19:04", "42,3 Days"

WARNING = "SignerExpiration: Less than 180 Days"

CTLEntries = 480

Within the output we might see in the dump a set of missing certificates (in an example test run 30 of them). These are root certificates referenced in the CTL which cannot be found as files on the system (which should be downloaded from the same location as the CTL), e.g.:

ERROR = "The system cannot find the file specified. 0x80070002 (WIN32: 2 ERROR_FILE_NOT_FOUND)", "ec2c834072af269510ff0ef203ee3170f6789dca.crt"

The system cannot find the file specified. 0x80070002 (WIN32: 2 ERROR_FILE_NOT_FOUND) -- eca2d530a9ab2c7d0e7561644e0ae016a154387d.crt

ERROR = "The system cannot find the file specified. 0x80070002 (WIN32: 2 ERROR_FILE_NOT_FOUND)", "eca2d530a9ab2c7d0e7561644e0ae016a154387d.crt"

The system cannot find the file specified. 0x80070002 (WIN32: 2 ERROR_FILE_NOT_FOUND) -- f6b11c1a8338e97bdbb3a8c83324e02d9c7f2666.crt

ERROR = "The system cannot find the file specified. 0x80070002 (WIN32: 2 ERROR_FILE_NOT_FOUND)", "f6b11c1a8338e97bdbb3a8c83324e02d9c7f2666.crt"

[...]

MissingCerts = 30

If we lookup them up in the text file, using the missing file names from the output above (searching for the names without the .crt extension) there are entries for them, e.g.

[f6b11c1a8338e97bdbb3a8c83324e02d9c7f2666]

CertId = 1.3.6.1.4.1.311.10.11.3, "CERT_SHA1_HASH_PROP_ID"

Subject = "MISSING_CERTIFICATE"

FriendlyName = "TWCA CYBER Root CA"

EKU = 1.3.6.1.5.5.7.3.1, "Server Authentication"

KeyId = 9d8561147cc1626f9768e44f3740e1ade00d5637

SHA256Thumbprint = 3f63bb2814be174ec8b6439cf08d6d56f0b7c405883a5648a334424d6b3ec558

Looks like those are root CAs referenced in the CTL which cannot be found in the system as .crt files and therefore have not been included in the certificate store (e.g. the TWCA CYBER Root CA referenced above is missing in the store in the example above).

If a particular Root CA is expected but missing on a Windows system it could be that it is not added to the certificate store because, although it is mentioned in the CTL, the file cannot be found on the system.

To troubleshoot such a case we can try to download the certificate that failed the same way the process would do, using the <thumbprint>.crt file name:

Invoke-WebRequest -uri http://ctldl.windowsupdate.com/msdownload/update/v3/static/trustedr/en/f6b11c1a8338e97bdbb3a8c83324e02d9c7f2666.crt -outfile f6b11c1a8338e97bdbb3a8c83324e02d9c7f2666.crt

Using the PowerShell command above potential connectivity issues towards Windows Update servers can also be identified.


By default, Windows downloads the CTLs from the Internet via this automatic mechanism ,called the CTL Updater. The public URLs used by the CTL Updater can be made available to clients in case connectivity issues are identified. Connectivity should be in place towards e.g.:            http://ctldl.windowsupdate.com/msdownload/update/v3/static/trustedr/en/disallowedcertstl.cab

http://ctldl.windowsupdate.com/msdownload/update/v3/static/trustedr/en/authrootstl.cab

We can also download a copy of the whole thing to a separate location, to check whether there is any issue with any of the downloads.With certutil we can retrieve the roots from WU:

certutil -generateSSTFromWU rootWU.sst

The generated rootWU.sst file can then be open just double-clicking on it, which will open it using certmgr.msc, and contains all the roots accepted by Microsoft.


NOTE: All the above applies to the full Root CA list, which is the scope of the CTL, but that does not mean that the full list is "active" in the Windows client, the list is used for the so called dynamic or lazy-loading process Windows implements for Root CAs, whereby only a subset of them is active on a system by default, and the rest (based on the latest CTL) is only activated if and when required.

For offline systems and/or download issues, a copy of the CTL and the actual Root CA certificates is also embedded into crypt32.dll. This means that updates only happen when the library itself is updated, which is likely not to be as often as the HTTP based downloads of CTLs. In this case the particular version of the library a system is using might be relevant, and that can be validated running:

PS C:\Windows\System32> [System.Diagnostics.FileVersionInfo]::GetVersionInfo("C:\Windows\System32\crypt32.dll").FileVersion
10.0.22000.1 (WinBuild.160101.0800)
PS C:\Windows\System32> [System.Diagnostics.FileVersionInfo]::GetVersionInfo("C:\Windows\System32\crypt32.dll").FileVersion
10.0.22000.1 (WinBuild.160101.0800)
PS C:\Windows\System32> [System.Diagnostics.FileVersionInfo]::GetVersionInfo("C:\Windows\System32\crypt32.dll").FileVersionRaw
Major  Minor  Build  Revision
-----  -----  -----  --------
10     0      22000  2600

A script to check which roots are "embedded" into crypt32.dll is available here:
$signature = @"
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr LoadLibraryEx(
    String lpFileName,
    IntPtr hFile,
    UInt32 dwFlags
);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr FindResource(
    IntPtr hModule,
    int lpID,
    string lpType
);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern uint SizeofResource(
    IntPtr hModule,
    IntPtr hResInfo
);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr LoadResource(
    IntPtr hModule,
    IntPtr hResInfo
);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool FreeLibrary(
    IntPtr hModule
);
"@
Add-Type -MemberDefinition $signature -Namespace PKI -Name Kernel32
$path = $Env:SystemRoot + "\System32\crypt32.dll"
$hModule = [PKI.Kernel32]::LoadLibraryEx($path,[IntPtr]::Zero,0x2)
$hResInfo = [PKI.Kernel32]::FindResource($hModule,1010,"AUTHROOTS")
$size = [PKI.Kernel32]::SizeOfResource($hModule, $hResInfo)
$resource = [PKI.Kernel32]::LoadResource($hModule, $hResInfo)
$bytes = New-Object byte[] -ArgumentList $size
[Runtime.InteropServices.Marshal]::Copy($resource, $bytes, 0, $size)
$AUTHROOTS = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection
$AUTHROOTS.Import($bytes)
[void][PKI.Kernel32]::FreeLibrary($hModule)
$AUTHROOTS | ?{$_.thumbprint -eq "75E0ABB6138512271C04F85FDDDE38E4B7242EFE"}

After running the code above just typing $AUTHROOTS will dump the value of the variable, displayed as a list of embedded roots:

PS C:\Users\johndoe\Desktop> $AUTHROOTS
Thumbprint                                Subject
----------                                -------
010C0695A6981914FFBF5FC6B0B695EA29E912A6  CN=Hellenic Academic and Research Institutions RootCA 2015, O=Hellenic Academic and Research Institutions Cert. Authority, L=Athens, C=GR
0119E81BE9A14CD8E22F40AC118C687ECBA3F4D8  CN=Microsoft Time Stamp Root Certificate Authority 2014, O=Microsoft Corporation, L=Redmond, S=Washington, C=US
016897E1A0B8F2C3B134665C20A727B7A158E28F  E=info@netlock.hu, CN=NetLock Minositett Kozjegyzoi (Class QA) Tanusitvanykiado, OU=Tanusitvanykiadok, O=NetLock Halozatbiztonsagi Kft., L=Budapest, C=HU
022D0582FA88CE140C0679DE7F1410E945D7A56D  CN=HARICA TLS RSA Root CA 2021, O=Hellenic Academic and Research Institutions CA, C=GR
027268293E5F5D17AAA4B3C3E6361E1F92575EAA  CN=KISA RootCA 1, OU=Korea Certification Authority Central, O=KISA, C=KR
02FAF3E291435468607857694DF5E45B68851868  CN=AddTrust External CA Root, OU=AddTrust External TTP Network, O=AddTrust AB, C=SE
039EEDB80BE7A03C6953893B20D2D9323A4C2AFD  CN=GeoTrust Primary Certification Authority - G3, OU=(c) 2008 GeoTrust Inc. - For authorized use only, O=GeoTrust Inc., C=US
0409565B77DA582E6495AC0060A72354E64B0192  CN=Halcom CA FO, O=Halcom, C=SI
0456F23D1E9C43AECB0D807F1C0647551A05F456  O=ACNLB, C=SI
[…]

Comments

Popular posts from this blog

Decoding OCSP GET requests

Signing a CSR with an Enrollment Agent certificate

Compacting an AD CS database