Hello S-1-1-0, here is another blog post in the PSPKI v3.7 enhancement blog series. Today I will talk about another interesting work done in PSPKI.


In one of my previous posts, I’ve outlined a very general roadmap for PKI library and the move towards the .NET Core. There are several obstacles for the move and biggest one is tight coupling with COM interfaces.

  • About 146% of Microsoft ADCS API is implemented as COM interfaces which are not good for any .NET Core project. It is very Windows-specific technology and can match Win32 interop. Neither Win32, nor COM are easy to use directly in .NET/PowerShell projects. In this version, I’ve started a work on abstracting (decoupling) internal code from direct access to COM stuff by adding another layer of abstraction using .NET interfaces.
  • Documentation. MSDN documentation on ADCS API is very generic and unclear in most cases. There is better documentation for Distributed COM (DCOM) interfaces published by Open Specifications team.

ADCS API are implemented as low-level DCOM (where server and client can run on different computers) which are very hard to use are not available from managed platform such as .NET. For that purposes are used simplified COM wrappers around DCOM. In some ways, DCOM and COM interface members match (though, not necessary), so the documentation can be reused to some extent. By naming convention, DCOM interfaces are ended with D letter. For example, main ADCS management interface is ICertAdminD and corresponding client-side COM wrapper is ICertAdmin.

  • COM object handling and memory leaks. Once you instantiate a COM object, its reference count is increased (via IUnknown base interface) behind CLR runtime and not visible to CLR garbage collector. This may lead to memory leaks and you have to explicitly dispose COM object from memory when it is no longer needed.

However, during research and countless of tests I found that even this documentation is misleading or otherwise incorrect and I worked with docs team to fix docs issues I found:


What’s new?

PSPKI actively uses COM interfaces as helpers to perform various ADCS management operations. In v3.7, I refactored the code and moved helpers to individual .NET interfaces and classes. There is a brand new namespace called SysadminsLV.PKI.Dcom. This namespace contains adapted managed interfaces and enumerations for COM interfaces. Every interface that is supposed to be a wrapper around DCOM gets D letter as well. This denotes the purpose of interface and avoids possible interface name duplicates (such as ICertConfig COM interface). COM-based implementations for these interfaces are located in SysadminsLV.PKI.Dcom.Implementations namespace. There can be non COM-based implementations. For example, ICertRegManagerD is implemented in COM-based class: CertSrvRegManagerD and in remote registry-based class: CertSrvRegManager.

For all managed implementation COM objects are disposed automatically when necessary.

Certification Authority autodiscovery

The most useful interface we start with is ICertConfigD interface which is an improved wrapper around ICertConfig COM interface. Implementations of this interface are responsible for Certification Authority discovery and basic information retrieval from registration sources. This interface doesn’t check for connectivity and only reads CA registration sources. Implemented sources are defined in CertConfigLocation enumeration. Here is a quick example in my demo lab:

PS C:\> $CertConfig = New-Object SysadminsLV.PKI.Dcom.Implementations.CertConfigD
PS C:\> $CertConfig.EnumConfigEntries()

ComputerName         : dc1.contoso.com
CommonName           : Contoso CA
DisplayName          : Contoso CA
Description          :
OrganizationUnit     :
Organization         :
StateProvince        :
Locality             :
Country              :
ConfigString         : dc1.contoso.com\Contoso CA
Flags                : DsEntry, Registry, RegistryParent
SanitizedName        : Contoso CA
ShortName            : Contoso CA
SanitizedShortName   : Contoso CA
WebEnrollmentServers : {}

ComputerName         : dc2.contoso.com
CommonName           : contoso-DC2-CA

PS C:\> $CertConfig.FindConfigEntryByServerName("stdca01.contoso.com")

ComputerName         : StdCA01.contoso.com
CommonName           : Contoso Standalone CA01
DisplayName          : Contoso Standalone CA01
Description          :
OrganizationUnit     : PKI
Organization         : Contoso Pharmaceuticals
StateProvince        :
Locality             : Riga
Country              : LV
ConfigString         : StdCA01.contoso.com\Contoso Standalone CA01
Flags                : DsEntry
SanitizedName        : Contoso Standalone CA01
ShortName            : Contoso Standalone CA01
SanitizedShortName   : Contoso Standalone CA01
WebEnrollmentServers : {}

PS C:\>

First command retrieves all registered CAs. Alternatively, we can retrieve specific registration by either, CA host or certificate name. In last call I use filtering by host name. This interface is actively used in CertificateAuthority class.

Certification Authority property values

Another very big improvement is wrapping ICertRequest2::GetCAProperty and ICertAdmin2::GetCAProperty methods. This method isn’t very user-friendly, it has a lot of dependencies between parameter values, documentation is unclear and vague. After all, return value for this method is System.Object, so callers must know return type in advance and carefully use explicit cast. I’ve extracted this single method and refactored into separate class with a number of methods that correspond to property names and simplified the usage with strongly-typed return values (instead of generic System.Object). Abstract interface is ICertPropReaderD and implementation in CertPropReaderD class. Class constructor accepts CA configuration string and optional value whether ICertAdmin or ICertRequest underlying COM interface is used. The only difference between two is that ICertAdmin2 requires CA administrator permissions to activate, ICertRequest doesn’t. Constructor defaults to ICertRequest, so no need to have CA admin permissions to call methods in this class.

Certification Authority configuration values

Microsoft ADCS API supports CA configuration management (read and write values) using ICertAdmin2::GetConfigEntry method. When I explored this API, I was unable to access several configuration entries and moved to remote registry functionality as primary method and ICertAdmin::GetConfigEntry as a backup which wasn’t stable enough. After working closely with Microsoft Docs team I was able to match functionality provided by remote registry and this API. As the result, we have two methods to access CA configuration settings:

  • Remote Registry which relies on remote registry and permissions on remote CA server. Can read/write configuration when CA service is stopped. This transport is implemented in CertSrvRegManager class.
  • DCOM which relies on DCOM transport (do not require remote registry). Can read/write configuration only when CA service is running. This transport is implemented in CertSrvRegManagerD class.

Both classes implement same ICertRegManagerD interface and provide same functionality implemented in different ways. Here are features of this interface:

In addition, there is a combined class CertSrvConfigUtil which uses both implementations in fall-back mode. When invoking methods, the class checks if remote registry is accessible. If it is not, class attempts to use DCOM transport. Currently, this composite class supports only reading operations. I will add modification operations in next version. Writing operations are still supported in individual implementations: CertSrvRegManager and CertSrvRegManagerD. Let’s take a look at how it works.

Microsoft CA configuration is stored in registry under HKLM:\SYSTEM\CurrentControlSet\Services\CertSvc\Configuration:


This node contains settings which are common to all instances of Microsoft CA. This is so-called Root Configuration Node. Root node contains Active string property that points to an active CA instance. That is, you may have multiple CA instances on single host and only one instance can be active and running at any time. Every CA instance is represented as subnode under root configuration node and is called an Active Configuration Node:


I’ve translated these two nodes to contexts: root context and active context:

PS C:\> $ConfigReader = New-Object SysadminsLV.PKI.Dcom.Implementations.CertSrvRegManagerD "dc2"
PS C:\> $ConfigReader

ComputerName                                                       IsAccessible ActiveConfig
------------                                                       ------------ ------------
dc2                                                                        True contoso-DC2-CA

PS C:\> $ConfigReader.GetStringConfigEntry("DBDirectory")
PS C:\> $ConfigReader.GetNumericConfigEntry("SetupStatus")
PS C:\>

By default, we refer to root context. Switching between contexts is done by using ICertRegManagerD.SetRootNode method. When forceActive method parameter is True, we switch to active context and to root context otherwise:

PS C:\> $ConfigReader.SetRootNode($true)
PS C:\> $ConfigReader.GetMultiStringConfigEntry("CACertPublicationURLs")
PS C:\> $ConfigReader.GetStringConfigEntry("CRLPeriod")
PS C:\> $ConfigReader.GetNumericConfigEntry("CRLPeriodUnits")
PS C:\> $ConfigReader.GetBooleanConfigEntry("UseDS")
PS C:\>

As we can see in registry tree, there are subnodes under CA node, how do we access them? Easily, every read/write method in ICertRegManagerD interface has optional node parameter. It is set to null if you deal with entries directly under active CA node. In order to access CSP node, for example, you specify subnode or subnode path:

PS C:\> $ConfigReader.GetConfigEntry("Provider","CSP")
Microsoft Software Key Storage Provider
PS C:\> $ConfigReader.GetConfigEntry("CNGHashAlgorithm","CSP")
PS C:\> $ConfigReader.GetConfigEntry("EventFilter","ExitModules\CertificateAuthority_MicrosoftDefault.Exit\SMTP")
PS C:\>

Writing value is as simple as reading:

PS C:\> $ConfigReader.SetConfigEntry

void SetConfigEntry(System.Object data, string entryName, string node)
void ICertRegManagerD.SetConfigEntry(System.Object data, string entryName, string node)

PS C:\> $ConfigReader.SetConfigEntry(5,"CRLPeriod")
PS C:\> $ConfigReader.SetConfigEntry("Days","CRLPeriodUnits")
PS C:\> $ConfigReader.GetConfigEntry("CRLPeriod")
PS C:\> $ConfigReader.GetConfigEntry("CRLPeriodUnits")
PS C:\>

Do not forget that CA service must be restarted to apply changes.

In next post, I will show more features we’ve added in PSPKI module and APIs.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.