Most internal PKIs fail in one of three ways, and all three are self-inflicted. Someone stands up an Enterprise Root CA directly on a domain controller, so the root key is permanently online, reachable by every domain admin, and impossible to protect — one Golden-Cert forgery away from total forest compromise. Or a team builds a proper two-tier hierarchy but wires CDP/AIA to LDAP-only paths that non-domain hosts and pre-authentication VPN clients cannot reach, so “revocation server offline” errors start haunting them the day they onboard a Mac or a Linux build agent. Or — the classic 2 a.m. call — they build the hierarchy correctly, then never plan the CRL lifecycle, and eighteen months later the offline root’s CRL silently expires and certificate validation breaks everywhere at once, even though every certificate is still perfectly valid.
This guide builds the boring, correct thing and then hardens it against the attacks that made AD CS the hottest privilege-escalation target of the decade. You will stand up an offline standalone root CA that lives powered off in a safe, and an enterprise subordinate issuing CA that is domain-joined and does the day-to-day work. You will wire Authority Information Access (AIA) and CRL Distribution Points (CDP) to HTTP-first locations, stand up an OCSP responder, author certificate templates with least-privilege EKUs and permissions, turn on autoenrollment via Group Policy, enable key archival with recovery agents, and — critically — audit and close every ESC1 through ESC8 misconfiguration from the Certified Pre-Owned research that turns a low-privileged user into a Domain Admin using nothing but a certificate request. We finish with NDES/SCEP + Intune for mobile and non-domain devices, the monitoring that catches an expiring CRL before it bites, and a tested backup/restore and root-renewal runbook.
Assumptions throughout: Windows Server 2022, an existing single-forest AD (corp.example.com), and an IIS reverse proxy reachable at pki.corp.example.com for publishing CRLs, CA certificates and OCSP. Every command is real certutil/PowerShell you can paste. Replace names, OIDs and CIDRs with your own — and never copy an OID arc verbatim.
What problem this solves
A PKI is the trust root for a startling amount of infrastructure: 802.1X/EAP-TLS network access, Wi-Fi, VPN, SMB/LDAP-over-TLS, code signing, S/MIME, Windows Hello for Business, domain-controller authentication certificates, and every internal HTTPS endpoint that isn’t using a public CA. When the PKI is wrong, the failures are broad and non-obvious: authentication works for months, then a whole class of clients breaks simultaneously, and the certificates themselves look fine — because the failure is almost always in the validation path (revocation reachability, chain building, template permissions), not the leaf certificate.
What breaks without a correctly designed hierarchy: an online root means a single compromised domain admin can mint a certificate for Administrator and forge Kerberos PKINIT logons forever, undetectably. LDAP-only CDP means non-Windows and pre-auth clients get “revocation offline” and either hard-fail or (worse, if misconfigured) skip revocation entirely. A missed root-CRL renewal is a forest-wide outage with no code change to blame. And a single over-permissive certificate template — ENROLLEE_SUPPLIES_SUBJECT plus Client Authentication EKU plus Authenticated Users: Enroll — is a documented, weaponized path from any user account to Domain Admin (ESC1).
Who hits this: every enterprise running Windows, plus anyone doing EAP-TLS, Intune-managed device certificates, internal service mesh mTLS, or Windows Hello for Business. The stakes are high enough that AD CS is now a first-class objective on every red-team engagement. This article is the build guide and the hardening guide, because in 2026 you cannot responsibly ship one without the other.
To frame the whole field before the deep dive, here is the shape of a two-tier PKI, what each layer owns, and where its characteristic failures live:
| Layer | What it is | Powered / online? | Issues | Characteristic failure |
|---|---|---|---|---|
| Offline root CA | Standalone, workgroup, RSA 4096 self-signed | Off, in a safe; booted a few times/year | Only the issuing-CA cert + its own CRL | Expired root CRL → forest-wide validation break |
| Issuing CA(s) | Enterprise subordinate, domain-joined | Always on | End-entity certs (users, computers, services) | LDAP-only CDP, ESC1-ESC8 template/CA misconfig |
| AIA / CDP / OCSP | HTTP + LDAP publication of certs, CRLs, status | Always reachable | Chain-building + revocation material | Unreachable CDP → “revocation server offline” |
| Templates + GPO | What can be issued, to whom, how | Config in AD | The issuance policy | Over-permissive template = privilege escalation |
| Clients | Auto-enrolling members, NDES/Intune devices | Various | Consume certs | Fail closed on bad chain/revocation |
Learning objectives
By the end of this article you can:
- Justify a two-tier hierarchy versus one-tier and three-tier, and explain exactly why the root is offline and standalone rather than an Enterprise Root on a DC.
- Build the offline root with a correct
CAPolicy.inf(no self-referencing CDP/AIA, long CRL, defined key length and validity) and configure itscertutilregistry so the issuing-CA certificate it signs carries reachable AIA/CDP. - Build the enterprise issuing CA, submit its request to the offline root, install the signed cert, and wire HTTP-first AIA/CDP plus an OCSP responder, avoiding the LDAP-only trap.
- Run CRL publication with discipline — base + delta,
CRLOverlapPeriodsafety margin, and a calendared offline-root CRL renewal that prevents the expired-CRL outage. - Author, version, permission, publish and supersede certificate templates, and drive autoenrollment via GPO for users and computers.
- Configure key archival with one or more Key Recovery Agents (KRA) and recover a lost private key with
certutil -getkey/-recoverkey. - Audit and remediate ESC1-ESC8 (and know ESC9-ESC11 exist), the Certified Pre-Owned attack classes — including
EDITF_ATTRIBUTESUBJECTALTNAME2, dangerous template ACLs,ManageCArights, and NTLM relay to web enrollment. - Integrate NDES/SCEP with Intune for mobile and non-domain devices, and monitor the whole PKI with
pkiview.msc,certutilhealth checks and the right Windows event IDs.
Prerequisites & where this fits
You should be comfortable with core Active Directory: forests, domains, sites, the Configuration naming context, and how Group Policy applies to computers and users. You should be able to run elevated PowerShell, read certutil output, and understand the difference between a standalone CA (no AD integration, manual approval, no templates) and an enterprise CA (AD-integrated, template-driven, supports autoenrollment). Familiarity with X.509 basics — subject, SAN, EKU (Enhanced/Extended Key Usage), key usage, the chain from leaf to root — is assumed.
This sits in the Windows Server security and identity track. It builds directly on a healthy directory: get Active Directory Domain Services forest design and DC promotion and the tiered admin model for an AD DS forest right first, because CA administration belongs in Tier 0 alongside domain controllers. Autoenrollment is delivered by Group Policy managed as code, and the CA and NDES service identities should follow the same discipline you use to eliminate static service credentials with gMSA and Windows LAPS. Certificate-based logon underpins FIDO2 and Windows Hello for Business rollouts, and CA-admin access should be brokered through Privileged Identity Management / PAM.
Here is who owns what during a PKI incident, so you page the right person fast:
| Concern | Lives where | Usual owner | Failure classes it causes |
|---|---|---|---|
| Root key material | Offline root / HSM / safe | PKI + security (Tier 0) | Total trust compromise if leaked |
| Issuing CA config (AIA/CDP/periods) | HKLM\...\CertSvc\Configuration |
PKI team | Unreachable revocation, wrong lifetimes |
| Certificate templates | AD Configuration NC | PKI + app owners | ESC1-ESC4 escalation, wrong EKUs |
CA-level permissions (ManageCA) |
CA security tab | PKI team | ESC7 escalation, rogue issuance |
| HTTP publishing (IIS/CDP) | pki.corp.example.com |
Web / platform | 404 on CRL → validation break |
| Autoenrollment GPO | AD GPO | AD + PKI | Silent non-enrollment, mass reissue |
| NDES / Intune connector | NDES box + Intune | Endpoint team | SCEP failures, ESC8-style relay |
Core concepts
Six mental models make every later decision obvious.
Trust flows down; keys must be protected at the root. Every certificate is trusted because a chain builds from it up to a root your clients already trust. Compromise the root private key and you can forge any certificate in the org — including one whose subjectAltName = Administrator@corp.example.com, which Kerberos PKINIT will honour as a logon for the real Administrator. That is why the root key is generated on a machine that never touches the network and is stored offline. The issuing CA’s key is online (it must be, to issue), so it is treated as replaceable: if compromised you revoke that one subordinate, stand up a new one, and reissue — the root and its trust survive.
Standalone vs enterprise is a real, load-bearing distinction. A standalone CA has no AD dependency: requests go into a Pending queue for manual approval, there are no certificate templates, and the subject comes entirely from the request. That is exactly what you want for an offline root that boots a few times a year. An enterprise CA is domain-joined, reads templates from AD, can auto-issue based on the requester’s AD identity and permissions, and supports autoenrollment. That is exactly what you want for a busy issuing CA.
The certificate carries its own validation pointers, and they are immutable. When the CA issues a cert, it stamps the AIA (where to fetch the issuer’s cert to build the chain) and CDP (where to fetch the CRL to check revocation) into the certificate. You cannot change those URLs after issuance — they are frozen for the life of that certificate. Decide HTTP-first on day one, or you reissue the entire estate later.
Revocation must be both published and reachable. A CRL (Certificate Revocation List) is a signed, time-bounded list of revoked serial numbers. A base CRL is the full list; a delta CRL is just the changes since the last base, so clients refresh cheaply. OCSP (Online Certificate Status Protocol) answers “is this one serial revoked?” over HTTP without downloading a large CRL. All three must be reachable from wherever your clients live — AD-joined or not.
A certificate template is an issuance policy, and its ACL is a security boundary. A template defines the key size, EKUs, subject-name source, validity, and — decisively — who may enrol and whether the enrollee supplies the subject. Get the combination wrong and the template becomes a privilege-escalation primitive. Templates are the number-one AD CS attack surface.
Enterprise trust distribution is automatic — for the stores that matter. Publishing the root to the Certification Authorities container (the “RootCA” store) makes every domain member trust it via Group Policy’s autoenrollment/trust push. Publishing an enterprise issuing CA to the NTAuthCertificates store is what authorizes it to issue logon certificates — a cert not chained to NTAuth cannot be used for smartcard/PKINIT auth even if the chain is otherwise valid.
The vocabulary in one table
| Term | One-line definition | Where it lives | Why it matters |
|---|---|---|---|
| Root CA | Self-signed trust anchor | Offline, workgroup | Compromise = forge anything |
| Issuing (subordinate) CA | Signed by the root, issues end-entity certs | Domain-joined, online | Day-to-day workhorse; revocable |
| AIA | Authority Information Access — where to get the issuer cert | Cert extension + registry | Chain building; also carries OCSP URL |
| CDP | CRL Distribution Point — where to get the CRL | Cert extension + registry | Revocation checking |
| CRL / delta CRL | Signed list of revoked serials / just the changes | Published to CDP | Freshness vs size trade-off |
| OCSP | Per-cert revocation status over HTTP | Online Responder role | Scales better than huge CRLs |
| KSP | Key Storage Provider (software or HSM) | On the CA | Where/how the CA key is protected |
| Template | Issuance policy (EKU, subject, ACL) | AD Configuration NC | The main attack surface |
| Autoenrollment | Silent request/renew via GPO | Client + GPO | Scales cert distribution |
| KRA | Key Recovery Agent — can recover archived keys | Template + CA | Recover lost encryption keys |
| NTAuth store | CAs authorized to issue logon certs | AD NTAuthCertificates |
Required for PKINIT/smartcard |
| NDES/SCEP | Network Device Enrollment Service / SCEP protocol | NDES server + Intune connector | Non-domain / mobile enrollment |
Why two tiers: the offline root and online issuing split
The number of tiers is a deliberate risk-versus-cost decision. Most enterprises land on two. Here is the honest comparison:
| Model | Structure | Pros | Cons | Use when |
|---|---|---|---|---|
| One-tier | Single Enterprise Root CA (often on a DC) | Trivial to build | Root key always online; no revocation of the root; total compromise if breached | Lab only; never production |
| Two-tier | Offline standalone root → online enterprise issuing CA(s) | Root key offline & protected; issuing CA revocable/replaceable; good cost/risk balance | Slightly more build + a calendared root-CRL job | The default for nearly all enterprises |
| Three-tier | Offline root → offline policy CA → online issuing CA(s) | Separate policy boundaries per business unit/CP-CPS; issuing CAs constrained | More machines, more CRLs, more operational overhead | Very large orgs, strict policy separation, regulated multi-BU |
Two rules of hierarchy math prevent most incidents:
A subordinate CA can never issue a certificate whose validity exceeds its own remaining lifetime. Give the issuing CA a 10-year cert so a 2-year end-entity cert requested in year 8 still fits inside it. When the issuing cert crosses roughly half-life, plan its renewal.
The offline root’s CRL validity must be long (26-52 weeks) because nobody wants to boot the root often — but “long” is not “infinite.” A missed root-CRL renewal is a forest-wide outage. It goes on the calendar the day you build it.
The properties that distinguish the two CAs, side by side — this table is the blueprint:
| Property | Offline Root CA | Enterprise Issuing CA |
|---|---|---|
| CA type | Standalone Root | Enterprise Subordinate |
| Domain joined | No (workgroup) | Yes |
| Powered on | Only to sign a sub-CA/renew and republish its CRL | Always |
| Key protection | Software KSP + exported PFX in a safe, or HSM | Software KSP or (better) HSM |
| Key length | RSA 4096 | RSA 4096 (or 3072) |
| Hash | SHA-256 | SHA-256 |
| Self/issued validity | 20 years (self-signed) | 10 years (issued by root) |
| What it issues | Only the subordinate CA cert(s) | End-entity certs (1-2 years typical) |
| CRL period | 26-52 weeks, no delta | 1 week base + 1 day delta |
| CDP/AIA in its own cert | Suppressed (root is a trust anchor) | Points to HTTP + LDAP + OCSP |
| Templates | None (standalone) | Duplicated V2/V3/V4 templates |
Use SHA-256. SHA-1 is deprecated and distrusted by modern clients; SHA-384/512 is cryptographically fine but adds interop friction with some older network gear (EAP supplicants, load balancers), so SHA-256 is the pragmatic default. On key algorithm:
| Algorithm | Interop | Performance | Notes |
|---|---|---|---|
| RSA 2048 | Universal | Fast | Acceptable for end-entity; light for a modern CA |
| RSA 3072 | Universal | Moderate | Good CA-key middle ground |
| RSA 4096 | Universal | Slower signing | Recommended for root & issuing CA keys |
| ECDSA P-256/P-384 | Good but not universal | Fast, small | Some legacy EAP/VPN/appliances still choke on ECC; pilot before committing |
Building the offline root CA
The root is a workgroup (non-domain-joined) machine — a hardened Server Core VM or physical box that will spend its life powered off. Before installing the CA role, drop a CAPolicy.inf into C:\Windows. This file controls the root’s own self-signed certificate and, critically, suppresses CDP/AIA in the root certificate itself. An offline root must not advertise a CRL location in its own cert: it is a trust anchor, nobody revokes it, and a self-referencing CDP just creates a URL that will never resolve.
; C:\Windows\CAPolicy.inf — OFFLINE ROOT
[Version]
Signature="$Windows NT$"
[Certsrv_Server]
RenewalKeyLength=4096
RenewalValidityPeriod=Years
RenewalValidityPeriodUnits=20
CRLPeriod=Weeks
CRLPeriodUnits=26
CRLDeltaPeriod=Days
CRLDeltaPeriodUnits=0
AlternateSignatureAlgorithm=0
LoadDefaultTemplates=0
[PolicyStatementExtension]
Policies=InternalPolicy
[InternalPolicy]
OID=1.3.6.1.4.1.311.21.8.<your>.<unique>.<arc>.1
Notice="This CA is operated by Example Corp under the Example Corp CPS."
URL=http://pki.corp.example.com/cps/cps.html
Every directive in that file, decoded:
| Directive | Value here | What it does | Gotcha if wrong |
|---|---|---|---|
RenewalKeyLength |
4096 | Key size when the CA cert is renewed | Set once; changing later needs a new key |
RenewalValidityPeriod(Units) |
Years / 20 | Root self-signed lifetime | Too short → premature forest-wide renewal |
CRLPeriod(Units) |
Weeks / 26 | Base CRL validity | Too long = stale; too short = frequent boots |
CRLDeltaPeriodUnits |
0 | Disables delta CRLs on the root | Root issues ~1 thing; deltas are pointless overhead |
AlternateSignatureAlgorithm |
0 | Use classic PKCS#1 v1.5 signatures | 1 (PSS) breaks some legacy validators |
LoadDefaultTemplates |
0 | Don’t load default templates | Harmless on standalone; explicit is good |
[PolicyStatementExtension] |
InternalPolicy | Embeds a CPS notice/OID | OID must be a real arc you own — never copy |
Now install the role and configure it as a standalone root:
# On the OFFLINE root (workgroup machine), elevated
Install-WindowsFeature ADCS-Cert-Authority -IncludeManagementTools
Install-AdcsCertificationAuthority `
-CAType StandaloneRootCA `
-CACommonName "Example Corp Root CA" `
-KeyLength 4096 `
-HashAlgorithmName SHA256 `
-CryptoProviderName "RSA#Microsoft Software Key Storage Provider" `
-ValidityPeriod Years `
-ValidityPeriodUnits 20 `
-Force
The parameters that matter, and the choices behind them:
| Parameter | Value | Alternative | Why this choice |
|---|---|---|---|
-CAType |
StandaloneRootCA |
EnterpriseRootCA |
Standalone = no AD dependency, correct for offline |
-KeyLength |
4096 | 2048 / 3072 | Root key longevity over 20 years |
-HashAlgorithmName |
SHA256 | SHA384/512 | Interop-safe modern default |
-CryptoProviderName |
Software KSP | HSM KSP (nShield/Luna) | HSM in production; software only if truly offline + PFX in safe |
-ValidityPeriodUnits |
20 (Years) | 10-30 | Long, so you renew rarely |
-Force |
present | prompt | Non-interactive; scriptable |
Production note: back the root key with a hardware security module (HSM) and use that vendor’s KSP (
SafeNet Key Storage Provider,nCipher Security World Key Storage Provider) instead of the software provider. The software KSP is acceptable only if the root machine is genuinely offline and the key is exported to an encrypted PFX and stored in a safe under dual control. Record the root cert’s thumbprint and serial in the runbook so you can detect substitution.
Set the root’s registry: CRL periods and the issued-cert lifetime
The offline root must be told (a) how long its CRL is valid, and (b) how long the certificates it issues — the subordinate-CA certs — should live. Do this with certutil -setreg:
# CRL period for the offline root: 26 weeks, no delta
certutil -setreg CA\CRLPeriodUnits 26
certutil -setreg CA\CRLPeriod "Weeks"
certutil -setreg CA\CRLDeltaPeriodUnits 0
certutil -setreg CA\CRLDeltaPeriod "Days"
certutil -setreg CA\CRLOverlapUnits 6
certutil -setreg CA\CRLOverlapPeriod "Weeks"
# Validity of certs the root issues (the sub-CA cert): 10 years
certutil -setreg CA\ValidityPeriodUnits 10
certutil -setreg CA\ValidityPeriod "Years"
We will wire the root’s CDP/AIA in the next section (it belongs with the whole AIA/CDP design discussion), then restart and publish. For now the role is installed and the periods are set.
Building the enterprise issuing CA
Move to the domain-joined server that will be the issuing CA. This is a Tier 0 asset: treat it like a domain controller — dedicated host, no other roles, restricted logon, admin access brokered through PIM/PAM. Give it a short CAPolicy.inf to set its own renewal key length and CRL behaviour up front:
; C:\Windows\CAPolicy.inf — ISSUING CA
[Version]
Signature="$Windows NT$"
[Certsrv_Server]
RenewalKeyLength=4096
RenewalValidityPeriod=Years
RenewalValidityPeriodUnits=10
LoadDefaultTemplates=0
AlternateSignatureAlgorithm=0
LoadDefaultTemplates=0 is important here: it stops the enterprise-CA install from auto-publishing the built-in templates (several of which are dangerous defaults, as the ESC section shows). You will publish only the templates you have vetted.
Install the role and configure as Enterprise Subordinate. Because the parent (offline root) is not online, the installer cannot auto-submit — it writes a .req request file to disk:
Install-WindowsFeature ADCS-Cert-Authority -IncludeManagementTools
Install-AdcsCertificationAuthority `
-CAType EnterpriseSubordinateCA `
-CACommonName "Example Corp Issuing CA 01" `
-KeyLength 4096 `
-HashAlgorithmName SHA256 `
-CryptoProviderName "RSA#Microsoft Software Key Storage Provider" `
-OutputCertRequestFile "C:\IssuingCA01.req" `
-Force
The command finishes with a warning that the CA is not yet started because it lacks a certificate. That is expected. Transfer C:\IssuingCA01.req to the offline root (USB, one-way, scanned) and submit it there:
# On the OFFLINE root
certreq -submit "C:\IssuingCA01.req"
# It prints a RequestId and typically "Certificate request is pending: Taken Under Submission"
# Approve it, then retrieve the signed cert:
certutil -resubmit <RequestId>
certreq -retrieve <RequestId> "C:\IssuingCA01.crt"
The standalone-CA submit/approve/retrieve dance, step by step:
| Step | Command (on offline root) | What happens | Confirm |
|---|---|---|---|
| 1. Submit | certreq -submit IssuingCA01.req |
Request enters Pending queue | Note the RequestId |
| 2. Approve | certutil -resubmit <RequestId> |
Standalone CA issues the cert | No error returned |
| 3. Retrieve | certreq -retrieve <RequestId> IssuingCA01.crt |
Writes the signed sub-CA cert | .crt file on disk |
| 4. Grab the chain | copy the root .crt + root .crl too |
Needed to build/validate the chain | Both files present |
Carry IssuingCA01.crt (and the root cert/CRL) back to the issuing CA, install the signed cert, and start the service:
# On the issuing CA
certutil -installcert "C:\IssuingCA01.crt"
Start-Service certsvc
Verify the chain immediately — this is the moment you discover whether the root’s AIA/CDP were correct:
certutil -verify -urlfetch (Get-ChildItem Cert:\LocalMachine\My |
Where-Object Subject -like "*Issuing CA 01*").PSPath
-urlfetch forces the validator to actually download the root cert and CRL from your HTTP AIA/CDP. Look for Verified Issuance Policies and a leaf/chain revocation status of passed, with no ERROR_... lines.
AIA and CDP design: HTTP-first, OCSP, and the LDAP-only trap
This is the step everyone botches, and the one that causes the most painful, delayed outages. By default the CA writes CDP/AIA pointing to LDAP and to a local C:\Windows\System32\CertSrv\CertEnroll path that does not exist for anything off the CA. You want every issued certificate to reference an HTTP location that any client — Windows, macOS, Linux, a pre-authentication VPN client, an appliance — can reach, with LDAP retained as a secondary for AD-joined hosts, and an OCSP URL for cheap per-cert status.
The registry keys are CA\CRLPublicationURLs (CDP) and CA\CACertPublicationURLs (AIA). Each entry is <flags>:<url>, where the numeric flag is a bitmask telling the CA both where to publish and what to stamp into issued certificates/CRLs. Get these bits right and everything works; get them wrong and you either publish nothing or bake an unreachable URL into 12,000 certificates.
The CDP (CRLPublicationURLs) flag bits:
| Bit (dec) | Constant | Meaning |
|---|---|---|
| 1 | SERVERPUBLISH |
CA writes the CRL file to this location |
| 2 | ADDTOCERTCDP |
Add this URL to the CDP extension of issued certs |
| 4 | ADDTOFRESHESTCRL |
Add to the FreshestCRL (delta pointer) of issued certs |
| 8 | ADDTOCRLCDP |
Add to the CDP extension of published CRLs |
| 64 | SERVERPUBLISHDELTA |
CA also writes the delta CRL here |
| 128 | ADDTOIDP |
Add to the Issuing Distribution Point extension of the CRL |
The AIA (CACertPublicationURLs) flag bits:
| Bit (dec) | Constant | Meaning |
|---|---|---|
| 1 | SERVERPUBLISH |
CA writes the CA cert file to this location |
| 2 | ADDTOCERTCDP/AIA |
Add this URL to the AIA extension of issued certs |
| 32 | ADDTOCERTOCSP |
Add this URL as the OCSP locator in the AIA extension |
Common combinations you will actually type, decoded:
| Flag | = bits | Used for | Meaning in plain English |
|---|---|---|---|
1: (CDP) |
1 | Root local file | Publish CRL to disk, don’t stamp any URL |
65: (CDP) |
1+64 | Issuing local file | Publish base and delta CRL to disk |
6: (CDP) |
2+4 | HTTP CDP | Stamp HTTP CRL + delta pointer into certs (don’t publish — can’t write to remote HTTP) |
79: (CDP) |
1+2+4+8+64 | LDAP CDP | Publish base+delta to AD and stamp LDAP into certs/CRLs |
2: (AIA) |
2 | HTTP AIA | Stamp HTTP issuer-cert URL into certs |
3: (AIA) |
1+2 | File/LDAP AIA | Publish the CA cert and stamp the URL |
32: (AIA) |
32 | OCSP | Stamp the OCSP responder URL into certs |
Wire the offline root’s AIA/CDP
Back on the offline root, replace the default CDP/AIA so the sub-CA certificate the root issues points at your HTTP location (the root publishes its files locally; you copy them to IIS by hand):
# On the OFFLINE root
certutil -setreg CA\CRLPublicationURLs "1:C:\Windows\System32\CertSrv\CertEnroll\%3%8%9.crl\n2:http://pki.corp.example.com/cdp/%3%8%9.crl"
certutil -setreg CA\CACertPublicationURLs "1:C:\Windows\System32\CertSrv\CertEnroll\%1_%3%4.crt\n2:http://pki.corp.example.com/cdp/%1_%3%4.crt"
Restart-Service certsvc
certutil -crl
Those %n tokens are CA replacement variables. Memorize the common ones:
| Token | Expands to | Example |
|---|---|---|
%1 |
Server DNS name | root01 |
%2 |
Server short (NetBIOS) name | ROOT01 |
%3 |
Sanitized CA name | Example Corp Root CA |
%4 |
Cert renewal suffix | (1) after a renewal |
%6 |
Configuration container DN | CN=Configuration,DC=corp,DC=example,DC=com |
%7 |
Sanitized short CA name | for LDAP CN |
%8 |
CRL name suffix (key/renewal index) | ties CRL to a specific CA key |
%9 |
Delta-CRL indicator (+) |
marks the delta file |
%10/%11 |
CDP / CA object class | for LDAP DNs |
After certutil -crl, two files appear under C:\Windows\System32\CertSrv\CertEnroll:
Example Corp Root CA.crl— the root CRL<server>_Example Corp Root CA.crt— the root certificate
Copy both off the offline machine to the IIS box, into the folder mapped to http://pki.corp.example.com/cdp/. Then publish the root cert (and CRL) into Active Directory so every domain member trusts it automatically:
# On a DOMAIN-JOINED admin box, with the root .crt and .crl copied over
certutil -dspublish -f "Example Corp Root CA.crt" RootCA
certutil -dspublish -f "Example Corp Root CA.crl"
-dspublish ... RootCA pushes the root into the Certification Authorities container, from which Group Policy distributes it to every member’s Trusted Root store — no manual import anywhere.
Wire the issuing CA’s AIA/CDP and stand up OCSP
On the issuing CA we do want delta CRLs (end-entity certs get revoked) and LDAP publication (clients are domain-joined), plus HTTP-first for everyone else and an OCSP URL. HTTP is listed before LDAP so non-AD clients hit it without waiting for an LDAP timeout:
# On the ISSUING CA — CRL periods first
certutil -setreg CA\CRLPeriodUnits 1
certutil -setreg CA\CRLPeriod "Weeks"
certutil -setreg CA\CRLDeltaPeriodUnits 1
certutil -setreg CA\CRLDeltaPeriod "Days"
certutil -setreg CA\CRLOverlapUnits 12
certutil -setreg CA\CRLOverlapPeriod "Hours"
# CDP: local file (base+delta) ; HTTP first ; LDAP for AD-joined
certutil -setreg CA\CRLPublicationURLs "65:C:\Windows\System32\CertSrv\CertEnroll\%3%8%9.crl\n6:http://pki.corp.example.com/cdp/%3%8%9.crl\n79:ldap:///CN=%7%8,CN=%2,CN=CDP,CN=Public Key Services,CN=Services,%6%10"
# AIA: local file ; HTTP first ; LDAP ; and the OCSP URL (flag 32)
certutil -setreg CA\CACertPublicationURLs "3:C:\Windows\System32\CertSrv\CertEnroll\%1_%3%4.crt\n2:http://pki.corp.example.com/cdp/%1_%3%4.crt\n3:ldap:///CN=%7,CN=AIA,CN=Public Key Services,CN=Services,%6%11\n32:http://pki.corp.example.com/ocsp"
# End-entity validity ceiling (templates usually override, but cap it)
certutil -setreg CA\ValidityPeriodUnits 2
certutil -setreg CA\ValidityPeriod "Years"
Restart-Service certsvc
certutil -crl
Now stand up the Online Responder (OCSP). It can live on the issuing CA or (better) a separate hardened host. It needs the OCSP Response Signing template published and enroll rights granted to the responder computer:
# On the OCSP host
Install-WindowsFeature ADCS-Online-Cert -IncludeManagementTools
Install-AdcsOnlineResponder -Force
# Then in ocsp.msc: add a Revocation Configuration for "Example Corp Issuing CA 01",
# select the signing cert (auto-enrol the OCSP Response Signing template),
# and point it at the issuing CA's CRLs.
When to reach for CRL versus delta versus OCSP:
| Mechanism | How clients use it | Strength | Weakness | Best for |
|---|---|---|---|---|
| Base CRL | Download full list, cache to Next Update | Simple, offline-cacheable | Large when many revocations; staleness window | Small/medium estates |
| Delta CRL | Download small delta between bases | Fresh without re-downloading full list | Extra moving part; some validators ignore deltas | High-revocation-rate CAs |
| OCSP | Ask “is serial N revoked?” per cert | Tiny responses, near-real-time | Needs an always-up responder; privacy (leaks which cert) | Large estates, TLS at scale |
The most expensive lesson in AD CS: the AIA/CDP URLs baked into a certificate are immutable for that certificate’s life. You cannot retroactively add an HTTP CDP to a cert already carrying only LDAP. Decide HTTP-first on day one, list HTTP before LDAP, and you avoid a forced reissuance of the entire estate.
CRL publication discipline: base, delta, overlap, and the expired-CRL outage
The single most important operational fact in this whole article:
When a CRL expires and no fresh one is reachable, every certificate that chains through that CA fails revocation checking, which fails chain validation everywhere — even though the certificates themselves are still valid and unexpired. On the offline root, whose CRL nobody remembers to renew, this is the classic forest-wide TLS/auth outage that arrives with no warning and no code change to blame.
Three periods govern the lifecycle, and the relationship between them is what keeps you safe:
| Setting | What it controls | Issuing CA value | Root CA value |
|---|---|---|---|
CRLPeriod(Units) |
How long a base CRL is valid | 1 Week | 26 Weeks |
CRLDeltaPeriod(Units) |
How long a delta CRL is valid | 1 Day | 0 (disabled) |
CRLOverlapPeriod(Units) |
Publish the next CRL this far before the current expires | 12 Hours | 6 Weeks |
CRLOverlapPeriod is your safety margin. It makes the CA publish the next CRL before the current one expires, so there is always a fresh, valid CRL live even if a publish is briefly delayed. Without overlap, a CRL that expires at 03:00 and republishes at 03:00 leaves a race where clients can momentarily find only an expired CRL. On the issuing CA, 1-week base + 1-day delta + 12-hour overlap means clients refresh constantly and a short CA outage is invisible. On the root, a 6-week overlap inside a 26-week period gives you a wide window to boot the machine and republish.
How the pieces behave, and the failure each prevents:
| Behaviour | Mechanism | If misconfigured |
|---|---|---|
| Clients always find a valid CRL | CRLOverlapPeriod > publish latency |
Momentary “revocation offline” at each expiry |
| Cheap freshness between bases | Delta CRL + FreshestCRL pointer |
Clients re-download the full base each time |
| Delta ties to the right base | %8/%9 suffix tokens in the URL |
Delta and base names collide; validators confused |
| Root CRL survives to next boot | Long CRLPeriod + calendared renewal |
Forest-wide validation break at expiry |
The calendared offline-root CRL renewal
Because the root is powered off, its CRL renewal is a recurring, calendared procedure — not something the machine can do for itself. Roughly every ~5 months (comfortably inside the 26-week window and the 6-week overlap), boot the root and republish its CRL only. Do not renew the CA certificate — that is a separate, rarer event:
# OFFLINE ROOT — recurring CRL renewal (republish the CRL, NOT the CA cert)
certutil -crl
# Copy the regenerated .crl from C:\Windows\System32\CertSrv\CertEnroll
# to the IIS /cdp path, verify it serves over HTTP, then shut the root down.
Validate the freshness after copying to IIS:
# Anywhere with HTTP reach to pki.corp.example.com
Invoke-WebRequest "http://pki.corp.example.com/cdp/Example%20Corp%20Root%20CA.crl" -OutFile $env:TEMP\root.crl
certutil -dump $env:TEMP\root.crl | Select-String "Next Update","This Update"
A future Next Update confirms the republish worked. Put a calendar reminder at half the period so a missed cycle still has slack. This one recurring task is the difference between a PKI that runs for a decade and one that self-destructs.
Certificate templates: versions, permissions, autoenrollment, supersedence
Templates are where issuance policy lives — and where the biggest attack surface hides. Never edit or issue the built-in templates. Duplicate them so you own the version and every setting. Template schema versions matter because features (and safe defaults) differ:
| Template version | Introduced | Key features | Use for |
|---|---|---|---|
| V1 | Windows 2000 | Built-ins, not editable, no autoenroll | Legacy only; avoid |
| V2 | Server 2003 | Editable, autoenrollment, superseding | Broad compatibility |
| V3 | Server 2008 | CNG/KSP, SHA-2, stronger crypto | Modern default |
| V4 | Server 2012+ | Per-template compatibility list, renewal-with-same-key, KSP list | Recommended for new templates |
Authoring a computer/TLS template
Open certtmpl.msc, duplicate Workstation Authentication (or Computer), and configure the tabs:
| Tab / setting | Set to | Why |
|---|---|---|
| Compatibility | CA: Server 2016+, Recipient: Win10/2016+ | Unlocks V4/CNG options |
| General → validity/renewal | 1-2 years, renew at 6 weeks | Fits inside issuing-CA lifetime |
| Cryptography | KSP, RSA 2048 min, request hash SHA-256 | Modern crypto; match fleet capability |
| Subject Name | Build from AD → DNS name (computer) | No enrollee-supplied subject (avoids ESC1) |
| Extensions → Application Policies (EKU) | Only what’s needed: Client Auth 1.3.6.1.5.5.7.3.2, Server Auth 1.3.6.1.5.5.7.3.1 |
Least privilege; no “Any Purpose” |
| Issuance Requirements | CA manager approval for high-value templates | Blocks silent abuse |
| Security | Target group: Read + Enroll + Autoenroll | Scope enrollment tightly |
| Superseded Templates | The old template(s) being replaced | Clean migration on renewal |
Hard-won lesson: a template’s minimum key size is a floor, not a default. Set 4096 on a template but leave clients/HSMs generating 2048 and enrollment simply fails. Match the floor to what your fleet can actually produce.
Template permissions are a security boundary — enumerate exactly who gets what:
| Permission | Grants | Give to | Danger if over-granted |
|---|---|---|---|
| Read | See the template | Enrollees | Low |
| Enroll | Request a cert from it | Intended group only | ESC1 if paired with supplied-subject + auth EKU |
| Autoenroll | Silent request/renew via GPO | Autoenrolling group | Mass unintended issuance |
| Write / Full Control | Modify the template | PKI admins only | ESC4 — attacker edits template into ESC1 |
Publish the vetted template to the issuing CA so it can issue from it:
# Publish a template to the issuing CA (use the template's internal name)
Add-CATemplate -Name "CorpComputerAuth" -Force
# Confirm exactly what this CA will issue
Get-CATemplate | Format-Table Name, Oid
Autoenrollment via Group Policy
Autoenrollment lets domain members silently request and renew certs from templates where they hold Enroll + Autoenroll. Create or edit a GPO linked at the domain or an OU:
Computer Configuration → Policies → Windows Settings → Security Settings → Public Key Policies → Certificate Services Client - Auto-Enrollment
Set Configuration Model: Enabled, and tick both boxes:
| GPO option | Effect | Recommended |
|---|---|---|
| Enroll certificates automatically | Base autoenrollment on | Yes |
| Renew expired certs, update pending, remove revoked | Lifecycle hygiene | Yes |
| Update certificates that use certificate templates | Picks up superseded/changed templates | Yes |
For user certificates, set the matching policy under User Configuration. Then trigger and verify on a test machine:
gpupdate /force
certutil -pulse # kick the autoenrollment engine immediately
Get-ChildItem Cert:\LocalMachine\My | Format-List Subject, Issuer, NotAfter, Template
Supersedence and reissuance
When you change a template (new key size, tighter EKU, corrected subject source), list the old template under Superseded Templates on the new one. On the next autoenrollment cycle, clients holding the old cert are reissued from the new template and the old cert is superseded — a clean, hands-off migration. The alternative (delete-and-hope) causes gaps where machines have no valid cert.
| Reissuance approach | Mechanism | Downtime | When |
|---|---|---|---|
| Supersedence | New template lists old; autoenroll reissues | None | Normal template changes |
| Re-enroll all (certutil) | certutil -pulse after publish |
None | Force a refresh cycle |
| Revoke + reissue | Revoke old serials, autoenroll new | Brief per-cert | Key compromise / algorithm change |
Key archival and recovery agents
By default the CA never sees a private key — the requester generates it locally. That is correct for signing/auth keys (you never want those recoverable). But for encryption keys (S/MIME, EFS), losing the private key means losing access to all data it protected. Key archival lets the CA store an encrypted copy of the private key so a Key Recovery Agent (KRA) can recover it later.
The pieces involved:
| Component | Role | Where configured |
|---|---|---|
| Key Recovery Agent cert | Holds the key that decrypts archived keys | Issued from the “Key Recovery Agent” template |
| CA archival setting | Turns archival on; lists which KRAs can recover | CA properties → Recovery Agents |
| Template “Archive subject’s key” | Marks a template for archival | Template → Request Handling |
| CA database | Stores the encrypted private keys | %windir%\system32\certlog\<CA>.edb |
Enable it end to end:
# 1) Enroll a KRA cert for the recovery operator (from the KRA template), then
# register it on the CA and set how many agents must cooperate:
certutil -setreg CA\KRAFlags +KRAF_ENABLEFOREIGN # allow archival
# In certsrv.msc → CA Properties → Recovery Agents:
# "Archive the key", add the KRA cert(s), set "Number of recovery agents to use".
Restart-Service certsvc
# 2) On the template (Request Handling tab): tick "Archive subject's encryption private key".
Recover a lost key later — a two-person-friendly, two-step process:
# Find the archived key by the cert's serial or the subject:
certutil -getkey <SerialNumberOrSubject> C:\recover\recovered.blob
# The KRA decrypts the blob into a PFX (prompts for a protection password):
certutil -recoverkey C:\recover\recovered.blob C:\recover\recovered.pfx
Guardrails that keep archival from becoming its own risk:
| Guardrail | Why |
|---|---|
| Archive encryption keys only, never signing/auth keys | A recoverable signing key destroys non-repudiation |
Require ≥2 recovery agents (M of N) for high-value data |
Prevents a single rogue KRA from exfiltrating keys |
| Protect the KRA cert like a Tier 0 secret | It can decrypt every archived key |
Audit every -recoverkey operation |
Recovery is a sensitive, logged event |
The ESC1-ESC8 attacks and hardening (Certified Pre-Owned)
In 2021 SpecterOps published Certified Pre-Owned, cataloguing a family of AD CS misconfigurations — ESC1 through ESC8 (the list has since grown to ESC16) — that escalate a low-privileged user to Domain Admin using certificate requests. Tools like Certify, Certipy and PSPKI enumerate them in seconds; assume every attacker who reaches your network runs them. You must audit and close each one. Here is the full table — the misconfiguration, how it is abused, how to find it, and how to fix it:
| ESC | Misconfiguration | How it’s abused | Enumerate / confirm | Hardening fix |
|---|---|---|---|---|
| ESC1 | Template: enrollee supplies subject (ENROLLEE_SUPPLIES_SUBJECT) + client-auth EKU + low-priv Enroll |
Request a cert with SAN = Administrator; authenticate as them (PKINIT) |
Certify.exe find /vulnerable; template flags + ACL |
Remove supplied-subject, or require CA manager approval, or restrict Enroll to non-privileged use |
| ESC2 | Template with Any Purpose EKU or no EKU | Cert usable for anything, incl. client auth | Template EKU list empty or 2.5.29.37.0 |
Set explicit least-privilege EKUs; remove Any Purpose |
| ESC3 | Enrollment Agent template (Certificate Request Agent EKU) enrollable by low-priv users | Enroll “on behalf of” any user → their cert | Template EKU 1.3.6.1.4.1.311.20.2.1 + broad Enroll |
Restrict enrollment-agent templates; use enrollment-agent restrictions on the CA |
| ESC4 | Write/Owner/FullControl ACL on a template | Rewrite the template into an ESC1, then abuse it | ACL audit: non-admins with Write on template objects | Remove dangerous ACEs; only PKI admins write templates |
| ESC5 | Weak ACLs on PKI objects (CA computer object, NTAuthCertificates, OIDs, CA in AD) |
Modify CA config / add rogue CA to NTAuth | ACL audit on CN=Public Key Services,... and CA host |
Tighten ACLs to Tier 0; monitor those objects |
| ESC6 | CA flag EDITF_ATTRIBUTESUBJECTALTNAME2 set |
Any request can inject an arbitrary SAN — org-wide ESC1 | certutil -getreg policy\EditFlags shows the flag |
certutil -setreg policy\EditFlags -EDITF_ATTRIBUTESUBJECTALTNAME2 + restart |
| ESC7 | Low-priv user has ManageCA / ManageCertificates on the CA | Enable ESC6 flag, approve pending, or issue directly | certutil -getreg CA\Security / CA Security tab |
Restrict CA roles to PKI admins; separate officer roles |
| ESC8 | HTTP web enrollment / CES enabled without HTTPS + EPA | NTLM-relay a machine account to the enrollment endpoint → cert for it | Endpoint reachable at /certsrv or /…_CES_…; NTLM allowed |
Require HTTPS + Extended Protection for Authentication; disable web enrollment if unused; disable NTLM |
Two more you should also confirm are closed, because attacker tooling checks them and the fixes overlap with Windows patch state:
| ESC | Misconfiguration | Fix |
|---|---|---|
| ESC9 | Template with CT_FLAG_NO_SECURITY_EXTENSION (no szOID_NTDS_CA_SECURITY_EXT) weakens strong mapping |
Remove the flag; enforce strong certificate mapping (KB5014754) |
| ESC10 | Weak certificate mapping registry (CertificateMappingMethods, StrongCertificateBindingEnforcement) |
Set strong binding to enforcement mode; patch per KB5014754 |
| ESC11 | RPC enrollment (ICertPassage/ICPR) without IF_ENFORCEENCRYPTICERTREQUEST |
Enforce RPC encryption on the CA interface |
Practical hardening you run now
The highest-value, lowest-risk fixes — apply these on day one:
# ESC6: ensure the dangerous SAN flag is NOT set on the CA
certutil -getreg policy\EditFlags # inspect; look for EDITF_ATTRIBUTESUBJECTALTNAME2
certutil -setreg policy\EditFlags -EDITF_ATTRIBUTESUBJECTALTNAME2
Restart-Service certsvc
# ESC1/ESC2/ESC3: enumerate what your templates actually allow
certutil -template # dump templates + flags
# Look for CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT, Any-Purpose EKU, enrollment-agent EKU
# on templates enrollable by broad groups (Domain Users / Authenticated Users).
# ESC7: audit who holds ManageCA / ManageCertificates
certutil -getreg CA\Security # review the officer/manager ACEs
# ESC8: turn on full CA auditing so relay/rogue issuance is visible
certutil -setreg CA\AuditFilter 127
auditpol /set /subcategory:"Certification Services" /success:enable /failure:enable
Restart-Service certsvc
The Windows Security-log event IDs that let you detect abuse (not just prevent it):
| Event ID | Meaning | Watch for |
|---|---|---|
| 4886 | CA received a certificate request | Baseline of who requests what |
| 4887 | CA approved a request and issued a cert | Unexpected SAN/subject; off-hours issuance |
| 4888 | CA denied a request | Recon / probing patterns |
| 4899 | A template was updated | Unplanned template edits (ESC4) |
| 4900 | Template security (ACL) changed | ACL tampering on templates |
| 4768/4769 (DC) | Kerberos TGT/TGS with certificate (PKINIT) | Logon by a forged/anomalous cert |
Design rule to defang ESC1 permanently: for any template a broad group can enrol, the subject must be built from AD, never supplied by the enrollee. Reserve “supply in request” (and any SAN flexibility) for tightly scoped, manager-approved templates enrollable only by a named service group. Then run Certify/Certipy against your own CA on a schedule and treat any “vulnerable” finding as a P1.
NDES, SCEP, and Intune integration
Domain-joined Windows autoenrolls beautifully. Everything else — iOS, Android, macOS, unmanaged or Intune-managed devices, network gear — cannot talk to an enterprise CA over DCOM/LDAP. They speak SCEP (Simple Certificate Enrollment Protocol) over HTTP, and Microsoft’s SCEP server is NDES (Network Device Enrollment Service), a role service of AD CS fronted by IIS.
NDES has several moving parts, and getting the identities right is most of the battle:
| Component | What it is | Notes |
|---|---|---|
| NDES server | IIS app (/certsrv/mscep) that accepts SCEP |
Never co-locate on the issuing CA in production |
| NDES service account | Runs the app pool; enrols on behalf of devices | Use a gMSA; grant Request + the right template permissions |
| CEP Encryption cert | Encrypts the SCEP challenge/response | Auto-enrolled by NDES setup |
| Exchange Enrollment Agent (Offline request) | Lets NDES request on behalf of devices | Auto-enrolled by NDES setup |
| Registry templates | HKLM\SOFTWARE\Microsoft\Cryptography\MSCEP |
EncryptionTemplate, SignatureTemplate, GeneralPurposeTemplate |
| Intune Certificate Connector | Bridges Intune SCEP profiles to NDES | Replaces the static NDES challenge with a validated one |
The classic standalone-NDES weakness is the static challenge password — a single shared secret any device can use. Intune fixes this: the Intune Certificate Connector generates and validates a per-request challenge, so devices never see a reusable secret. The flow:
| Step | What happens |
|---|---|
| 1 | You define a SCEP profile in Intune (template, key size, SAN, EKU, validity) and assign it |
| 2 | The device receives the profile, generates a keypair, and requests a cert from the NDES URL (via the Intune reverse proxy / Certificate Connector) |
| 3 | The Certificate Connector validates the request’s challenge against Intune (device is enrolled, profile matches) |
| 4 | NDES, using its enrollment-agent identity, submits to the issuing CA against the mapped template |
| 5 | The issued cert flows back to the device; Intune records it and manages renewal/revocation |
Install and point NDES at your templates:
# On the NDES server (member server, NOT the issuing CA)
Install-WindowsFeature ADCS-Device-Enrollment, Web-Server -IncludeManagementTools
Install-AdcsNetworkDeviceEnrollmentService `
-ServiceAccountName "corp\gmsa-ndes$" `
-CAConfig "issca01.corp.example.com\Example Corp Issuing CA 01" -Force
# Map NDES to purpose-built templates (internal template names)
reg add "HKLM\SOFTWARE\Microsoft\Cryptography\MSCEP" /v EncryptionTemplate /t REG_SZ /d CorpSCEPEnc /f
reg add "HKLM\SOFTWARE\Microsoft\Cryptography\MSCEP" /v SignatureTemplate /t REG_SZ /d CorpSCEPSig /f
reg add "HKLM\SOFTWARE\Microsoft\Cryptography\MSCEP" /v GeneralPurposeTemplate /t REG_SZ /d CorpSCEPGen /f
iisreset
NDES-specific hardening — NDES is internet-adjacent, so it is squarely in ESC8/relay territory:
| Hardening | Why |
|---|---|
| Front NDES with HTTPS only + Extended Protection | Blocks NTLM relay to the enrollment endpoint |
| Use the Intune Certificate Connector (per-request challenge) | Kills the static-password weakness |
| Run the pool under a gMSA, least template permissions | No standing human credential; scoped issuance |
| Dedicate narrow SCEP templates (no supplied-subject beyond what the profile sets) | Contain what a compromised device can request |
| Never put NDES on the issuing CA | Compartmentalize the internet-facing surface |
Monitoring PKI health: pkiview, certutil, and event IDs
A PKI fails quietly and then loudly. Monitoring turns the loud, forest-wide surprise into a boring calendar item. The single best tool is Enterprise PKI (pkiview.msc): it walks every CA in the hierarchy and checks each AIA, CDP, delta-CRL and OCSP location for reachability and freshness, colour-coding the result.
pkiview.msc status meanings and the action each demands:
| Status | Meaning | Action |
|---|---|---|
| OK | Location reachable, CRL/cert valid and fresh | None |
| Expiring | CRL/cert within its overlap/renewal window | Republish soon (boot root if it’s the root CRL) |
| Expired | CRL/cert past Next Update | Incident — republish immediately |
| Unable to download | AIA/CDP/OCSP URL 404s or times out | Fix IIS path / DNS / firewall |
| Error | Signature/chain problem | Investigate CA cert / trust |
The certutil health commands you keep in the runbook:
| Command | Checks | Look for |
|---|---|---|
certutil -verify -urlfetch <cert> |
Full chain + revocation, fetching AIA/CDP | passed, no ERROR_ lines |
certutil -CAInfo |
CA name, state, exchange/signing certs | CA is running, certs valid |
certutil -getreg CA\CRLPublicationURLs |
The configured CDP URLs | HTTP present and first |
certutil -getreg CA\CRLOverlapUnits |
Overlap safety margin | Non-zero, sensible |
certutil -URL <cert> |
Interactive URL Retrieval Tool (GUI) | AIA/CDP/OCSP all retrieve |
certutil -ping |
CA responds to DCOM requests | Reachable |
Confirm the two AD trust stores are correct — this is what actually makes certs trusted and usable for logon:
# Root is distributed as a trusted root (RootCA container):
certutil -viewstore "ldap:///CN=Certification Authorities,CN=Public Key Services,CN=Services,CN=Configuration,DC=corp,DC=example,DC=com?cACertificate"
# Issuing CA is authorized to issue LOGON certs (NTAuth):
certutil -viewstore "ldap:///CN=NTAuthCertificates,CN=Public Key Services,CN=Services,CN=Configuration,DC=corp,DC=example,DC=com?cACertificate"
What to alert on, and where the signal lives:
| Signal | Source | Threshold |
|---|---|---|
| Any CDP/AIA “Expired”/“Unable to download” | pkiview.msc / scripted certutil -verify |
Immediate page |
| Root CRL Next Update approaching | Scheduled certutil -dump of the HTTP CRL |
Alert at half the overlap window |
| CA service stopped | Service monitor on certsvc |
Immediate |
| Unexpected 4887 (issuance) off-hours or odd SAN | Security log / SIEM | Investigate |
| Template/ACL change (4899/4900) | Security log / SIEM | Confirm it was planned |
| OCSP responder unhealthy | Online Responder mgmt / probe | Immediate |
Backup, restore, and root renewal
A CA without a tested restore plan is a liability. Back up two distinct things: the CA database + logs, and the private key. And back up a third thing everyone forgets: the registry configuration that holds your AIA/CDP URLs and CRL periods — Backup-CARoleService does not capture it.
# Full CA backup: database + private key, into a password-protected PFX
Backup-CARoleService -Path "D:\CABackup\IssuingCA01\$(Get-Date -Format yyyyMMdd)" -Password (Read-Host -AsSecureString)
# The registry config (CDP/AIA/periods) — NOT covered above:
reg export "HKLM\SYSTEM\CurrentControlSet\Services\CertSvc\Configuration" "D:\CABackup\IssuingCA01\CertSvc-config.reg" /y
What each backup artifact covers, and what restores it:
| Artifact | Contains | Backed up by | Restored by |
|---|---|---|---|
CA database (.edb + logs) |
Issued/revoked/pending certs, request history | Backup-CARoleService / certutil -backupDB |
Restore-CARoleService / certutil -restoreDB |
| Private key (PFX) | The CA signing key | Backup-CARoleService / certutil -backupKey |
certutil -importPFX / restore |
| Registry config | AIA/CDP URLs, CRL periods, EditFlags | reg export (manual) |
reg import |
| CAPolicy.inf | Root/issuing policy at build | File copy | File copy |
| Templates | Issuance policy | In AD (backed up with AD) | AD restore / re-author |
Restore path on a rebuilt host (same CA name and key):
# After reinstalling the ADCS role on the replacement server:
Restore-CARoleService -Path "D:\CABackup\IssuingCA01\20260608" -Password (Read-Host -AsSecureString) -Force
reg import "D:\CABackup\IssuingCA01\CertSvc-config.reg"
Restart-Service certsvc
certutil -verify -urlfetch (Get-ChildItem Cert:\LocalMachine\My | Where-Object Subject -like "*Issuing CA 01*").PSPath
For the offline root, DR is simpler and stricter: the root PFX (exported at build) lives in a physical safe under dual control / split knowledge if policy requires; the machine itself is disposable. Record the thumbprint and serial so you can detect substitution.
Renewing the issuing CA certificate
When the issuing CA cert approaches roughly half-life (say year 6 of 10, or definitely before year 8), renew it from the offline root. Renewing with the same key keeps existing chains valid and does not roll the CDP/AIA %4 suffix; renewing with a new key starts a fresh CRL scope and changes the %8 suffix:
| Renewal choice | Effect | Choose when |
|---|---|---|
| Same key | Chain/AIA/CDP names unchanged; simplest | Key still strong and uncompromised |
| New key | New %8/%4 suffixes; fresh CRL; both old+new valid during overlap |
Key compromise, algorithm upgrade, or policy requires periodic rekey |
# On issuing CA: certsrv.msc → right-click CA → All Tasks → Renew CA Certificate
# (choose same key or new key), which writes a .req; OR:
certreq -new RenewalRequest.inf C:\IssuingCA01-renew.req
# Submit to the offline root exactly as in the build (submit/resubmit/retrieve),
# then: certutil -installcert C:\IssuingCA01-renew.crt ; Restart-Service certsvc
Architecture at a glance
Picture the system as three horizontal zones connected by a single, deliberate trust flow.
The vault (top zone) — the offline root. A standalone, workgroup Windows Server that is powered off and locked in a safe. It holds an RSA-4096 self-signed certificate valid 20 years, and its private key never touches the network. Its CAPolicy.inf deliberately strips CDP/AIA from its own certificate — a trust anchor points at nothing. It boots only a few times a year for two jobs: to sign a subordinate CA request, and to republish its 26-week CRL. When it does, a human carries files out on removable media.
The engine room (middle zone) — the issuing CA and its publishing plane. A domain-joined, always-on Enterprise Subordinate CA holding a 10-year certificate signed by the root. It reads certificate templates from the AD Configuration container and issues end-entity certificates — computer/TLS, user, EAP, S/MIME — to autoenrolling members. Beside it sit two supporting services: an Online Responder (OCSP) answering per-certificate revocation over HTTP, and an IIS reverse proxy at pki.corp.example.com that serves the root and issuing CRLs and CA certificates over HTTP. Every certificate this CA issues carries, frozen into its extensions, an AIA (HTTP-first, then LDAP) so validators can build the chain up to the root, a CDP (HTTP-first, then LDAP, with a delta pointer) so they can check the CRL, and an OCSP URL for cheap status. The CA also publishes into two AD stores that make the magic automatic: RootCA (so every member trusts the root) and NTAuthCertificates (so the issuing CA is authorized to mint logon certificates).
The floor (bottom zone) — the consumers. Domain-joined Windows machines silently autoenroll and renew through a Group Policy that flips on the autoenrollment client. Non-domain and mobile devices — iOS, Android, unmanaged laptops — take a different road: an Intune SCEP profile drives them to an NDES server, whose Intune Certificate Connector validates a per-request challenge and submits to the issuing CA on the device’s behalf. Read top to bottom, trust is created once in the vault, delegated to the engine room, published so it is universally reachable, and consumed automatically at the floor. Read it as an attacker and the hot zones light up — the templates and CA permissions in the engine room (ESC1-ESC7) and the internet-adjacent NDES/web-enrollment endpoints (ESC8) are where a certificate request becomes a Domain Admin.
Real-world scenario
A platform team at a 12,000-seat manufacturer — call it Northwind Components — built exactly this two-tier hierarchy, and for eleven months it was flawless. Their mistake was a single default they never revisited: the issuing CA’s CDP/AIA pointed only to LDAP and a local file path, the exact output of the setup wizard. In an all-Windows, all-domain-joined world, LDAP revocation worked, so nobody noticed.
Two projects landed in the same sprint and broke it. Security rolled out an always-on VPN with strict split tunneling, and the OT group onboarded a fleet of macOS and Linux build agents that had to validate the corporate TLS chain. Neither population could reach a domain controller over LDAP: the non-Windows hosts had no LDAP path to AD at all, and the Windows VPN clients, before authenticating, could not reach a DC to fetch the CRL. The symptom was maddeningly intermittent — “revocation server offline” during VPN connect, hard chain-validation failures on the build agents — and the certificates were all perfectly valid. The revocation information was simply unreachable.
The constraint that made it a real incident: they could not change the CDP inside 12,000 already-issued certificates. Those URLs are frozen at issuance. Reissuing 12,000 certs by hand was a non-starter.
The fix was two-phased. First, they added an HTTP CDP/AIA to the issuing CA — listed before LDAP so non-AD clients hit it without an LDAP timeout — and stood up a hardened IIS reverse proxy at pki.corp.example.com serving the CRLs and CA certs from the CertEnroll directory. That fixed every newly issued and renewed certificate immediately. Second, for the existing certs, they leaned on autoenrollment: with a 1-year lifetime and 6-week renewal window, the entire estate rolled to certificates carrying both HTTP and LDAP CDPs within one renewal cycle — no manual reissue. As a bridge during that cycle, they allowed the small pki.corp.example.com HTTP range through the VPN pre-authentication filter so even old, LDAP-only certs could be validated (the LDAP publication was reachable once the CRL was also on HTTP).
The fix was the same HTTP-first ordering shown earlier — 6:http://pki.corp.example.com/cdp/... ahead of 79:ldap:///... — and it became a standing rule: decide HTTP-first on day one, list HTTP before LDAP, and never ship a PKI whose revocation only resolves inside the domain. The postscript: while fixing CDP, their security lead ran Certify against the issuing CA and found a WebServer template duplicated years earlier with ENROLLEE_SUPPLIES_SUBJECT and Authenticated Users: Enroll — a textbook ESC1, exploitable the entire eleven months. They locked it the same afternoon.
Advantages and disadvantages
| Advantages | Disadvantages |
|---|---|
| Root key offline → catastrophic-compromise resistance | Extra build steps + an air-gapped machine to maintain |
| Issuing CA is revocable/replaceable without re-rooting trust | Calendared offline-root CRL renewal is a human dependency |
| Enterprise CA gives templates, autoenrollment, AD trust push | Template/CA misconfig is a real privilege-escalation surface (ESC1-8) |
| HTTP-first AIA/CDP works for any client, anywhere | Requires an always-reachable publishing plane (IIS/OCSP) |
| Full control of lifetimes, EKUs, key archival, OCSP | More parts to monitor (CRL freshness, OCSP, NTAuth) |
| Free with Windows Server (no per-cert licensing) | Real operational maturity required to run safely |
The trade-off that matters most: the offline root and the calendared CRL job feel like overhead until the day an online-root shortcut becomes a forest-wide compromise. Spend the operational discipline; it is cheap insurance against an un-recoverable event.
Hands-on lab
A compact, safe walk-through you can run in a throwaway lab (three VMs: ROOT01 workgroup, ISSCA01 domain-joined, WEB01/IIS for HTTP publishing). Non-destructive; teardown at the end.
1. Build the offline root.
# ROOT01 (workgroup) — place CAPolicy.inf in C:\Windows first (see article), then:
Install-WindowsFeature ADCS-Cert-Authority -IncludeManagementTools
Install-AdcsCertificationAuthority -CAType StandaloneRootCA `
-CACommonName "Example Corp Root CA" -KeyLength 4096 -HashAlgorithmName SHA256 `
-ValidityPeriod Years -ValidityPeriodUnits 20 -Force
certutil -setreg CA\CRLPeriodUnits 26
certutil -setreg CA\CRLPeriod "Weeks"
certutil -setreg CA\CRLPublicationURLs "1:C:\Windows\System32\CertSrv\CertEnroll\%3%8%9.crl\n2:http://pki.corp.example.com/cdp/%3%8%9.crl"
certutil -setreg CA\CACertPublicationURLs "1:C:\Windows\System32\CertSrv\CertEnroll\%1_%3%4.crt\n2:http://pki.corp.example.com/cdp/%1_%3%4.crt"
Restart-Service certsvc; certutil -crl
Expected: Example Corp Root CA.crl and <host>_Example Corp Root CA.crt under C:\Windows\System32\CertSrv\CertEnroll.
2. Publish the root to AD and IIS.
# On a domain box, with the root .crt/.crl copied over:
certutil -dspublish -f "Example Corp Root CA.crt" RootCA
certutil -dspublish -f "Example Corp Root CA.crl"
# Copy both files to WEB01 at the folder served by http://pki.corp.example.com/cdp/
Validate: Invoke-WebRequest http://pki.corp.example.com/cdp/Example%20Corp%20Root%20CA.crl returns 200.
3. Build the issuing CA and get it signed.
# ISSCA01 (domain-joined) — CAPolicy.inf in place, then:
Install-WindowsFeature ADCS-Cert-Authority -IncludeManagementTools
Install-AdcsCertificationAuthority -CAType EnterpriseSubordinateCA `
-CACommonName "Example Corp Issuing CA 01" -KeyLength 4096 -HashAlgorithmName SHA256 `
-OutputCertRequestFile "C:\IssuingCA01.req" -Force
# Move .req to ROOT01:
certreq -submit "C:\IssuingCA01.req" # note RequestId
certutil -resubmit <RequestId>
certreq -retrieve <RequestId> "C:\IssuingCA01.crt"
# Move .crt back to ISSCA01:
certutil -installcert "C:\IssuingCA01.crt"; Start-Service certsvc
4. Wire issuing AIA/CDP + verify the chain.
certutil -setreg CA\CRLPeriodUnits 1; certutil -setreg CA\CRLDeltaPeriodUnits 1
certutil -setreg CA\CRLOverlapUnits 12; certutil -setreg CA\CRLOverlapPeriod "Hours"
certutil -setreg CA\CRLPublicationURLs "65:C:\Windows\System32\CertSrv\CertEnroll\%3%8%9.crl\n6:http://pki.corp.example.com/cdp/%3%8%9.crl\n79:ldap:///CN=%7%8,CN=%2,CN=CDP,CN=Public Key Services,CN=Services,%6%10"
Restart-Service certsvc; certutil -crl
certutil -verify -urlfetch (Get-ChildItem Cert:\LocalMachine\My | Where-Object Subject -like "*Issuing CA 01*").PSPath
Expected: verification reports the chain to the root with revocation passed.
5. Template + autoenrollment. Duplicate Workstation Authentication in certtmpl.msc (build subject from AD, Client Auth EKU, grant Domain Computers: Enroll + Autoenroll), then:
Add-CATemplate -Name "CorpComputerAuth" -Force
# Link/enable the auto-enrollment GPO, then on a test computer:
gpupdate /force; certutil -pulse
Get-ChildItem Cert:\LocalMachine\My | Format-List Subject, Issuer, NotAfter
Expected: the test machine holds a cert issued by “Example Corp Issuing CA 01”.
6. Prove hardening.
certutil -getreg policy\EditFlags # confirm EDITF_ATTRIBUTESUBJECTALTNAME2 is NOT set
certutil -setreg CA\AuditFilter 127; Restart-Service certsvc
pkiview.msc # all AIA/CDP/OCSP nodes should be green
Teardown.
Remove-WindowsFeature ADCS-Cert-Authority -IncludeManagementTools # on ISSCA01 and ROOT01
# Clean AD objects if this was a real forest (lab): remove the CA from
# CN=Enrollment Services / CN=Certification Authorities / NTAuthCertificates,
# and delete published CRLs/certs from WEB01. Discard the VMs.
Common mistakes & troubleshooting
| # | Symptom | Root cause | Confirm (exact) | Fix |
|---|---|---|---|---|
| 1 | Whole org: “revocation server offline”, chains fail | Root or issuing CRL expired | pkiview.msc shows Expired; certutil -dump <crl> past Next Update |
Boot root, certutil -crl, copy to IIS; verify Next Update in future |
| 2 | Non-domain/VPN clients fail validation; domain clients fine | CDP is LDAP-only | certutil -verify -urlfetch fails on the LDAP URL from a non-AD host |
Add HTTP CDP/AIA (HTTP-first); reissue via autoenroll over one cycle |
| 3 | HTTP CRL URL returns 404 | Files never copied to IIS, or wrong %n path |
Browse the URL; compare filename to CertEnroll | Copy .crl/.crt to the mapped folder; fix the URL tokens |
| 4 | Issuing CA won’t start after install | Sub-CA cert not installed / not signed | certutil -CAInfo errors; service stopped |
Complete submit→resubmit→retrieve→installcert; then Start-Service certsvc |
| 5 | certreq -submit says “Taken Under Submission” |
Standalone CA queues requests as Pending | It’s normal | certutil -resubmit <RequestId> then retrieve |
| 6 | Smartcard/PKINIT logon fails though cert is valid | Issuing CA not in NTAuth | certutil -viewstore ...NTAuthCertificates... missing it |
certutil -dspublish -f <IssuingCA>.crt NTAuthCA |
| 7 | New clients don’t trust internal certs | Root not distributed to Trusted Root | certutil -viewstore ...Certification Authorities... missing root |
certutil -dspublish -f <Root>.crt RootCA; gpupdate |
| 8 | Autoenrollment silently does nothing | Missing Enroll/Autoenroll ACE, or GPO not applied | certutil -pulse no cert; check template Security; gpresult |
Grant Enroll+Autoenroll; confirm GPO scope; certutil -pulse |
| 9 | Enrollment fails with key-length error | Template min key size > client capability | Template Cryptography tab vs client CSP/HSM | Lower template floor or upgrade client key provider |
| 10 | Certify/Certipy flags a template “vulnerable” (ESC1) | Supplied-subject + auth EKU + broad Enroll | certutil -template; template flags + ACL |
Remove supplied-subject or require manager approval; restrict Enroll |
| 11 | Any request can inject arbitrary SAN (ESC6) | EDITF_ATTRIBUTESUBJECTALTNAME2 set on CA |
certutil -getreg policy\EditFlags |
certutil -setreg policy\EditFlags -EDITF_ATTRIBUTESUBJECTALTNAME2; restart |
| 12 | Low-priv user can approve/issue (ESC7) | ManageCA/ManageCertificates over-granted |
certutil -getreg CA\Security / CA Security tab |
Remove ACEs; restrict CA roles to PKI admins |
| 13 | Delta CRL ignored / stale | Validator not using delta, or FreshestCRL missing |
certutil -dump <cert> for FreshestCRL; check flag 4 |
Ensure CDP entries include the delta pointer (bit 4) |
| 14 | After renewal, old certs suddenly fail | New key rolled CRL scope; old CRL not published | certutil -getreg CA\CRLPublicationURLs; check %8 files |
Publish both key-suffixed CRLs; keep old CRL live through overlap |
| 15 | NDES/Intune SCEP requests fail | Wrong MSCEP template names or gMSA permissions |
NDES event log; HKLM\...\MSCEP values |
Fix template registry mapping; grant gMSA Request + template Enroll |
Best practices
- Root offline, standalone, in a safe. Never an Enterprise Root on a DC. HSM-back the key in production; software KSP only if truly air-gapped with the PFX in a safe under dual control.
- HTTP-first AIA/CDP, always. List HTTP before LDAP; keep LDAP as a secondary for AD-joined hosts. Add an OCSP URL for scale. Decide this on day one — issued URLs are immutable.
- CRL overlap is mandatory. Set
CRLOverlapPeriodon every CA so a fresh CRL is always live. Calendar the offline-root CRL renewal at half its period. - Duplicate every template; never issue built-ins. Build subject from AD for broadly-enrollable templates; reserve supplied-subject for manager-approved, narrowly-scoped templates.
- Least-privilege EKUs and ACLs. No “Any Purpose”; grant Enroll/Autoenroll only to intended groups; restrict Write/Full Control on templates to PKI admins (closes ESC1/2/4).
- Audit ESC1-ESC11 on a schedule. Run Certify/Certipy against your own CA; treat any “vulnerable” as P1. Confirm
EDITF_ATTRIBUTESUBJECTALTNAME2is off andManageCAis scoped. - Treat the CA as Tier 0. Dedicated host, no extra roles, PIM/PAM-brokered admin, gMSA service identities, full CA auditing (
AuditFilter 127) shipped to SIEM. - Separate NDES from the issuing CA and front it with HTTPS + Extended Protection; use the Intune connector’s per-request challenge, never the static NDES password.
- Archive encryption keys only, with ≥2 recovery agents; never archive signing/auth keys.
- Back up database + key + registry config, and test the restore. Record root thumbprint/serial in the runbook.
- Monitor
pkiview.mscand CRL freshness as first-class alerts, with the root-CRL Next Update on a dashboard.
Security notes
The threat model for AD CS has two faces: key protection and issuance policy. Key protection is why the root is offline and HSM-backed — a stolen root key forges anything, including Kerberos PKINIT logons for privileged accounts, undetectably and unrevocably. Issuance policy is the ESC surface: a single template or CA-permission mistake lets an unprivileged user legitimately request a certificate that authenticates as Domain Admin. Both must be closed; neither alone is sufficient.
Concrete controls, mapped to what they defend:
| Control | Defends against |
|---|---|
| Offline/HSM root key, dual-control PFX | Root compromise, key theft |
| CA as Tier 0 (dedicated, PIM/PAM, gMSA) | Lateral movement to the CA; standing creds |
| Build-from-AD subjects + manager approval | ESC1 (supplied-subject escalation) |
| Explicit least-privilege EKUs | ESC2 (Any-Purpose/no-EKU abuse) |
| Restricted enrollment-agent templates | ESC3 (enroll-on-behalf abuse) |
| Tight template + PKI-object ACLs | ESC4/ESC5 (rewrite/rogue-CA) |
-EDITF_ATTRIBUTESUBJECTALTNAME2 |
ESC6 (org-wide SAN injection) |
Scoped ManageCA/ManageCertificates |
ESC7 (rogue approval/issuance) |
| HTTPS + Extended Protection, disable NTLM/web-enroll | ESC8 (NTLM relay to enrollment) |
| Strong cert mapping (KB5014754), remove weak flags | ESC9/ESC10 (mapping bypass) |
AuditFilter 127 + SIEM (4886-4900, 4768/4769) |
Detection of any of the above |
Two rules catch teams off guard. Publishing the issuing CA to NTAuthCertificates is what authorizes logon certs — remove a compromised CA from NTAuth to instantly stop it minting auth certs, even before full revocation. And the May 2022 KB5014754 strong-mapping changes moved DCs toward enforcement; unpatched or misconfigured mappings are the ESC9/ESC10 window — patch and set StrongCertificateBindingEnforcement to full enforcement once your certs carry the strong SID mapping.
Cost & sizing
AD CS itself is free — it is a Windows Server role with no per-certificate licensing. The cost is compute, HSM (optional), and operational time. Rough figures (INR and USD; cloud prices vary by region and are illustrative):
| Item | Sizing | Rough monthly cost | Notes |
|---|---|---|---|
| Issuing CA VM | 2-4 vCPU, 8-16 GB, 128 GB SSD | ₹6,000-12,000 / $70-140 | Always on; Tier 0 |
| Offline root | Small VM/physical, off most of the year | ~₹0 running (compute only when booted) | Storage/backup only |
| OCSP responder | 2 vCPU, 4-8 GB (can co-host in small orgs) | ₹3,000-6,000 / $35-70 | Optional but recommended at scale |
| IIS publishing (CDP/AIA) | Shared web host / small VM | ₹1,500-3,000 / $18-35 | Can reuse existing web tier |
| NDES + Intune connector | 2 vCPU, 8 GB member server | ₹4,000-8,000 / $50-95 | Only if mobile/non-domain enrollment |
| HSM (optional, prod root/issuing) | Network or PCIe HSM | Capital + support | ₹ lakhs / $ thousands; justified for high-assurance |
Sizing drivers — what actually scales the estate:
| Driver | Effect | Guidance |
|---|---|---|
| Cert count + churn | CA DB size, CRL size | Prefer OCSP + delta CRL as revocations grow |
| Revocation rate | CRL bloat | Large CRLs → move clients to OCSP |
| Enrollment burst (autoenroll rollout) | CA CPU during waves | Stagger GPO scope; watch certsvc CPU |
| Number of issuing CAs | Operational overhead | Add a second issuing CA for HA/region, not for volume alone |
| Key length/algorithm | Signing CPU | RSA 4096 signing is heavier; size CPU accordingly |
For most mid-size enterprises the whole PKI runs comfortably under ₹25,000-40,000 / month ($300-475) all-in without an HSM. The dominant real cost is engineer discipline — the calendared CRL renewal, the ESC audits, the tested restores — which is exactly what prevents the un-priced disaster of a compromised or expired root.
Interview & exam questions
Q1. Why is the root CA offline and standalone rather than an Enterprise Root on a DC? A standalone offline root keeps its private key off the network, protected from any online compromise; a stolen root key can forge any certificate, including PKINIT logons for privileged accounts, undetectably. Standalone (not enterprise) means no AD dependency, so it can live powered off. Maps to SC-300/MS-102 and security architecture.
Q2. What happens when the root CRL expires, and how do you prevent it?
Every certificate chaining through that CA fails revocation checking, which fails chain validation forest-wide even though certs are still valid. Prevent it with a long root CRLPeriod (26-52 weeks), a generous CRLOverlapPeriod, and a calendared procedure to boot the offline root and certutil -crl well inside the window.
Q3. Why HTTP-first for AIA/CDP, and why can’t you fix it after issuance? HTTP is reachable by any client (non-domain, pre-auth VPN, mobile); LDAP is AD-only. The AIA/CDP URLs are stamped into each certificate at issuance and are immutable for its life, so an LDAP-only cert can’t be retrofitted — you must reissue. List HTTP before LDAP on day one.
Q4. Explain ESC1 and its fix.
A template that lets the enrollee supply the subject (ENROLLEE_SUPPLIES_SUBJECT), has a client-auth EKU, and grants Enroll to a broad group lets a low-priv user request a cert with SAN=Administrator and authenticate as them. Fix: build subject from AD, or require CA-manager approval, and restrict Enroll.
Q5. What is EDITF_ATTRIBUTESUBJECTALTNAME2 (ESC6) and how do you check/disable it?
A CA policy flag that lets any request inject an arbitrary SAN — an org-wide ESC1. Check with certutil -getreg policy\EditFlags; disable with certutil -setreg policy\EditFlags -EDITF_ATTRIBUTESUBJECTALTNAME2 and restart certsvc.
Q6. Difference between CRL, delta CRL, and OCSP? A base CRL is the full signed list of revoked serials; a delta CRL carries only changes since the last base so clients refresh cheaply; OCSP answers “is this one serial revoked?” over HTTP without downloading a list. OCSP scales best for large estates; CRLs cache offline.
Q7. What does publishing a CA to NTAuthCertificates do? It authorizes that CA to issue certificates usable for domain logon (smartcard/PKINIT). A cert not chaining to an NTAuth-listed CA cannot be used for authentication even if otherwise valid; removing a CA from NTAuth instantly stops it minting auth certs.
Q8. When would you use key archival, and what must you never archive? Archive encryption keys (S/MIME, EFS) so a Key Recovery Agent can recover data if the key is lost. Never archive signing/authentication keys — a recoverable signing key destroys non-repudiation. Require ≥2 recovery agents for high-value data.
Q9. How does Intune improve on standalone NDES/SCEP security? Standalone NDES uses a static challenge password (a shared secret). The Intune Certificate Connector generates and validates a per-request challenge tied to the enrolled device and assigned profile, so devices never see a reusable secret; combine with HTTPS + Extended Protection to blunt ESC8-style relay.
Q10. What is CRLOverlapPeriod and why does it matter?
It makes the CA publish the next CRL a configured span before the current one expires, guaranteeing a fresh, valid CRL is always live even if a publish is delayed. Without it, clients can momentarily find only an expired CRL at each rollover.
Q11. How do you renew an issuing CA cert with same vs new key?
Same key keeps chains/AIA/CDP names stable — simplest, when the key is uncompromised. New key rolls the %8/%4 suffixes and starts a fresh CRL scope (both old and new valid during overlap) — used on key compromise or algorithm upgrade. Renew before roughly half-life.
Q12. Which events tell you a certificate was issued or a template changed? Security-log 4886 (request received), 4887 (issued), 4888 (denied), 4899 (template updated), 4900 (template ACL changed); on DCs, 4768/4769 show PKINIT logons. Ship them to SIEM to detect ESC abuse and unplanned template edits.
Quick check
- Why must the offline root’s own certificate not carry a CDP/AIA extension?
- Your macOS build agents fail chain validation but domain PCs are fine. Most likely cause and fix?
- What single CA flag turns every certificate request into a potential ESC1, and how do you disable it?
- Which AD store must the issuing CA appear in for smartcard/PKINIT logon to work?
- What is the purpose of
CRLOverlapPeriod, and roughly what value would you set on an offline root with a 26-week CRL?
Answers
- The root is a trust anchor — nobody revokes it, so a CDP/AIA in its own cert points at a location that serves no purpose and can only fail. The
CAPolicy.infsuppresses them. - LDAP-only CDP/AIA: non-domain hosts can’t reach AD to fetch the CRL/issuer. Add an HTTP CDP/AIA (HTTP-first) on the issuing CA and let autoenrollment reissue over one renewal cycle; existing certs’ URLs are immutable.
EDITF_ATTRIBUTESUBJECTALTNAME2(ESC6). Disable withcertutil -setreg policy\EditFlags -EDITF_ATTRIBUTESUBJECTALTNAME2and restartcertsvc.NTAuthCertificates(publish withcertutil -dspublish -f <IssuingCA>.crt NTAuthCA).- It forces the next CRL to publish before the current one expires so a valid CRL is always live. On a 26-week root CRL, a multi-week overlap (e.g. 6 weeks) gives ample slack to boot the root and republish.
Glossary
| Term | Definition |
|---|---|
| AIA | Authority Information Access — cert extension pointing to the issuer’s certificate (chain building) and, via flag 32, the OCSP responder. |
| CDP | CRL Distribution Point — cert extension pointing to where the CRL is published. |
| CRL / delta CRL | Signed, time-bounded list of revoked serials / just the changes since the last base CRL. |
| CRLOverlapPeriod | How far before expiry the CA publishes the next CRL, guaranteeing a fresh one is always live. |
| OCSP | Online Certificate Status Protocol — per-certificate revocation status over HTTP via the Online Responder role. |
| Standalone CA | CA with no AD integration; manual approval, no templates; used for the offline root. |
| Enterprise CA | AD-integrated CA; template-driven, supports autoenrollment; used for issuing. |
| KSP | Key Storage Provider — software or HSM component holding/using the CA private key. |
| Certificate template | AD-stored issuance policy (EKU, subject source, key size, ACL) defining what can be issued to whom. |
| Autoenrollment | GPO-driven silent request/renewal of certs for members with Enroll+Autoenroll rights. |
| KRA | Key Recovery Agent — holder of a cert that can decrypt archived private keys. |
| NTAuthCertificates | AD store listing CAs authorized to issue logon (PKINIT/smartcard) certificates. |
| NDES / SCEP | Network Device Enrollment Service / Simple Certificate Enrollment Protocol — HTTP-based enrollment for non-domain and mobile devices. |
| ESC1-ESC11 | Certified Pre-Owned classes of AD CS template/CA misconfiguration enabling privilege escalation. |
| EDITF_ATTRIBUTESUBJECTALTNAME2 | CA policy flag that lets any request inject an arbitrary SAN — ESC6. |
| CAPolicy.inf | File in C:\Windows controlling a CA’s self-cert extensions, key length, validity and CRL behaviour at install/renewal. |
Next steps
- Get the directory foundation right first: Active Directory Domain Services forest design and DC promotion and the tiered admin model for an AD DS forest — your CA is a Tier 0 asset.
- Deliver autoenrollment cleanly with Group Policy managed as code.
- Run the CA/NDES service identities without standing secrets using gMSA and Windows LAPS.
- Broker CA-admin access through Privileged Identity Management / PAM.
- Build on certificate-based auth with FIDO2 and Windows Hello for Business, and reduce lateral movement with SMB hardening and Credential Guard.