{"id":12125,"date":"2026-04-04T19:25:54","date_gmt":"2026-04-05T00:25:54","guid":{"rendered":"https:\/\/www.rushworth.us\/lisa\/?p=12125"},"modified":"2026-04-10T19:43:14","modified_gmt":"2026-04-11T00:43:14","slug":"creating-and-using-an-ad-cs-custom-policy-module","status":"publish","type":"post","link":"https:\/\/www.rushworth.us\/lisa\/?p=12125","title":{"rendered":"Creating and Using an AD CS Custom Policy Module"},"content":{"rendered":"<p>This information relates to the custom CA policy module implementation at <a href=\"https:\/\/github.com\/ljr55555\/CustomEkuPolicy\">https:\/\/github.com\/ljr55555\/CustomEkuPolicy<\/a><\/p>\n<h2>Values Used in this Implementation<\/h2>\n<p>The following GUID values are used within the code<\/p>\n<p>LIBID CustomEkuPolicyModule 71e54225-3720-45d5-a181-1ce854b74c58<\/p>\n<p>CLSID CustomEkuPolicyModule bea77360-4ed0-469c-a888-7b5cac3b8776<\/p>\n<p>Project GUID 98d72278-b7ab-4f6b-817c-55dac0796675<\/p>\n<p>Documentation is written while adding the custom module to a CA named UG-IssuingCA-02<\/p>\n<h2>Code Overview<\/h2>\n<p>CustomEkuPolicy.dll is a custom <strong>AD CS policy module<\/strong> implemented as a native <strong>ATL COM DLL<\/strong>. It is designed to work as a <strong>wrapper<\/strong> around Microsoft\u2019s default AD CS policy module rather than replacing that behavior entirely.<\/p>\n<p><strong>Purpose<\/strong><\/p>\n<p>The DLL adds a controlled enhancement to certificate issuance:<\/p>\n<ul>\n<li>preserve normal Microsoft CA policy behavior<\/li>\n<li>preserve compatibility with Venafi\/MS CA workflows<\/li>\n<li>inject <strong>Server Authentication<\/strong> and <strong>Client Authentication<\/strong> EKUs only when appropriate<\/li>\n<\/ul>\n<p><strong>High-level behavior<\/strong><\/p>\n<p>When the CA processes a certificate request, the module does the following:<\/p>\n<ol>\n<li>Loads and delegates to the Microsoft default policy module:\n<ul>\n<li>CertificateAuthority_MicrosoftDefault.Policy<\/li>\n<\/ul>\n<\/li>\n<li>Lets the default policy module evaluate the request normally:\n<ul>\n<li>issuance<\/li>\n<li>pending\/deny decisions<\/li>\n<li>standard AD CS behavior<\/li>\n<\/ul>\n<\/li>\n<li>If the default policy module decides to <strong>issue immediately<\/strong>:\n<ul>\n<li>inspect the request\/certificate context<\/li>\n<li>determine whether the certificate is eligible for EKU injection<\/li>\n<\/ul>\n<\/li>\n<li>Inject the following EKUs only when all conditions are met:\n<ul>\n<li><strong>Server Authentication<\/strong>: 1.3.6.1.5.5.7.3.1<\/li>\n<li><strong>Client Authentication<\/strong>: 1.3.6.1.5.5.7.3.2<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<p><strong>Injection conditions<\/strong><\/p>\n<p>EKU injection occurs only if:<\/p>\n<ul>\n<li>the default Microsoft policy module returned <strong>issue now<\/strong><\/li>\n<li>the certificate is <strong>not<\/strong> a CA certificate<\/li>\n<li>the certificate\u2019s template\/type name is allowed<\/li>\n<li>the certificate does <strong>not already contain<\/strong> an EKU extension<\/li>\n<\/ul>\n<p>If any of those conditions are not met, the request is left unchanged.<\/p>\n<p><strong>Why the wrapper design is used<\/strong><\/p>\n<p>Earlier testing showed that completely replacing Microsoft\u2019s default policy logic caused incompatibility with Venafi.<br \/>\nTo avoid that, this module wraps the default policy module and preserves its normal behavior first, then applies the EKU enhancement afterward.<\/p>\n<p>This design is what allows:<\/p>\n<ul>\n<li>successful issuance through Venafi<\/li>\n<li>retention of normal CA behavior<\/li>\n<li>selective EKU injection<\/li>\n<\/ul>\n<p><strong>Template\/type filtering<\/strong><\/p>\n<p>The module supports a registry-driven allow-list of certificate template\/type names.<\/p>\n<p>Registry path:<\/p>\n<p>HKLM\\SYSTEM\\CurrentControlSet\\Services\\CertSvc\\Configuration\\&lt;CAName&gt;\\PolicyModules\\CustomEkuPolicy.Module<\/p>\n<p>&nbsp;<\/p>\n<p>Registry value:<\/p>\n<p>AllowedTemplateNames<\/p>\n<p>Type:<\/p>\n<p>REG_MULTI_SZ<\/p>\n<p>If the registry value is missing or empty, the module defaults to allowing only:<\/p>\n<p>WebServer<\/p>\n<p>This prevents the module from broadly injecting TLS EKUs into unrelated certificate types such as code-signing or other special-purpose leaf certificates.<\/p>\n<p><strong>Important implementation details<\/strong><\/p>\n<ul>\n<li>Implemented as ICertPolicy2<\/li>\n<li>Built as an in-process COM DLL loaded by certsrv.exe<\/li>\n<li>Uses ATL for COM plumbing<\/li>\n<li>Uses CryptoAPI to ASN.1-encode the EKU extension<\/li>\n<li>Supports binary extension values returned by AD CS as either:\n<ul>\n<li>VT_BSTR<\/li>\n<li>VT_ARRAY | VT_UI1<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p><strong>Failure behavior<\/strong><\/p>\n<p>If EKU injection fails:<\/p>\n<ul>\n<li>the module logs the failure<\/li>\n<li>the default Microsoft issuance decision is preserved<\/li>\n<\/ul>\n<p>This means the module is effectively <strong>fail-open<\/strong> with respect to EKU injection, to avoid breaking certificate issuance workflows.<\/p>\n<p><strong>Result<\/strong><\/p>\n<p>In normal operation, certificates of the allowed type that do not already contain EKUs are issued with:<\/p>\n<ul>\n<li>Server Authentication<\/li>\n<li>Client Authentication<\/li>\n<\/ul>\n<p>Other certificate types are left unchanged.<\/p>\n<h2>Function Reference Table<\/h2>\n<table>\n<thead>\n<tr>\n<th><strong>Function<\/strong><\/th>\n<th><strong>Location<\/strong><\/th>\n<th><strong>Called from<\/strong><\/th>\n<th><strong>Calls<\/strong><\/th>\n<th><strong>Purpose<\/strong><\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>DllMain<\/td>\n<td>dllmain.cpp<\/td>\n<td>Windows loader<\/td>\n<td>_AtlModule.DllMain<\/td>\n<td>Standard DLL entry point for process\/thread attach and detach.<\/td>\n<\/tr>\n<tr>\n<td>DllCanUnloadNow<\/td>\n<td>dllmain.cpp<\/td>\n<td>COM runtime<\/td>\n<td>_AtlModule.DllCanUnloadNow<\/td>\n<td>Standard COM export used to determine whether the DLL can be unloaded.<\/td>\n<\/tr>\n<tr>\n<td>DllGetClassObject<\/td>\n<td>dllmain.cpp<\/td>\n<td>COM runtime<\/td>\n<td>_AtlModule.DllGetClassObject<\/td>\n<td>Standard COM export that returns the class factory for CCustomEkuPolicyModule.<\/td>\n<\/tr>\n<tr>\n<td>DllRegisterServer<\/td>\n<td>dllmain.cpp<\/td>\n<td>COM registration tools \/ COM runtime if invoked<\/td>\n<td>_AtlModule.DllRegisterServer<\/td>\n<td>Standard COM registration export. Manual registry registration is used operationally instead.<\/td>\n<\/tr>\n<tr>\n<td>DllUnregisterServer<\/td>\n<td>dllmain.cpp<\/td>\n<td>COM registration tools \/ COM runtime if invoked<\/td>\n<td>_AtlModule.DllUnregisterServer<\/td>\n<td>Standard COM unregistration export.<\/td>\n<\/tr>\n<tr>\n<td>GetTypeInfoCount<\/td>\n<td>CustomEkuPolicyModule.cpp<\/td>\n<td>COM\/IDispatch clients if they query dispatch metadata<\/td>\n<td>none<\/td>\n<td>Minimal IDispatch stub implementation. Returns no type information.<\/td>\n<\/tr>\n<tr>\n<td>GetTypeInfo<\/td>\n<td>CustomEkuPolicyModule.cpp<\/td>\n<td>COM\/IDispatch clients<\/td>\n<td>none<\/td>\n<td>Minimal IDispatch stub implementation. Not implemented for this module.<\/td>\n<\/tr>\n<tr>\n<td>GetIDsOfNames<\/td>\n<td>CustomEkuPolicyModule.cpp<\/td>\n<td>COM\/IDispatch clients<\/td>\n<td>none<\/td>\n<td>Minimal IDispatch stub implementation. Not used by AD CS in this design.<\/td>\n<\/tr>\n<tr>\n<td>Invoke<\/td>\n<td>CustomEkuPolicyModule.cpp<\/td>\n<td>COM\/IDispatch clients<\/td>\n<td>none<\/td>\n<td>Minimal IDispatch stub implementation. Not used by AD CS in this design.<\/td>\n<\/tr>\n<tr>\n<td>Initialize<\/td>\n<td>CustomEkuPolicyModule.cpp<\/td>\n<td>AD CS when the policy module is initialized<\/td>\n<td>EnsureDefaultPolicyLoaded, LoadConfiguration, m_spDefaultPolicy-&gt;Initialize<\/td>\n<td>Stores the CA config string, loads registry-based configuration, loads the Microsoft default policy module, and delegates initialization to the default module.<\/td>\n<\/tr>\n<tr>\n<td>VerifyRequest<\/td>\n<td>CustomEkuPolicyModule.cpp<\/td>\n<td>AD CS for each certificate request<\/td>\n<td>EnsureDefaultPolicyLoaded, m_spDefaultPolicy-&gt;VerifyRequest, TryInjectDefaultEku, LogHr<\/td>\n<td>Main policy processing function. Delegates request handling to the Microsoft default policy module and, if the request is immediately issued, attempts EKU injection.<\/td>\n<\/tr>\n<tr>\n<td>GetDescription<\/td>\n<td>CustomEkuPolicyModule.cpp<\/td>\n<td>AD CS \/ administrative interfaces<\/td>\n<td>none<\/td>\n<td>Returns a human-readable description of the policy module.<\/td>\n<\/tr>\n<tr>\n<td>ShutDown<\/td>\n<td>CustomEkuPolicyModule.cpp<\/td>\n<td>AD CS during service shutdown \/ module unload<\/td>\n<td>m_spDefaultPolicy-&gt;ShutDown<\/td>\n<td>Delegates shutdown to the default policy module.<\/td>\n<\/tr>\n<tr>\n<td>GetManageModule<\/td>\n<td>CustomEkuPolicyModule.cpp<\/td>\n<td>AD CS \/ administrative interfaces<\/td>\n<td>m_spDefaultPolicy2-&gt;GetManageModule<\/td>\n<td>Pass-through to the default policy module\u2019s ICertPolicy2::GetManageModule() when available.<\/td>\n<\/tr>\n<tr>\n<td>EnsureDefaultPolicyLoaded<\/td>\n<td>CustomEkuPolicyModule.cpp<\/td>\n<td>Initialize, VerifyRequest<\/td>\n<td>CLSIDFromProgID, CoCreateInstance, QueryInterface<\/td>\n<td>Loads the Microsoft default policy module (CertificateAuthority_MicrosoftDefault.Policy) and caches interface pointers.<\/td>\n<\/tr>\n<tr>\n<td>LoadConfiguration<\/td>\n<td>CustomEkuPolicyModule.cpp<\/td>\n<td>Initialize<\/td>\n<td>LoadAllowedTemplateNamesFromRegistry<\/td>\n<td>Loads custom registry-driven configuration for the wrapper module.<\/td>\n<\/tr>\n<tr>\n<td>LoadAllowedTemplateNamesFromRegistry<\/td>\n<td>CustomEkuPolicyModule.cpp<\/td>\n<td>LoadConfiguration<\/td>\n<td>RegOpenKeyExW, RegQueryValueExW, RegCloseKey, LogInfo<\/td>\n<td>Reads AllowedTemplateNames from the CA policy module registry key and populates the internal allow-list. Falls back to WebServer if not configured.<\/td>\n<\/tr>\n<tr>\n<td>TryInjectDefaultEku<\/td>\n<td>CustomEkuPolicyModule.cpp<\/td>\n<td>VerifyRequest<\/td>\n<td>CoCreateInstance, SetContext, IsCaRequest, GetTemplateName, IsTemplateAllowed, IsExtensionPresent, BuildDefaultEkuEncoded, SetExtensionBytes, LogInfo, LogHr<\/td>\n<td>Performs the conditional EKU injection logic after default policy approval.<\/td>\n<\/tr>\n<tr>\n<td>GetExtensionBytes<\/td>\n<td>CustomEkuPolicyModule.cpp<\/td>\n<td>IsExtensionPresent, IsCaRequest, GetTemplateName<\/td>\n<td>ICertServerPolicy::GetCertificateExtension, ICertServerPolicy::GetCertificateExtensionFlags, VariantClear<\/td>\n<td>Reads a certificate\/request extension value from ICertServerPolicy as binary.<\/td>\n<\/tr>\n<tr>\n<td>SetExtensionBytes<\/td>\n<td>CustomEkuPolicyModule.cpp<\/td>\n<td>TryInjectDefaultEku<\/td>\n<td>SysAllocStringByteLen, ICertServerPolicy::SetCertificateExtension, VariantClear<\/td>\n<td>Writes a binary certificate\/request extension value back through ICertServerPolicy. Used to inject EKU.<\/td>\n<\/tr>\n<tr>\n<td>IsExtensionPresent<\/td>\n<td>CustomEkuPolicyModule.cpp<\/td>\n<td>TryInjectDefaultEku<\/td>\n<td>GetExtensionBytes, VariantClear<\/td>\n<td>Checks whether a specific extension OID already exists in the current request\/certificate context.<\/td>\n<\/tr>\n<tr>\n<td>BuildDefaultEkuEncoded<\/td>\n<td>CustomEkuPolicyModule.cpp<\/td>\n<td>TryInjectDefaultEku<\/td>\n<td>CryptEncodeObjectEx<\/td>\n<td>Builds and ASN.1-encodes the default EKU extension containing Server Auth and Client Auth OIDs.<\/td>\n<\/tr>\n<tr>\n<td>IsCaRequest<\/td>\n<td>CustomEkuPolicyModule.cpp<\/td>\n<td>TryInjectDefaultEku<\/td>\n<td>GetExtensionBytes, VariantBinaryToBytes, CryptDecodeObjectEx, VariantClear, LocalFree<\/td>\n<td>Reads and decodes Basic Constraints to determine whether the request is for a CA certificate.<\/td>\n<\/tr>\n<tr>\n<td>VariantBinaryToBytes<\/td>\n<td>CustomEkuPolicyModule.cpp<\/td>\n<td>IsCaRequest, GetTemplateName<\/td>\n<td>SysStringByteLen, SafeArrayGetLBound, SafeArrayGetUBound, SafeArrayAccessData, SafeArrayUnaccessData<\/td>\n<td>Normalizes binary values returned from AD CS (VT_BSTR or `VT_ARRAY<\/td>\n<\/tr>\n<tr>\n<td>GetTemplateName<\/td>\n<td>CustomEkuPolicyModule.cpp<\/td>\n<td>TryInjectDefaultEku<\/td>\n<td>GetExtensionBytes, VariantBinaryToBytes, CryptDecodeObjectEx, VariantClear, LocalFree<\/td>\n<td>Reads and decodes the certificate template\/type name extension (1.3.6.1.4.1.311.20.2).<\/td>\n<\/tr>\n<tr>\n<td>IsTemplateAllowed<\/td>\n<td>CustomEkuPolicyModule.cpp<\/td>\n<td>TryInjectDefaultEku<\/td>\n<td>EqualsIgnoreCase<\/td>\n<td>Checks whether the decoded template\/type name matches one of the allowed names from configuration.<\/td>\n<\/tr>\n<tr>\n<td>EqualsIgnoreCase<\/td>\n<td>CustomEkuPolicyModule.cpp<\/td>\n<td>IsTemplateAllowed<\/td>\n<td>towlower<\/td>\n<td>Case-insensitive string comparison helper for template\/type matching.<\/td>\n<\/tr>\n<tr>\n<td>LogEventWord<\/td>\n<td>CustomEkuPolicyModule.cpp<\/td>\n<td>LogInfo, LogHr<\/td>\n<td>RegisterEventSourceW, ReportEventW, DeregisterEventSource<\/td>\n<td>Low-level Windows Event Log writer for the CustomEkuPolicy source.<\/td>\n<\/tr>\n<tr>\n<td>LogInfo<\/td>\n<td>CustomEkuPolicyModule.cpp<\/td>\n<td>Multiple internal functions<\/td>\n<td>LogEventWord<\/td>\n<td>Convenience wrapper for informational event logging.<\/td>\n<\/tr>\n<tr>\n<td>LogHr<\/td>\n<td>CustomEkuPolicyModule.cpp<\/td>\n<td>Multiple internal functions<\/td>\n<td>StringCchPrintfW, LogEventWord<\/td>\n<td>Convenience wrapper for logging HRESULT failures to the Windows Event Log.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>Code Flow<\/h2>\n<p>This section describes how the main code paths in CustomEkuPolicy.dll work.<\/p>\n<p><strong>1. DLL load and COM registration<\/strong><\/p>\n<p>The DLL is an ATL COM in-process server.<\/p>\n<p>Key file:<\/p>\n<ul>\n<li>dllmain.cpp<\/li>\n<\/ul>\n<p>Key responsibilities:<\/p>\n<ul>\n<li>exports standard COM entry points:\n<ul>\n<li>DllMain<\/li>\n<li>DllCanUnloadNow<\/li>\n<li>DllGetClassObject<\/li>\n<li>DllRegisterServer<\/li>\n<li>DllUnregisterServer<\/li>\n<\/ul>\n<\/li>\n<li>exposes the COM class:\n<ul>\n<li>CCustomEkuPolicyModule<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>The class is registered in the ATL object map with:<\/p>\n<p>OBJECT_ENTRY_AUTO(CLSID_CustomEkuPolicyModule, CCustomEkuPolicyModule)<\/p>\n<p>This is what allows AD CS to instantiate the policy module through COM.<\/p>\n<p><strong>2. AD CS loads the policy module<\/strong><\/p>\n<p>When Certificate Services starts, AD CS reads the configured active policy module from the registry and creates the COM object.<\/p>\n<p>Main class:<\/p>\n<ul>\n<li>CCustomEkuPolicyModule<\/li>\n<\/ul>\n<p>Implemented interface:<\/p>\n<ul>\n<li>ICertPolicy2<\/li>\n<\/ul>\n<p>This class is the entry point for all CA policy decisions handled by the DLL.<\/p>\n<p><strong>3. Initialize()<\/strong><\/p>\n<p>Function:<\/p>\n<ul>\n<li>CCustomEkuPolicyModule::Initialize(BSTR strConfig)<\/li>\n<\/ul>\n<p>Purpose:<\/p>\n<ul>\n<li>store the CA configuration string<\/li>\n<li>load configuration from registry<\/li>\n<li>load the Microsoft default policy module<\/li>\n<li>delegate initialization to the default module<\/li>\n<\/ul>\n<p>Flow:<\/p>\n<ol>\n<li>Save strConfig<\/li>\n<li>Call EnsureDefaultPolicyLoaded()<\/li>\n<li>Call LoadConfiguration()<\/li>\n<li>Call the default module\u2019s Initialize()<\/li>\n<\/ol>\n<p>This ensures the wrapper is fully initialized before the CA begins handling requests.<\/p>\n<p><strong>4. EnsureDefaultPolicyLoaded()<\/strong><\/p>\n<p>Function:<\/p>\n<ul>\n<li>EnsureDefaultPolicyLoaded()<\/li>\n<\/ul>\n<p>Purpose:<\/p>\n<ul>\n<li>create an instance of the Microsoft default policy module<\/li>\n<\/ul>\n<p>How it works:<\/p>\n<ol>\n<li>Resolve the ProgID:<\/li>\n<\/ol>\n<p>CertificateAuthority_MicrosoftDefault.Policy<\/p>\n<ol>\n<li>Convert it to a CLSID using CLSIDFromProgID<\/li>\n<li>Create the COM object with CoCreateInstance<\/li>\n<li>Store:\n<ul>\n<li>ICertPolicy<\/li>\n<li>optionally ICertPolicy2<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<p>Why this matters:<\/p>\n<ul>\n<li>This is the core wrapper behavior<\/li>\n<li>Instead of replacing Microsoft policy behavior, the module delegates to it first<\/li>\n<\/ul>\n<p><strong>5. LoadConfiguration()<\/strong><\/p>\n<p>Function:<\/p>\n<ul>\n<li>LoadConfiguration()<\/li>\n<\/ul>\n<p>Purpose:<\/p>\n<ul>\n<li>load custom module behavior from registry<\/li>\n<\/ul>\n<p>Currently this loads:<\/p>\n<ul>\n<li>AllowedTemplateNames<\/li>\n<\/ul>\n<p>Supporting function:<\/p>\n<ul>\n<li>LoadAllowedTemplateNamesFromRegistry()<\/li>\n<\/ul>\n<p>Flow:<\/p>\n<ol>\n<li>Read the CA name from strConfig<\/li>\n<li>Build registry path:<\/li>\n<\/ol>\n<p>HKLM\\SYSTEM\\CurrentControlSet\\Services\\CertSvc\\Configuration\\&lt;CAName&gt;\\PolicyModules\\CustomEkuPolicy.Module<\/p>\n<ol>\n<li>Read AllowedTemplateNames as REG_MULTI_SZ<\/li>\n<li>Store values in:\n<ul>\n<li>m_allowedTemplateNames<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<p>Fallback behavior:<\/p>\n<ul>\n<li>if the value is missing or unusable, default to:\n<ul>\n<li>WebServer<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>This controls which certificate types are eligible for EKU injection.<\/p>\n<p><strong>6. VerifyRequest()<\/strong><\/p>\n<p>Function:<\/p>\n<ul>\n<li>CCustomEkuPolicyModule::VerifyRequest(&#8230;)<\/li>\n<\/ul>\n<p>This is the most important function in the module.<\/p>\n<p>Purpose:<\/p>\n<ul>\n<li>let Microsoft default policy evaluate the request<\/li>\n<li>preserve its result<\/li>\n<li>attempt EKU injection only after successful default issuance<\/li>\n<\/ul>\n<p>Flow:<\/p>\n<ol>\n<li>Call EnsureDefaultPolicyLoaded()<\/li>\n<li>Call the default module\u2019s:<\/li>\n<\/ol>\n<p>m_spDefaultPolicy-&gt;VerifyRequest(&#8230;)<\/p>\n<ol>\n<li>If the default module fails:\n<ul>\n<li>return the failure unchanged<\/li>\n<\/ul>\n<\/li>\n<li>If the default module returns a disposition other than:<\/li>\n<\/ol>\n<p>VR_INSTANT_OK<\/p>\n<p>then exit without modification<\/p>\n<ol>\n<li>If the request is being issued immediately:\n<ul>\n<li>call TryInjectDefaultEku(Context)<\/li>\n<\/ul>\n<\/li>\n<li>If EKU injection fails:\n<ul>\n<li>log the failure<\/li>\n<li>preserve the default issue decision<\/li>\n<\/ul>\n<\/li>\n<li>Return success<\/li>\n<\/ol>\n<p><strong>7. TryInjectDefaultEku()<\/strong><\/p>\n<p>Function:<\/p>\n<ul>\n<li>TryInjectDefaultEku(LONG Context)<\/li>\n<\/ul>\n<p>Purpose:<\/p>\n<ul>\n<li>inspect the certificate request\/certificate context<\/li>\n<li>determine whether EKU injection should happen<\/li>\n<li>add the EKU extension if appropriate<\/li>\n<\/ul>\n<p>Flow:<\/p>\n<ol>\n<li>Create ICertServerPolicy<\/li>\n<li>Call SetContext(Context)<\/li>\n<li>Determine whether the request is a CA certificate:\n<ul>\n<li>IsCaRequest()<\/li>\n<\/ul>\n<\/li>\n<li>If CA cert:\n<ul>\n<li>skip<\/li>\n<\/ul>\n<\/li>\n<li>Read certificate type\/template name:\n<ul>\n<li>GetTemplateName()<\/li>\n<\/ul>\n<\/li>\n<li>Check whether template\/type is allowed:\n<ul>\n<li>IsTemplateAllowed()<\/li>\n<\/ul>\n<\/li>\n<li>If not allowed:\n<ul>\n<li>skip<\/li>\n<\/ul>\n<\/li>\n<li>Check whether EKU already exists:\n<ul>\n<li>IsExtensionPresent(&#8220;2.5.29.37&#8221;)<\/li>\n<\/ul>\n<\/li>\n<li>If EKU already exists:\n<ul>\n<li>skip<\/li>\n<\/ul>\n<\/li>\n<li>Build the encoded EKU extension:<\/li>\n<\/ol>\n<ul>\n<li>BuildDefaultEkuEncoded()<\/li>\n<\/ul>\n<ol>\n<li>Write the extension:<\/li>\n<\/ol>\n<ul>\n<li>SetExtensionBytes(&#8220;2.5.29.37&#8221;, &#8230;)<\/li>\n<\/ul>\n<p>Only if all of those conditions succeed does the module inject the EKU.<\/p>\n<p><strong>8. IsCaRequest()<\/strong><\/p>\n<p>Function:<\/p>\n<ul>\n<li>IsCaRequest(ICertServerPolicy* pServer, bool&amp; isCa)<\/li>\n<\/ul>\n<p>Purpose:<\/p>\n<ul>\n<li>prevent EKU injection on CA certificates<\/li>\n<\/ul>\n<p>How it works:<\/p>\n<ol>\n<li>Read Basic Constraints extension:\n<ul>\n<li>OID 2.5.29.19<\/li>\n<\/ul>\n<\/li>\n<li>Decode it with CryptoAPI<\/li>\n<li>Check fCA<\/li>\n<li>Set isCa = true if CA certificate<\/li>\n<\/ol>\n<p>This protects subordinate CA and CA certificate requests from receiving inappropriate TLS EKUs.<\/p>\n<p><strong>9. GetTemplateName()<\/strong><\/p>\n<p>Function:<\/p>\n<ul>\n<li>GetTemplateName(ICertServerPolicy* pServer, std::wstring&amp; templateName)<\/li>\n<\/ul>\n<p>Purpose:<\/p>\n<ul>\n<li>identify the certificate type\/template name<\/li>\n<\/ul>\n<p>How it works:<\/p>\n<ol>\n<li>Read extension:\n<ul>\n<li>1.3.6.1.4.1.311.20.2<\/li>\n<\/ul>\n<\/li>\n<li>Convert returned binary value into bytes<\/li>\n<li>Decode the string value<\/li>\n<li>Return the template\/type name<\/li>\n<\/ol>\n<p>Example expected values:<\/p>\n<ul>\n<li>WebServer<\/li>\n<\/ul>\n<p>This is the main filter used to decide whether EKU injection is allowed.<\/p>\n<p><strong>10. IsTemplateAllowed()<\/strong><\/p>\n<p>Function:<\/p>\n<ul>\n<li>IsTemplateAllowed(const std::wstring&amp; templateName) const<\/li>\n<\/ul>\n<p>Purpose:<\/p>\n<ul>\n<li>compare the certificate type name against the configured allow-list<\/li>\n<\/ul>\n<p>How it works:<\/p>\n<ul>\n<li>case-insensitive comparison against m_allowedTemplateNames<\/li>\n<\/ul>\n<p>If the template\/type is not in the allow-list, the request is left unchanged.<\/p>\n<p><strong>11. IsExtensionPresent()<\/strong><\/p>\n<p>Function:<\/p>\n<ul>\n<li>IsExtensionPresent(ICertServerPolicy* pServer, LPCWSTR wszOid, bool&amp; present)<\/li>\n<\/ul>\n<p>Purpose:<\/p>\n<ul>\n<li>determine whether a specific extension already exists<\/li>\n<\/ul>\n<p>Used for:<\/p>\n<ul>\n<li>checking whether 2.5.29.37 Enhanced Key Usage is already present<\/li>\n<\/ul>\n<p>If it is already present, the module does not modify it.<\/p>\n<p>This preserves explicitly requested EKUs.<\/p>\n<p><strong>12. BuildDefaultEkuEncoded()<\/strong><\/p>\n<p>Function:<\/p>\n<ul>\n<li>BuildDefaultEkuEncoded(BYTE** ppbEncoded, DWORD* pcbEncoded)<\/li>\n<\/ul>\n<p>Purpose:<\/p>\n<ul>\n<li>ASN.1-encode the default EKU list<\/li>\n<\/ul>\n<p>EKUs encoded:<\/p>\n<ul>\n<li>Server Authentication\n<ul>\n<li>1.3.6.1.5.5.7.3.1<\/li>\n<\/ul>\n<\/li>\n<li>Client Authentication\n<ul>\n<li>1.3.6.1.5.5.7.3.2<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>How it works:<\/p>\n<ol>\n<li>Build CERT_ENHKEY_USAGE<\/li>\n<li>Encode it using:\n<ul>\n<li>CryptEncodeObjectEx<\/li>\n<\/ul>\n<\/li>\n<li>Return the encoded blob<\/li>\n<\/ol>\n<p>This produces the value written into extension 2.5.29.37.<\/p>\n<p><strong>13. SetExtensionBytes()<\/strong><\/p>\n<p>Function:<\/p>\n<ul>\n<li>SetExtensionBytes(ICertServerPolicy* pServer, LPCWSTR wszOid, const BYTE* pbData, DWORD cbData, LONG extFlags)<\/li>\n<\/ul>\n<p>Purpose:<\/p>\n<ul>\n<li>write a binary certificate extension back into the current request\/certificate context<\/li>\n<\/ul>\n<p>How it works:<\/p>\n<ol>\n<li>Convert the encoded bytes into a binary BSTR<\/li>\n<li>Wrap in a VARIANT<\/li>\n<li>Call:\n<ul>\n<li>ICertServerPolicy::SetCertificateExtension(&#8230;)<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<p>This is the actual point where EKU injection occurs.<\/p>\n<p><strong>14. VariantBinaryToBytes()<\/strong><\/p>\n<p>Function:<\/p>\n<ul>\n<li>VariantBinaryToBytes(const VARIANT&amp; var, std::vector&lt;BYTE&gt;&amp; bytes)<\/li>\n<\/ul>\n<p>Purpose:<\/p>\n<ul>\n<li>normalize AD CS binary extension values into a byte vector<\/li>\n<\/ul>\n<p>Why it exists:<\/p>\n<ul>\n<li>AD CS may return binary property values as either:\n<ul>\n<li>VT_BSTR<\/li>\n<li>VT_ARRAY | VT_UI1<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>This helper makes downstream decoding logic reliable and avoids earlier failures caused by assuming only one format.<\/p>\n<p><strong>15. Logging<\/strong><\/p>\n<p>Functions:<\/p>\n<ul>\n<li>LogEventWord()<\/li>\n<li>LogInfo()<\/li>\n<li>LogHr()<\/li>\n<\/ul>\n<p>Purpose:<\/p>\n<ul>\n<li>write operational\/debug events to the Windows Application log<\/li>\n<\/ul>\n<p>Used for:<\/p>\n<ul>\n<li>initialization failures<\/li>\n<li>EKU injection failures<\/li>\n<li>important decision points<\/li>\n<\/ul>\n<p>This was added mainly to make runtime behavior observable and troubleshoot CA\/AD CS integration.<\/p>\n<p><strong>16. Failure model<\/strong><\/p>\n<p>The wrapper preserves Microsoft default behavior wherever possible.<\/p>\n<p>Important principle:<\/p>\n<ul>\n<li><strong>default policy decision comes first<\/strong><\/li>\n<li>EKU injection is <strong>best effort<\/strong><\/li>\n<li>if EKU injection fails, issuance is not blocked unless the default policy blocked it<\/li>\n<\/ul>\n<p>This makes the module effectively <strong>fail-open for EKU injection<\/strong>, while still preserving standard CA behavior.<\/p>\n<p><strong>Summary of end-to-end request flow<\/strong><\/p>\n<p>In simplified form, the code path is:<\/p>\n<ol>\n<li>AD CS loads CustomEkuPolicy.dll<\/li>\n<li>Initialize() loads the Microsoft default policy module and configuration<\/li>\n<li>On each request, VerifyRequest() calls the Microsoft default policy module<\/li>\n<li>If Microsoft policy chooses immediate issuance:\n<ul>\n<li>inspect request<\/li>\n<li>check CA\/not-CA<\/li>\n<li>check template\/type allow-list<\/li>\n<li>check existing EKU<\/li>\n<li>inject Server Auth + Client Auth EKU if eligible<\/li>\n<\/ul>\n<\/li>\n<li>Return the Microsoft default issuance result<\/li>\n<\/ol>\n<h2>Build<\/h2>\n<p>Install <strong>Build Tools for Visual Studio 2022<\/strong> with the <strong>Desktop development with C++<\/strong> workload, including the MSVC v143 toolset, Windows SDK, and ATL support.<\/p>\n<p>Build the project from the <strong>x64 Native Tools Command Prompt for VS 2022<\/strong> using msbuild CustomEkuPolicy.vcxproj \/p:Configuration=Release \/p:Platform=x64<\/p>\n<p><a href=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/custompolicy-build.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-12137\" src=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/custompolicy-build.jpg\" alt=\"\" width=\"1113\" height=\"626\" srcset=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/custompolicy-build.jpg 1113w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/custompolicy-build-300x169.jpg 300w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/custompolicy-build-1024x576.jpg 1024w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/custompolicy-build-768x432.jpg 768w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/custompolicy-build-750x422.jpg 750w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/custompolicy-build-480x270.jpg 480w\" sizes=\"auto, (max-width: 1113px) 100vw, 1113px\" \/><\/a><\/p>\n<p>The equivalent of make clean is<\/p>\n<p>msbuild CustomEkuPolicy.vcxproj \/t:Clean \/p:Configuration=Release \/p:Platform=x64<\/p>\n<p><a href=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/custompolicy-clean.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-12138\" src=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/custompolicy-clean.jpg\" alt=\"\" width=\"1116\" height=\"416\" srcset=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/custompolicy-clean.jpg 1116w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/custompolicy-clean-300x112.jpg 300w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/custompolicy-clean-1024x382.jpg 1024w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/custompolicy-clean-768x286.jpg 768w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/custompolicy-clean-750x280.jpg 750w\" sizes=\"auto, (max-width: 1116px) 100vw, 1116px\" \/><\/a><\/p>\n<h2>Installation<\/h2>\n<p>Copy the file into a location on the CA server. I am using a path under c:\\program files\\EKUPolicy\\CustomEkuPolicy<\/p>\n<p>Create a REG file to import the COM registration<\/p>\n<p>Windows Registry Editor Version 5.00<\/p>\n<p>[HKEY_CLASSES_ROOT\\CLSID\\{BEA77360-4ED0-469C-A888-7B5CAC3B8776}]<\/p>\n<p>@=&#8221;CustomEkuPolicyModule Class&#8221;<\/p>\n<p>&#8220;ProgID&#8221;=&#8221;CustomEkuPolicy.Module&#8221;<\/p>\n<p>&#8220;VersionIndependentProgID&#8221;=&#8221;CustomEkuPolicy.Module&#8221;<\/p>\n<p>[HKEY_CLASSES_ROOT\\CLSID\\{BEA77360-4ED0-469C-A888-7B5CAC3B8776}\\InprocServer32]<\/p>\n<p>@=&#8221;C:\\\\Program Files\\\\EKUPolicy\\\\CustomEkuPolicy\\\\CustomEkuPolicy.dll&#8221;<\/p>\n<p>&#8220;ThreadingModel&#8221;=&#8221;Both&#8221;<\/p>\n<p>[HKEY_CLASSES_ROOT\\CustomEkuPolicy.Module]<\/p>\n<p>@=&#8221;CustomEkuPolicyModule Class&#8221;<\/p>\n<p>&#8220;CLSID&#8221;=&#8221;{BEA77360-4ED0-469C-A888-7B5CAC3B8776}&#8221;<\/p>\n<p>[HKEY_CLASSES_ROOT\\CustomEkuPolicy.Module\\CLSID]<\/p>\n<p>@=&#8221;{BEA77360-4ED0-469C-A888-7B5CAC3B8776}&#8221;<\/p>\n<p>In an elevated command prompt, import the reg file and verify the values are present<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"990\" height=\"583\" class=\"wp-image-12129\" src=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/word-image-12125-4.png\" srcset=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/word-image-12125-4.png 990w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/word-image-12125-4-300x177.png 300w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/word-image-12125-4-768x452.png 768w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/word-image-12125-4-750x442.png 750w\" sizes=\"auto, (max-width: 990px) 100vw, 990px\" \/><\/p>\n<p>Back up the current registry settings<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"972\" height=\"85\" class=\"wp-image-12130\" src=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/word-image-12125-5.png\" srcset=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/word-image-12125-5.png 972w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/word-image-12125-5-300x26.png 300w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/word-image-12125-5-768x67.png 768w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/word-image-12125-5-750x66.png 750w\" sizes=\"auto, (max-width: 972px) 100vw, 972px\" \/><\/p>\n<p>Add key for custom module<\/p>\n<p>reg add &#8220;HKLM\\SYSTEM\\CurrentControlSet\\Services\\CertSvc\\Configuration\\&lt;CAName&gt;\\PolicyModules\\CustomEkuPolicy.Module&#8221; \/f<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"1006\" height=\"64\" class=\"wp-image-12131\" src=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/word-image-12125-6.png\" srcset=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/word-image-12125-6.png 1006w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/word-image-12125-6-300x19.png 300w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/word-image-12125-6-768x49.png 768w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/word-image-12125-6-750x48.png 750w\" sizes=\"auto, (max-width: 1006px) 100vw, 1006px\" \/><\/p>\n<p>Add WebServer to the registry key for the custom policy module <em>unless<\/em> you want to rely on the DLL defaults<\/p>\n<p>reg add &#8220;HKLM\\SYSTEM\\CurrentControlSet\\Services\\CertSvc\\Configuration\\&lt;CANAME&gt;\\PolicyModules\\CustomEkuPolicy.Module&#8221; \/v AllowedTemplateNames \/t REG_MULTI_SZ \/d &#8220;WebServer\\0&#8221; \/f<\/p>\n<p>And activate the custom module:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"996\" height=\"69\" class=\"wp-image-12132\" src=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/word-image-12125-7.png\" srcset=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/word-image-12125-7.png 996w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/word-image-12125-7-300x21.png 300w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/word-image-12125-7-768x53.png 768w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/word-image-12125-7-750x52.png 750w\" sizes=\"auto, (max-width: 996px) 100vw, 996px\" \/><\/p>\n<p>Restart the certificate server service<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"988\" height=\"575\" class=\"wp-image-12133\" src=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/word-image-12125-8.png\" srcset=\"https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/word-image-12125-8.png 988w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/word-image-12125-8-300x175.png 300w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/word-image-12125-8-768x447.png 768w, https:\/\/www.rushworth.us\/lisa\/wp-content\/uploads\/2026\/04\/word-image-12125-8-750x436.png 750w\" sizes=\"auto, (max-width: 988px) 100vw, 988px\" \/><\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This information relates to the custom CA policy module implementation at https:\/\/github.com\/ljr55555\/CustomEkuPolicy Values Used in this Implementation The following GUID values are used within the code LIBID CustomEkuPolicyModule 71e54225-3720-45d5-a181-1ce854b74c58 CLSID CustomEkuPolicyModule bea77360-4ed0-469c-a888-7b5cac3b8776 Project GUID 98d72278-b7ab-4f6b-817c-55dac0796675 Documentation is written while adding the custom module to a CA named UG-IssuingCA-02 Code Overview CustomEkuPolicy.dll is a custom AD &hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1525],"tags":[2187,2186,2185],"class_list":["post-12125","post","type-post","status-publish","format-standard","hentry","category-windows","tag-active-directory-certificate-services","tag-ad-cs","tag-windows-ad-cs"],"_links":{"self":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/12125","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=12125"}],"version-history":[{"count":5,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/12125\/revisions"}],"predecessor-version":[{"id":12140,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=\/wp\/v2\/posts\/12125\/revisions\/12140"}],"wp:attachment":[{"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=12125"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=12125"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.rushworth.us\/lisa\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=12125"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}