Windows Server 2016 – Fixing Missing Root Certificates on Fresh Install

certificate-authorityssl-certificatewindows-server-2016

At the company we never really bothered with the root certificates and were under the impression this is something that's managed along with Windows Updates (and there's WSUS for that) and all was well.

However, today, I've noticed that a fresh Windows Server 2016 install, with all the updates, seems to have only very VERY basic root certificates, to the point where I can't even open Google (on account of not trusting their certificate).

(I haven't checked a fresh Windows 10 installation yet…)

I'm a bit confused by this, as this didn't happen before. Either we've made some poor changes in our GPOs (tho I can't think of anything that would have this effect), or this is something that was recently changed? How should I proceed so that things like Google can be accessed without issues? Do I need to manually add trusted certificates via GPOs now?


Here are some screenshots of what the situation looks like on a fresh server install.

SSL certificate error

General

Path

Cert list

Best Answer

It is ok and expected behavior. By default, only few required certificates are visible in trusted root store. The rest (there are about 300 roots) are installed on demand, when you face them for the first time. There is a hidden copy of root certificates in Crypt32.dll and on Windows Update. There is nothing to worry about.

update:

I've made internal check and found that requested root is embedded in crypt32.dll file. Here is the PowerShell code you can extract embedded certificates from this dll and find expected root:

$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"}

just copy-paste this code to PS console and check if any object is returned/