Thanks for this great find! I also didnât think that there may be an certificate store for the SYSTEM account. But it actually makes sense since this is the default account for Windows Services and the âWWW Publishing Serviceâ (IIS) runs as System. Also, AFAIK IIS uses the Microsoft HTTP Api to register for HTTP Requests which binds the TCP ports as the SYSTEM process (PID 4) which also uses the system account (but I donât know the internals).
The difference between âlocal computerâ and âsystemâ certificate storages (as far as I understand) is that the former is visible to all users except for private keys (and is writeable only with administrator rights), while the latter is visible only to the âsystemâ user account (which also happens for other user accounts).
I have used SysInternals ProcessMonitor to check where the intermediate certificates are stored, and it seems they are stored in the registry at <User>\Software\Microsoft\SystemCertificates\CA\Certificates
.
The SYSTEM account has a well-known SID of S-1-5-18
, so you should find the certificates for the SYSTEM account in HKEY_USERS\S-1-5-18\Software\Microsoft\SystemCertificates\CA\Certificates
, the ones for the current user in HKEY_CURRENT_USER\Software\Microsoft\SystemCertificates\CA\Certificates
and the ones for the Local Computer (which are visible for all users) in HKEY_LOCAL_MACHINE\Software\Microsoft\SystemCertificates\CA\Certificates
.
For example, with the following quick&dirty C# program you can list all intermediate certificates for the Local Computer (all users), SYSTEM and the current User account (it will display the registry key name, Subject, Issuer and Thumbprint of the certificate):
using System;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Win32;
namespace ShowIntermediateCertificates
{
class Program
{
private const string userRegKey = @"Software\Microsoft\SystemCertificates\CA\Certificates";
static void Main(string[] args)
{
CertificateStorePath[] paths =
{
new CertificateStorePath()
{
Name = "Local Computer",
RegKey = userRegKey,
Hive = RegistryHive.LocalMachine
},
new CertificateStorePath()
{
Name = "SYSTEM Account (LocalSystem)",
RegKey = "S-1-5-18\\" + userRegKey,
Hive = RegistryHive.Users
},
new CertificateStorePath()
{
Name = $"Local User ({Environment.UserName})",
RegKey = userRegKey,
Hive = RegistryHive.CurrentUser
}
};
foreach (CertificateStorePath path in paths)
{
Console.WriteLine($"Searching Intermediate Certificates for {path.Name}...");
Console.WriteLine();
Console.WriteLine();
// Open the key.
using (RegistryKey k = RegistryKey.OpenBaseKey(path.Hive, RegistryView.Registry64))
{
using (var basekey = k.OpenSubKey(path.RegKey))
{
string[] subkeys = basekey.GetSubKeyNames();
foreach (var subkey in subkeys)
{
using (var sub = basekey.OpenSubKey(subkey))
{
var value = sub.GetValue("Blob");
if (value is byte[])
{
// Load the certificate.
X509Certificate2 cert = new X509Certificate2((byte[])value);
Console.WriteLine($"Certificate [{subkey}]: Subject={cert.Subject}, Issuer={cert.Issuer}, Thumprint (SHA1)={cert.Thumbprint}");
Console.WriteLine();
}
}
}
}
}
Console.WriteLine();
Console.WriteLine();
Console.WriteLine();
Console.WriteLine();
}
Console.WriteLine("Press a key to exit.");
Console.ReadKey();
}
private struct CertificateStorePath
{
public string Name { get;set;}
public string RegKey { get; set; }
public RegistryHive Hive { get; set; }
}
}
}
Using the registry key, I think you should be able to delete a certificate by deleting the corresponding key.
(It should be possible to rewrite this as a PowerShell script but I donât have much knowledge of powershell.)
However, what still is strange is that
- why intermediate certificates are added spontaneously to the userâs intermediate certificate storage (at least this shouldnât happen for the system account), and
- why SChannel is able to build a chain with the old X1 intermediate whereas other SSL tools will flag that as a chain error. I think this is a bug in the SChannel. Perhaps someone could clarify this with Microsoft?
Thanks!