Windows Server for beginners. Part 25: Setting up an AD CS certification authority
All parts of the series “Windows Server for beginners”How X.509 certificates, chains of trust, certification authorities and TLS work is covered in part 23 of the networking series; it isn't repeated here. In short: a certificate binds a public key to a name and is signed by a certification authority (CA), and a client trusts it if the chain of signatures reaches a root CA from its trusted list and no certificate in the chain is revoked. This part is about why a domain needs its own certification authority, how it fits into the directory, and why a domain computer gets a certificate without a single action from an administrator.
A public certification authority will issue a certificate for www.example.com, but not for dc1.corp.example.com: that name doesn't resolve from the internet, and ownership of it can't be proven (part 4). It won't issue a certificate to a user for smart card logon, to a computer for Wi-Fi with 802.1X, to a domain controller for LDAPS — thousands of short-lived certificates that have to be issued and renewed automatically. For that, Windows Server has the Active Directory Certificate Services role (AD CS): a certification authority that knows the directory, takes names and permissions from it and issues certificates to those entitled to them, by rules called templates.
Commands on the CA servers run in their console, in Windows PowerShell 5.1: the ADCSDeployment and ADCSAdministration modules work with the CA on the same server and reach PowerShell 7 only through the compatibility layer. Everything else runs from the workstation ws1 in PowerShell 7 (part 15).
The hierarchy
The recommended layout is two-tier:
┌───────────────────────────────────────────────────────────────┐
│ Example Corp Root CA standalone, NOT in the │
│ ca-root (powered off) domain, turned on yearly │
│ valid 20 years, 4096-bit key issues: subordinate CA │
│ certificates and its CRL │
└──────────────────────────┬────────────────────────────────────┘
│ signs
┌──────────────────────────▼────────────────────────────────────┐
│ Example Corp Issuing CA 1 enterprise, domain member, │
│ ca1.corp.example.com integrated with AD │
│ valid 10 years, 4096-bit key issues: all working │
│ certificates by templates │
└───────────────────────────────────────────────────────────────┘
▼
domain controllers, servers, workstations, users, NPS, web
The root CA is standalone: not in the domain, not integrated with the directory, with a single job — sign the issuing CA's certificate and publish a revocation list once a year (CRL, certificate revocation list — a list of serial numbers of revoked certificates, signed by the CA and valid for a set period). The rest of the time it is powered off: a virtual machine without a network, its key physically unreachable. A compromised root means rebuilding the whole PKI from scratch; a compromised issuing CA means the root revokes its certificate and a new issuing CA is installed.
The issuing CA is an enterprise CA: a domain member that reads templates from the directory, checks the requester's permissions against the directory, and publishes its certificate and revocation list to the directory. It runs all the time and issues everything.
A single-tier enterprise root is an acceptable option for a lab and for an organization that knowingly accepts the risk of a root key on a running server; such a CA shows up in "Hands-on". Three tiers (with an intermediate policy CA) are for organizations that need different issuance policies under different branches. For this series, two.
What the PKI keeps in the directory
The CN=Public Key Services,CN=Services,CN=Configuration container — from the tour in part 5:
PS C:\> Get-ADObject -SearchBase 'CN=Public Key Services,CN=Services,CN=Configuration,DC=corp,DC=example,DC=com' -Filter * -SearchScope OneLevel | Select-Object Name, ObjectClass
Name ObjectClass
---- -----------
AIA container
CDP container
Certificate Templates container
Certification Authorities container
Enrollment Services container
KRA container
NTAuthCertificates certificationAuthority
OID msPKI-Enterprise-Oid
| Container | What | Consequence |
|---|---|---|
| Certification Authorities | certificates of the root CAs the forest trusts | every domain member copies them into its enterprise root store, and they show up in Cert:\LocalMachine\Root |
| AIA | certificates of intermediate CAs | they land in the enterprise intermediate store (Cert:\LocalMachine\CA) — the chain is built without downloading |
| Enrollment Services | pKIEnrollmentService objects — enterprise CAs: name, server DNS name and the list of published templates |
clients find where to send a request |
| Certificate Templates | pKICertificateTemplate objects — the templates |
clients read what they may request and under which rules |
| NTAuthCertificates | one object with the list of CA certificates allowed to issue certificates for logon | a controller accepts a certificate at smart card logon only if the issuing CA is here |
| CDP | a container per CA server, holding a cRLDistributionPoint object with the CRL and the delta CRL |
clients can check revocation over LDAP |
| KRA, OID | key recovery agents, and OIDs of templates and issuance policies created in the forest | — |
The enterprise stores on a client are filled by the Certificate Services Client component on every Group Policy refresh — regardless of whether autoenrollment, described below, is enabled. From this table follows the main property of an enterprise CA: as soon as it is installed, all domain members trust it, and it gains the right to issue certificates for authentication in the domain. That's a convenience and a risk, which we come back to at the end.
The root CA
The server ca-root: Windows Server, a workgroup, no network after installation. Before installing the role, the file C:\Windows\CAPolicy.inf sets the parameters of the root certificate:
[Version]
Signature="$Windows NT$"
[Certsrv_Server]
RenewalKeyLength=4096
RenewalValidityPeriod=Years
RenewalValidityPeriodUnits=20
CRLPeriod=Weeks
CRLPeriodUnits=52
CRLDeltaPeriodUnits=0
LoadDefaultTemplates=0
AlternateSignatureAlgorithm=0
[AuthorityInformationAccess]
[CRLDistributionPoint]
The empty [AuthorityInformationAccess] and [CRLDistributionPoint] sections remove from the root certificate the AIA extension (where to get the issuer's certificate) and the CDP extension (where to get the CRL): the root is self-signed, there's no issuer above it, and Windows doesn't check revocation of a root certificate. Without these sections the root would get the default addresses. Renewal* applies only when the root certificate is renewed (the first one's validity is set by the installation), and CRLDeltaPeriodUnits=0 means the root needs no delta CRL. LoadDefaultTemplates=0 applies only when an enterprise CA is installed — don't publish the default templates, so the CA issues nothing until templates are enabled deliberately; a standalone CA has no templates, but the issuing CA will need this setting.
PS C:\> Install-WindowsFeature ADCS-Cert-Authority -IncludeManagementTools
PS C:\> 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
After installation come the parameters the root will write into the certificates it issues (that is, into the issuing CA's certificate): where to find its CRL and its own certificate. These must be addresses available to every client at all times — HTTP on an internal web server, which can be the issuing CA itself (HTTPS isn't supported in CDP and AIA):
PS C:\> certutil -setreg CA\CRLPublicationURLs "1:C:\Windows\System32\CertSrv\CertEnroll\%3%8.crl\n2:http://pki.corp.example.com/pki/%3%8.crl"
PS C:\> certutil -setreg CA\CACertPublicationURLs "1:C:\Windows\System32\CertSrv\CertEnroll\%1_%3%4.crt\n2:http://pki.corp.example.com/pki/%1_%3%4.crt"
PS C:\> certutil -setreg CA\CRLPeriodUnits 52
PS C:\> certutil -setreg CA\CRLPeriod "Weeks"
PS C:\> certutil -setreg CA\CRLOverlapUnits 4
PS C:\> certutil -setreg CA\CRLOverlapPeriod "Weeks"
PS C:\> certutil -setreg CA\ValidityPeriodUnits 10
PS C:\> certutil -setreg CA\ValidityPeriod "Years"
PS C:\> Restart-Service CertSvc
PS C:\> certutil -crl
The value is a list of strings separated by \n, and the number before the colon is a sum of flags: 1 — publish the file at this path, 2 — write the URL into issued certificates (into CDP for the revocation list, into AIA for the CA certificate); the issuing CA will also need 4 — write the delta CRL address into the base CRL, 64 — publish the delta CRL at this path too, and 32 — the OCSP address in AIA. Variables: %1 — the server's DNS name, %3 — the CA name, %4 — the CA certificate's index suffix after renewal, %8 — the CRL index suffix after key renewal, %9 — the + suffix of the delta CRL. A one-year CRL with a four-week overlap fits the "turn on once a year" routine exactly. Note the value name: the overlap is CRLOverlapUnits, without Period in the middle, and certutil -setreg with a typo in the name silently creates a new value the CA never reads. ValidityPeriod 10 is the maximum validity of the certificates the root issues to issuing CAs.
The root certificate in C:\Windows\System32\CertSrv\CertEnroll\ has to be carried off the server and published to the directory from a domain machine, as Enterprise Admins:
PS C:\> certutil -dspublish -f 'ca-root_Example Corp Root CA.crt' RootCA
The command puts the certificate into Certification Authorities and AIA, and from that moment every domain member trusts the root at its next policy refresh. The root's certificate and CRL go into the pki folder on the web server (we'll create it on the issuing CA): CDP has only the HTTP address, so putting the root CRL into the directory (certutil -dspublish with the .crl file) is optional. The root is powered off.
The issuing CA
The server ca1.corp.example.com, a domain member, with the same CAPolicy.inf but without the AIA and CDP sections (for a subordinate CA these extensions come from the root's configuration) and with LoadDefaultTemplates=0. The web server for CRLs is convenient to run right here, and the root's CRL and certificate must be in it before the CA is installed: without a reachable root CRL, chain validation fails when ca1's certificate is installed.
PS C:\> Install-WindowsFeature ADCS-Cert-Authority, Web-Server -IncludeManagementTools
PS C:\> New-Item C:\inetpub\pki -ItemType Directory
PS C:\> New-WebVirtualDirectory -Site 'Default Web Site' -Name pki -PhysicalPath C:\inetpub\pki
PS C:\> Set-WebConfigurationProperty -PSPath 'IIS:\Sites\Default Web Site\pki' -Filter system.webServer/security/requestFiltering -Name allowDoubleEscaping -Value $true
PS C:\> Copy-Item 'C:\Transfer\ca-root_Example Corp Root CA.crt', 'C:\Transfer\Example Corp Root CA.crl' C:\inetpub\pki\
allowDoubleEscaping is needed because of the + sign in delta CRL names: IIS request filtering rejects it in a path by default. The pki name in DNS — from the workstation:
PS C:\> Add-DnsServerResourceRecordCName -ComputerName dc2 -ZoneName corp.example.com -Name pki -HostNameAlias ca1.corp.example.com
Installing an enterprise CA writes to the configuration partition, so it runs as Enterprise Admins. A subordinate CA can't sign itself: the installation creates a certificate request that has to be taken to the root, and warns that the installation is incomplete:
PS C:\> Install-AdcsCertificationAuthority -CAType EnterpriseSubordinateCA -CACommonName 'Example Corp Issuing CA 1' -KeyLength 4096 -HashAlgorithmName SHA256 -CryptoProviderName 'RSA#Microsoft Software Key Storage Provider' -OutputCertRequestFile C:\Transfer\ca1.req -Force
On the root, powered on for an hour:
PS C:\> certreq -submit -config 'ca-root\Example Corp Root CA' C:\Transfer\ca1.req C:\Transfer\ca1.cer
PS C:\> certutil -resubmit 2
PS C:\> certreq -retrieve -config 'ca-root\Example Corp Root CA' 2 C:\Transfer\ca1.cer
A standalone CA without templates issues nothing on its own by default: certreq -submit reports the request number (2 here) and that the request is pending. certutil -resubmit 2 approves the pending request, and certreq -retrieve fetches the certificate. The file goes back to ca1:
PS C:\> certutil -installcert C:\Transfer\ca1.cer
PS C:\> Start-Service CertSvc
PS C:\> certutil -setreg CA\CRLPeriodUnits 1; certutil -setreg CA\CRLPeriod "Weeks"
PS C:\> certutil -setreg CA\CRLDeltaPeriodUnits 1; certutil -setreg CA\CRLDeltaPeriod "Days"
PS C:\> certutil -setreg CA\ValidityPeriodUnits 2; certutil -setreg CA\ValidityPeriod "Years"
PS C:\> certutil -setreg CA\AuditFilter 127
PS C:\> auditpol /set /subcategory:"Certification Services" /success:enable /failure:enable
PS C:\> Get-CACrlDistributionPoint | Where-Object Uri -like 'http*' | Remove-CACrlDistributionPoint -Force
PS C:\> Add-CACrlDistributionPoint -Uri 'http://pki.corp.example.com/pki/%3%8%9.crl' -AddToCertificateCdp -AddToFreshestCrl -Force
PS C:\> Add-CACrlDistributionPoint -Uri 'C:\inetpub\pki\%3%8%9.crl' -PublishToServer -PublishDeltaToServer -Force
PS C:\> Add-CAAuthorityInformationAccess -Uri 'http://pki.corp.example.com/pki/%1_%3%4.crt' -AddToCertificateAia -Force
PS C:\> Restart-Service CertSvc
PS C:\> certutil -crl
PS C:\> Copy-Item 'C:\Windows\System32\CertSrv\CertEnroll\ca1.corp.example.com_Example Corp Issuing CA 1.crt' C:\inetpub\pki\
The ADCSAdministration module provides cmdlets for CDP and AIA instead of certutil -setreg with flags: -AddToCertificateCdp is flag 2, -AddToFreshestCrl is 4, -PublishToServer and -PublishDeltaToServer are 1 and 64. The default LDAP address (ldap:///CN=%7%8,CN=%2,CN=CDP,…) stays and also goes into certificates; it's useless to machines outside the domain, and it's often removed, leaving only HTTP in CDP. A one-week CRL with a one-day delta: the issuing CA runs all the time and refreshes them itself. ValidityPeriod 2 caps the validity of issued certificates: a template can give less, but not more. AuditFilter 127 audits all CA operations to the Security log, and only together with the "Certification Services" audit policy: by default neither is set. The issuing CA publishes its certificate to AIA and NTAuthCertificates and itself to Enrollment Services on its own, and from that moment domain clients know where to send requests.
Templates
A template is a set of issuance rules: what the certificate is for (EKU, extended key usage: Server Authentication, Client Authentication, Smart Card Logon, …), where the subject name and SAN (Subject Alternative Name — additional names: DNS names, UPN) come from — the directory or the request, validity, key algorithm and length, whether the key is exportable, whether approval is needed, and who has the right to request (Enroll) and to receive automatically (Autoenroll). Templates live in the directory, shared by all CAs in the forest; each enterprise CA publishes the ones it issues from. A standalone CA knows nothing about templates.
There are over thirty built-in templates — Computer, User, Web Server, Domain Controller, Domain Controller Authentication, Kerberos Authentication, Workstation Authentication, RAS and IAS Server, Smartcard Logon, Code Signing, OCSP Response Signing — and one rule for working with them: built-in templates aren't edited, they're duplicated. In version 1 templates (Windows 2000) only permissions can be changed; version 2 (Windows Server 2003) brought configurable templates and autoenrollment, version 3 (2008) — CNG keys (Cryptography Next Generation, the newer Windows cryptographic API), version 4 (2012) — renewal with the same key and other later settings. A duplicate gets its version from the compatibility tab: minimum CA and recipient Windows Server 2003 — version 2, 2008 — 3, 2012 and later — 4; plus its own name and its own settings.
Editing templates is the one place in this series where the snap-in can't be avoided: no cmdlets for template properties ship with Windows (there is the third-party PSPKI module), and editing a pKICertificateTemplate object in the directory directly means knowing the format of a dozen binary attributes. The certtmpl.msc snap-in: right-click Workstation Authentication → Duplicate Template; compatibility Windows Server 2016, name Corp Computer, validity 1 year, renewal 6 weeks before expiry (as in the original), Subject Name → Build from AD (DNS name, and DNS in SAN), Security → Domain Computers: Read, Enroll, Autoenroll. The second is a duplicate of Web Server named Corp Web Server, Subject Name → Supply in the request, Security → the Web Admins group: Enroll, with "CA certificate manager approval" checked for control.
What PowerShell shows is the template objects. Here are the built-in ones the copies are made from:
PS C:\> $pks = 'CN=Public Key Services,CN=Services,CN=Configuration,DC=corp,DC=example,DC=com'
PS C:\> Get-ADObject -SearchBase "CN=Certificate Templates,$pks" -Filter "Name -eq 'Machine' -or Name -eq 'Workstation' -or Name -eq 'WebServer' -or Name -eq 'DomainController' -or Name -eq 'DomainControllerAuthentication' -or Name -eq 'KerberosAuthentication'" -Properties displayName, msPKI-Template-Schema-Version, pKIExtendedKeyUsage, msPKI-Certificate-Name-Flag, msPKI-Enrollment-Flag |
Format-Table displayName, @{n='Ver'; e={$_.'msPKI-Template-Schema-Version'}}, @{n='EKU'; e={$_.pKIExtendedKeyUsage -join ','}}, @{n='NameFlag'; e={'0x{0:X}' -f $_.'msPKI-Certificate-Name-Flag'}}, @{n='EnrollFlag'; e={'0x{0:X}' -f $_.'msPKI-Enrollment-Flag'}}
displayName Ver EKU NameFlag EnrollFlag
----------- --- --- -------- ----------
Computer 1 1.3.6.1.5.5.7.3.2,1.3.6.1.5.5.7.3.1 0x18000000 0x20
Web Server 1 1.3.6.1.5.5.7.3.1 0x1 0x0
Workstation Authentication 2 1.3.6.1.5.5.7.3.2 0x8000000 0x20
Domain Controller 1 1.3.6.1.5.5.7.3.2,1.3.6.1.5.5.7.3.1 0x19000000 0x29
Kerberos Authentication 2 1.3.6.1.5.5.7.3.2,1.3.6.1.5.5.7.3.1,1.3.6.1.4.1.311.20.2.2,1.3.6.1.5.2.3.5 0x8400000 0x20
Domain Controller Authentication 2 1.3.6.1.5.5.7.3.2,1.3.6.1.5.5.7.3.1,1.3.6.1.4.1.311.20.2.2 0x8000000 0x20
The object name (Machine, WebServer) is the template name used to publish and request it; displayName is what the snap-in shows. EKUs: 1.3.6.1.5.5.7.3.1 — Server Authentication, .3.2 — Client Authentication, 1.3.6.1.4.1.311.20.2.2 — Smart Card Logon, 1.3.6.1.5.2.3.5 — KDC Authentication.
msPKI-Certificate-Name-Flag says where the name comes from: 0x1 — from the request (ENROLLEE_SUPPLIES_SUBJECT), 0x8000000 — the DNS name from the directory into SAN, 0x10000000 — the same into the subject CN, 0x1000000 — objectGUID into SAN (Domain Controller), 0x400000 — the domain DNS name and NetBIOS name into SAN (Kerberos Authentication). msPKI-Enrollment-Flag says how to issue: 0x20 — autoenrollment allowed, 0x8 — publish the issued certificate to the directory, into the owner's userCertificate attribute, 0x2 — every request waits for manager approval, 0x1 — add the S/MIME extension to the certificate. Corp Computer will have 0x18000000 (DNS name in both CN and SAN) and 0x20, Corp Web Server — 0x1 and 0x2. These two attributes are what a template audit checks, more on that below.
Publish the templates on the CA — on ca1:
PS C:\> Add-CATemplate -Name CorpComputer -Force
PS C:\> Add-CATemplate -Name CorpWebServer -Force
PS C:\> Add-CATemplate -Name DomainControllerAuthentication -Force
PS C:\> Add-CATemplate -Name KerberosAuthentication -Force
PS C:\> Get-CATemplate
Get-CATemplate lists the published templates in Name and Oid columns; a template's OID is unique to each forest. Publishing is the certificateTemplates attribute of the CA object in Enrollment Services, so it's visible from any domain machine with Get-ADObject too.
Autoenrollment
Now the point of it all: every domain computer gets a Corp Computer certificate on its own. It's enabled by one policy — Computer Configuration → Policies → Windows Settings → Security Settings → Public Key Policies → Certificate Services Client – Auto-Enrollment: Enabled, with "Renew expired certificates, update pending certificates, and remove revoked certificates" and "Update certificates that use certificate templates" checked. On the client this is one registry value, AEPolicy = 7 (enabled with both options), and from part 24 we know how to set it with a command:
PS C:\> $gpo = New-GPO -Name 'PKI - Autoenrollment'
PS C:\> Set-GPRegistryValue -Guid $gpo.Id -Key 'HKLM\SOFTWARE\Policies\Microsoft\Cryptography\AutoEnrollment' -ValueName AEPolicy -Type DWord -Value 7
PS C:\> Set-GPRegistryValue -Guid $gpo.Id -Key 'HKCU\SOFTWARE\Policies\Microsoft\Cryptography\AutoEnrollment' -ValueName AEPolicy -Type DWord -Value 7
PS C:\> New-GPLink -Guid $gpo.Id -Target 'DC=corp,DC=example,DC=com' -LinkEnabled Yes
The HKCU key is the same policy for users. What happens on the client at the next policy refresh (and every 8 hours after that):
ws1 dc (LDAP) ca1 (DCOM)
│ │ │
├─ read templates ────────────────────►│ CN=Certificate Templates │
│◄─ list, permissions, settings ───────┤ │
│ keep those where I have Autoenroll │ │
│ and no valid certificate │ │
├─ read Enrollment Services ──────────►│ who publishes Corp Computer │
│◄─ ca1.corp.example.com ──────────────┤ │
│ generate a key, build a request │ │
├─ ICertRequest::Submit (RPC/DCOM) ──────────────────────────────────►│
│ │ check permissions in the │
│ │ template ACL, take the │
│ │ name from AD, sign │
│◄─ certificate ──────────────────────────────────────────────────────┤
│ store in Cert:\LocalMachine\My │ │
The request goes over RPC/DCOM (port 135 and a dynamic port, like everything in part 3) with Kerberos authentication: the CA knows who is asking and takes the name from the computer object, not from the request. Six weeks before expiry (the template's renewal period) the client requests a new certificate on its own. Checking and forcing a run:
PS C:\> Invoke-Command ws1 { certutil -pulse; Start-Sleep 5; Get-ChildItem Cert:\LocalMachine\My | Select-Object Subject, Issuer, NotAfter, @{n='Template'; e={ ($_.Extensions | Where-Object { $_.Oid.Value -in '1.3.6.1.4.1.311.21.7', '1.3.6.1.4.1.311.20.2' }).Format($false) -replace '^Template=([^(]+).*', '$1' }} }
certutil -pulse triggers autoenrollment now, and the last column extracts the template name from the certificate. There are two such extensions: version 1 templates use Certificate Template Name (1.3.6.1.4.1.311.20.2), which holds just the name (Machine), version 2 and later use Certificate Template Information (1.3.6.1.4.1.311.21.7) of the form Template=Corp Computer(<OID>), Major Version Number=100, Minor Version Number=…. The filter goes by OID, not FriendlyName: the extension's friendly name depends on the system language, and the simplified syntax Where-Object Oid.FriendlyName -eq … doesn't resolve nested properties and silently finds nothing. On ws1 the result is a certificate CN=ws1.corp.example.com from Example Corp Issuing CA 1 with the Corp Computer template.
A manual request from a template where the requester supplies the name — the PKI module:
PS C:\> Get-Certificate -Template CorpWebServer -DnsName web.corp.example.com, intranet.corp.example.com -SubjectName 'CN=web.corp.example.com' -CertStoreLocation Cert:\LocalMachine\My -Url ldap:
PS C:\> certutil -config 'ca1.corp.example.com\Example Corp Issuing CA 1' -resubmit 17 # approve on the CA
PS C:\> Get-Certificate -Request (Get-ChildItem Cert:\LocalMachine\Request | Select-Object -Last 1)
-Url ldap: means find the CA through the directory. The template requires approval, so the first call returns the status Pending and leaves the request in the Request store; after approval the second call fetches the certificate. For machines outside the domain and behind a firewall there are the Certificate Enrollment Policy Web Service and Certificate Enrollment Web Service roles — the same steps over HTTPS — and the classic certreq -new with an INF file, -submit, -accept.
Domain controllers get a certificate from the built-in Domain Controller template (version 1) even without the autoenrollment policy — it's enough for the template to be published on an enterprise CA. The version 2 templates Domain Controller Authentication and Kerberos Authentication (the Domain Controllers and Enterprise Domain Controllers groups have Autoenroll on them) arrive only with the policy enabled; the first of them supersedes Domain Controller. With a certificate that has Server Authentication and the controller's DNS name, LDAPS on 636 (LDAP inside TLS) starts working with no configuration: the controller finds the certificate in its store by itself. And the KDC can accept PKINIT — a form of Kerberos pre-authentication in which the client proves its identity by signing with the private key of its certificate instead of using a key derived from a password, and the KDC signs its reply with its own certificate carrying the KDC Authentication EKU. Smart card logon and Windows Hello for Business rest on PKINIT. WinRM HTTPS from part 14 takes a certificate with Server Authentication from the same CA — from Web Server or a duplicate of it.
Revocation
A certificate is revoked when its key is compromised or the certificate should no longer be valid (an employee left, a server was decommissioned):
PS C:\> certutil -config 'ca1.corp.example.com\Example Corp Issuing CA 1' -revoke 1a2b3c4d5e6f7081 1 # serial number, reason 1 = Key Compromise
PS C:\> certutil -crl # on ca1: publish the CRL now instead of waiting a week
A client learns about revocation in one of two ways whose addresses are written into the certificate: it downloads the CRL from the CDP URL (and caches it until the CRL expires; a delta CRL is a short list of revocations since the last base CRL, and its address is written in the base CRL itself), or it asks an OCSP responder at the AIA URL about the specific certificate: OCSP (Online Certificate Status Protocol) is an HTTP request with a serial number and a signed answer "good", "revoked" or "unknown". The responder is a separate role, ADCS-Online-Cert: Install-WindowsFeature ADCS-Online-Cert; Install-AdcsOnlineResponder, a revocation configuration for each CA with a signing certificate from the OCSP Response Signing template (short-lived, a couple of weeks, the responder renews it itself), and the URL http://ocsp.corp.example.com/ocsp is added to the CA's AIA with -AddToCertificateOcsp. OCSP is needed where the CRL is large or where the reaction to a revocation must be fast (smart cards, VPN); for a mid-size domain the CRL is enough.
The most important fact about revocation: an expired CRL breaks validation. If the issuing CA hasn't published a new CRL (the service is stopped, the disk is full, the web server doesn't answer), then once the old one expires every client checking any certificate from that CA gets "unable to check revocation". Strict verifiers — the KDC at certificate logon, NPS with EAP-TLS — treat that as a failure; other applications behave differently, but relying on leniency is a mistake. So CRLs get a margin. Even without CRLOverlapUnits the CA adds its own: on a real one-week CRL NextUpdate is 12 hours 10 minutes after the next publication time, and ThisUpdate is 10 minutes before the actual one, for clock skew (an example is in "Hands-on"). Publication is watched by monitoring, and the root CA is turned on by the calendar before its yearly CRL expires, not after the first phone call.
PS C:\> certutil -URL http://pki.corp.example.com/pki/Example%20Corp%20Issuing%20CA%201.crl # URL retrieval window
PS C:\> certutil -verify -urlfetch C:\Temp\ws1.cer | Select-String 'Verified|Expired|Revocation'
certutil -URL opens a window where the addresses from a certificate can be tested one by one; -verify -urlfetch walks the whole chain and actually downloads every CRL and OCSP response, bypassing the cache.
Backup
A CA is the database of issued certificates, its key and certificate, and its settings in the registry. Templates live in the directory and are backed up with a controller's system state (part 20).
PS C:\> Backup-CARoleService -Path D:\CABackup -Password (Read-Host -AsSecureString 'PFX password')
PS C:\> reg export 'HKLM\SYSTEM\CurrentControlSet\Services\CertSvc\Configuration' D:\CABackup\CertSvc.reg /y
Without switches, Backup-CARoleService copies both the database and the key: the database into a Database subfolder, the key with the CA certificate into a password-protected .p12 file (-KeyOnly and -DatabaseOnly do them separately). The CA's settings — CDP, AIA, periods, flags — aren't in the database, hence the export of the CertSvc\Configuration registry key next to it. The .p12 file is kept apart from the database, in the same place as the DSRM password. For the root, a backup is made after installation and after every power-on in which it issued or revoked something. Restore is Restore-CARoleService -Path … -Password … on a server with the role installed and the same CA name, followed by the registry import.
What breaks the domain
In 2021 the "Certified Pre-Owned" paper (SpecterOps) came out with a list of typical AD CS misconfigurations numbered ESC1–ESC8 — the list has grown since, and Microsoft Defender for Identity checks some of them under the same numbers. Most come down to one thing: a certificate with an authentication EKU (Client Authentication, Smart Card Logon, Any Purpose) from a CA in NTAuthCertificates is equivalent to the password of whoever's name is in it. If a template lets an ordinary user get such a certificate with someone else's name, that user can log on as a domain admin.
| Misconfiguration | What it is | Check |
|---|---|---|
| ESC1 | a template with an authentication EKU, name from the request (ENROLLEE_SUPPLIES_SUBJECT), no approval, Enroll for a broad group |
msPKI-Certificate-Name-Flag bit 0x1 together with EKU 1.3.6.1.5.5.7.3.2 and Enroll for Domain Users, Domain Computers or Authenticated Users |
| ESC2 | a template with the Any Purpose EKU (2.5.29.37.0) or no EKU, available to a broad group |
EKU 2.5.29.37.0 or empty |
| ESC3 | an unrestricted enrollment agent template (Certificate Request Agent): its holder requests certificates on behalf of others | EKU 1.3.6.1.4.1.311.20.2.1 and Enroll for a broad group |
| ESC4 | permission to edit a template held by non-admins | ACLs of objects in Certificate Templates |
| ESC5 | permissions on PKI objects in the directory and on the CA server itself | ACLs of Public Key Services, NTAuthCertificates, the CA's computer object |
| ESC6 | the EDITF_ATTRIBUTESUBJECTALTNAME2 flag on the CA: SAN from the request for any template |
certutil -getreg policy\EditFlags — it must not be there |
| ESC7 | CA Administrator (Manage CA) or Certificate Manager rights for the wrong people | certutil -getreg CA\Security |
| ESC8 | web enrollment (certsrv) or CES over HTTP with NTLM — NTLM relay |
don't install Web Enrollment; if it's there — HTTPS only, with Extended Protection (EPA, binding NTLM to the TLS channel) |
Of the later numbers, two are worth remembering: ESC11 — the RPC request interface without mandatory encryption (the IF_ENFORCEENCRYPTICERTREQUEST flag in CA\InterfaceFlags is on by default), and ESC15 (CVE-2024-49019) — version 1 templates with the name from the request, such as the built-in Web Server, in which, before the November 2024 updates, any application policy could be put into the request.
Since May 2022 enterprise CAs add an extension with the subject's SID (1.3.6.1.4.1.311.25.2) to certificates issued from templates that take the name from the directory, and controllers map a certificate to an account strongly — by that SID or by an explicit mapping in altSecurityIdentities, not by name. Since the February 2025 updates strong mode is on by default, and since September 2025 compatibility mode can't be brought back. A certificate from a template with the name from the request doesn't get this extension, and logging on to someone else's account with it by name alone no longer works. But that doesn't cancel template audits: a certificate with someone else's DNS name still lets you impersonate another server in TLS, and Enroll and Manage CA rights remain paths to the domain. And finally: the issuing CA is a tier 0 server from part 15, with the same access rules as the controllers.
Hands-on
The output below was captured in a domain where a single-tier enterprise root Example Corp CA has been running for years on ca.corp.example.com, with default CDP and AIA addresses (http://<server>/CertEnroll/) and an OCSP responder. The mechanisms are the same; in the two-tier layout the root store would hold Example Corp Root CA, and the issuing CA would be in the intermediate store.
The CA in the directory — the server and the number of published templates:
PS C:\> $pks = 'CN=Public Key Services,CN=Services,CN=Configuration,DC=corp,DC=example,DC=com'
PS C:\> Get-ADObject -SearchBase "CN=Enrollment Services,$pks" -SearchScope OneLevel -Filter * -Properties dNSHostName, certificateTemplates | Select-Object Name, dNSHostName, @{n='Templates'; e={$_.certificateTemplates.Count}}
Name dNSHostName Templates
---- ----------- ---------
Example Corp CA ca.corp.example.com 18
The root on the workstation came from the directory, not from a GPO and not by hand: certutil -enterprise shows exactly the enterprise store.
PS C:\> certutil -enterprise -store Root
Root "Trusted Root Certification Authorities"
================ Certificate 0 ================
Serial Number: …
Issuer: CN=Example Corp CA, DC=corp, DC=example, DC=com
NotBefore: 02.03.2015 23:06
NotAfter: 02.03.2035 23:16
Subject: CN=Example Corp CA, DC=corp, DC=example, DC=com
Certificate Template Name (Certificate Type): CA
CA Version: V0.0
Signature matches Public Key
Root Certificate: Subject matches Issuer
Template: CA, Root Certification Authority
Cert Hash(sha1): …
No key provider information
Cannot find the certificate and private key for decryption.
CertUtil: -store command completed successfully.
An enterprise CA's subject is its common name plus the domain DN. The same certificate is in certutil -enterprise -store CA (a single-tier CA publishes itself to AIA too), and -store NTAuth holds two: the current one and a previous, expired one — old certificates don't leave NTAuthCertificates by themselves.
LDAPS on a controller that got its certificate from the Domain Controller template on its own:
PS C:\> $tcp = New-Object Net.Sockets.TcpClient('dc2.corp.example.com', 636)
PS C:\> $ssl = New-Object Net.Security.SslStream($tcp.GetStream()); $ssl.AuthenticateAsClient('dc2.corp.example.com')
PS C:\> $ssl.RemoteCertificate | Select-Object Subject, Issuer; $ssl.SslProtocol; $tcp.Close()
Subject Issuer
------- ------
CN=dc2.corp.example.com CN=Example Corp CA, DC=corp, DC=example, DC=com
Tls13
A revocation check of dc1's certificate, saved to a file after the same kind of handshake ([IO.File]::WriteAllBytes("$PWD\dc1-ldaps.cer", $ssl.RemoteCertificate.Export('Cert'))):
PS C:\> certutil -verify -urlfetch .\dc1-ldaps.cer | Select-String 'Verified|Expired|Revocation'
ChainFlags = CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT (0x40000000)
ChainContext.dwRevocationFreshnessTime: 22 Hours, 12 Minutes, 24 Seconds
SimpleChain.dwRevocationFreshnessTime: 22 Hours, 12 Minutes, 24 Seconds
Verified "Base CRL (0df9)" Time: 0 …
Verified "Delta CRL (0df9)" Time: 0 …
Verified "OCSP" Time: 0 …
Verified Issuance Policies: None
Verified Application Policies:
Leaf certificate revocation check passed
Base CRL number 0df9, its delta and an OCSP response — all three sources are reachable. The chain is checked excluding the root (EXCLUDE_ROOT): root revocation isn't checked. The CRL's own dates:
PS C:\> Invoke-WebRequest 'http://ca.corp.example.com/CertEnroll/Example%20Corp%20CA.crl' -OutFile .\base.crl
PS C:\> certutil -dump .\base.crl | Select-String 'ThisUpdate|NextUpdate|CRL Entries'
ThisUpdate: 28.10.2026 17:36
NextUpdate: 05.11.2026 5:56
CRL Entries: 0
Nothing is revoked. The CA published this CRL at 17:46 (the Next CRL Publish extension names the next publication — November 4, 17:46): ThisUpdate is shifted 10 minutes back, and NextUpdate is a week plus a 12 hours 10 minutes margin, even though CRLOverlapUnits on the CA is 0. The one-day delta CRL gets the same 12 hours 10 minutes.
What the CA has issued since the start of September (the date format in -restrict follows the server's regional settings):
PS C:\> certutil -config 'ca.corp.example.com\Example Corp CA' -view -restrict "Disposition=20,NotBefore>=01.09.2026" -out "RequestID,RequesterName,CommonName,CertificateTemplate,NotAfter" csv
"Issued Request ID","Requester Name","Issued Common Name","Certificate Template","Certificate Expiration Date"
"449","CORP\CA$","ca.corp.example.com","1.3.6.1.4.1.311.21.8.… OCSP Response Signing","18.09.2026 14:33"
"450","CORP\CA$","ca.corp.example.com","1.3.6.1.4.1.311.21.8.… OCSP Response Signing","30.09.2026 14:23"
"451","CORP\DC4$","dc4.corp.example.com","DomainController","22.09.2027 19:22"
"455","CORP\CA$","ca.corp.example.com","1.3.6.1.4.1.311.21.8.… OCSP Response Signing","12.10.2026 14:13"
"456","CORP\CA$","ca.corp.example.com","1.3.6.1.4.1.311.21.8.… OCSP Response Signing","24.10.2026 14:03"
"457","CORP\CA$","ca.corp.example.com","1.3.6.1.4.1.311.21.8.… OCSP Response Signing","05.11.2026 13:53"
Disposition=20 means issued (21 — revoked, 9 — pending, 30 — errors, 31 — denied). A version 1 template is recorded by name, version 2 and later as the OID and display name separated by a space. The OCSP responder renews its two-week signing certificate every 12 days, and dc4 renewed its one-year Domain Controller certificate in September — with no autoenrollment policy at all: this domain doesn't have one.
What that means for ordinary computers shows on a workstation that once got a Computer certificate by hand:
PS C:\> Get-ChildItem Cert:\LocalMachine\My | Select-Object Subject, Issuer, NotAfter, @{n='Template'; e={ ($_.Extensions | Where-Object { $_.Oid.Value -in '1.3.6.1.4.1.311.21.7', '1.3.6.1.4.1.311.20.2' }).Format($false) -replace '^Template=([^(]+).*', '$1' }}
Subject Issuer NotAfter Template
------- ------ -------- --------
CN=ws1.corp.example.com CN=Example Corp CA, DC=corp, DC=example, DC=com 11.01.2026 22:17:24 Machine
CN=localhost CN=localhost 15.12.2029 3:00:00
CN=web.corp.example.com CN=Example Corp CA, DC=corp, DC=example, DC=com 16.01.2027 20:07:51 Host
PS C:\> Get-WinEvent -FilterHashtable @{LogName='Application'; ProviderName='Microsoft-Windows-CertificateServicesClient-AutoEnrollment'} -MaxEvents 3 | Select-Object TimeCreated, Id, LevelDisplayName
TimeCreated Id LevelDisplayName
----------- -- ----------------
29.10.2026 13:33:54 64 Warning
29.10.2026 5:33:58 64 Warning
29.10.2026 5:32:31 64 Warning
PS C:\> Get-WinEvent -LogName Microsoft-Windows-CertificateServicesClient-Lifecycle-System/Operational | Group-Object Id -NoElement
Count Name
----- ----
2 1002
348 1003
6 1004
3 1006
3 1007
The machine certificate expired in January, and since then the client writes warning 64 "Certificate for local system with Thumbprint … is about to expire or already expired." every 8 hours, but doesn't renew it: only autoenrollment renews. The client's logs: in Application, the …-CertificateServicesClient-AutoEnrollment provider writes 64 (expiring or expired), 6 (autoenrollment failed) and 15 (can't contact the directory), and the …-CertificateServicesClient-CertEnroll provider writes 19 and 20 (received and renewed), 13 and 16 (the CA refused to issue or renew, with an error code), 11 (no CA found in the forest), 46 (no permission on the template). The numbers overlap between providers (64 from CertEnroll is a successful policy load from a policy server), so filter by ProviderName. The Microsoft-Windows-CertificateServicesClient-Lifecycle-System/Operational log tracks the certificates themselves: 1006 — a new one installed, 1001 — replaced, 1003 — about to expire, 1002 — expired, 1004 — deleted, 1007 — exported. On the CA it's the Security log, events 4886 (request received), 4887 (issued), 4888 (denied), 4889 (set to pending), 4870 (revoked), 4876 and 4877 (backup): with AuditFilter 127 and the audit policy, every certificate leaves a 4886–4887 pair with the requester's name.
CA checks for ESC6, ESC7 and ESC8 — on ca itself:
PS C:\> certutil -getreg policy\EditFlags
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\CertSvc\Configuration\Example Corp CA\PolicyModules\CertificateAuthority_MicrosoftDefault.Policy\EditFlags:
EditFlags REG_DWORD = 11014e (1114446)
EDITF_REQUESTEXTENSIONLIST -- 2
EDITF_DISABLEEXTENSIONLIST -- 4
EDITF_ADDOLDKEYUSAGE -- 8
EDITF_BASICCONSTRAINTSCRITICAL -- 40 (64)
EDITF_ENABLEAKIKEYID -- 100 (256)
EDITF_ENABLEDEFAULTSMIME -- 10000 (65536)
EDITF_ENABLECHASECLIENTDC -- 100000 (1048576)
CertUtil: -getreg command completed successfully.
PS C:\> certutil -getreg CA\Security
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\CertSvc\Configuration\Example Corp CA\Security:
Security REG_BINARY =
Allow CA Administrator BUILTIN\Administrators
Allow Certificate Manager BUILTIN\Administrators
Allow CA Administrator CORP\Domain Admins
Allow Certificate Manager CORP\Domain Admins
Allow CA Administrator CORP\Enterprise Admins
Allow Certificate Manager CORP\Enterprise Admins
Allow Enroll NT AUTHORITY\Authenticated Users
CertUtil: -getreg command completed successfully.
EDITF_ATTRIBUTESUBJECTALTNAME2 (0x40000) isn't in the list, and the CA permissions are as after installation: management for administrators, only Enroll for everyone else. But web enrollment is installed on this CA, and over HTTP:
PS C:\> (Invoke-WebRequest http://ca.corp.example.com/certsrv/ -SkipHttpErrorCheck).Headers['WWW-Authenticate']
Negotiate
NTLM
The 401 response offers NTLM over HTTP — exactly ESC8: authentication captured from any machine can be relayed here to get a certificate in its name. The fix is removing the ADCS-Web-Enrollment role if nobody uses it, or HTTPS with EPA.
And a template check for ESC1 worth putting on a schedule: published templates with the name from the request, no approval or agent signature, an authentication EKU (or no EKU, or Any Purpose), and Enroll for anyone other than administrators:
PS C:\> Import-Module Microsoft.PowerShell.Security # without it, nTSecurityDescriptor has an empty Access in PowerShell 7
PS C:\> $published = Get-ADObject -SearchBase "CN=Enrollment Services,$pks" -Filter * -Properties certificateTemplates | ForEach-Object certificateTemplates
PS C:\> Get-ADObject -SearchBase "CN=Certificate Templates,$pks" -Filter * -Properties displayName, msPKI-Certificate-Name-Flag, pKIExtendedKeyUsage, msPKI-Enrollment-Flag, msPKI-RA-Signature, nTSecurityDescriptor |
Where-Object { $_.Name -in $published -and ($_.'msPKI-Certificate-Name-Flag' -band 1) -and -not ($_.'msPKI-Enrollment-Flag' -band 2) -and -not $_.'msPKI-RA-Signature' -and
(-not $_.pKIExtendedKeyUsage -or $_.pKIExtendedKeyUsage -match '^(1\.3\.6\.1\.5\.5\.7\.3\.2|1\.3\.6\.1\.4\.1\.311\.20\.2\.2|1\.3\.6\.1\.5\.2\.3\.4|2\.5\.29\.37\.0)$') } |
ForEach-Object { $t = $_; $t.nTSecurityDescriptor.Access |
Where-Object { $_.AccessControlType -eq 'Allow' -and $_.ObjectType -eq '0e10c968-78fb-11d2-90d4-00c04f79dc55' -and $_.IdentityReference -notmatch '\\(Domain|Enterprise) Admins$' } |
Select-Object @{n='Template'; e={$t.displayName}}, IdentityReference }
Template IdentityReference
-------- -----------------
Host CORP\Domain Computers
Managed User CORP\Domain Users
0e10c968-… is the Enroll extended right, 1.3.6.1.5.2.3.4 is PKINIT Client Authentication. A PowerShell 7 subtlety: the Access property of a security descriptor is added by the Microsoft.PowerShell.Security module, and until it is loaded (the first Get-Acl call loads it, for example) Access is empty — and the check silently finds nothing. Windows PowerShell 5.1 doesn't have this trap.
Two templates someone made years ago. Managed User is a textbook ESC1: any domain user gets a certificate with Client Authentication and any name, without approval. Host is the same for any computer account, and by default any user can create a computer in the domain (the ms-DS-MachineAccountQuota quota is 10). Fix today: unpublish the template (Remove-CATemplate) if nobody uses it, or switch the name to "Build from AD", or turn on approval. The built-in Subordinate Certification Authority template, published on this CA, also matches the filter (no EKU, name from the request), but only administrators have Enroll on it, and the last condition filters it out. Corp Computer from our layout wouldn't appear here — its name comes from the directory — and Corp Web Server wouldn't either, because of approval and a server-only EKU.
Sources
How AD CS works and is installed:
- What is Active Directory Certificate Services — the role, CA types, enterprise and standalone, the link with the directory;
- Prepare the CAPolicy.inf File —
CAPolicy.infsections, empty[CRLDistributionPoint]and[AuthorityInformationAccess]for the root,LoadDefaultTemplates,Renewal*, no HTTPS in CDP and AIA; - Install-AdcsCertificationAuthority — installing root and subordinate CAs,
-OutputCertRequestFile; - certutil —
-setreg,-getreg,-dspublish,-installcert,-resubmit,-revoke,-crl,-verify -urlfetch,-view,-pulse,-store -enterprise; - certreq —
-submit,-retrieve,-new,-accept; - Configure the CDP and AIA Extensions on CA1 — CDP and AIA addresses, the publish and include-in-certificate options;
- Add-CACrlDistributionPoint and Add-CAAuthorityInformationAccess — the same settings from PowerShell, variables in addresses,
-AddToCertificateOcsp; - Configure CRL and Delta CRL Overlap Periods —
CRLOverlapUnitsandCRLOverlapPeriod, the default margin of 10% of the validity and no more than 12 hours; - Install-AdcsOnlineResponder — installing the OCSP responder;
- Import third-party CAs into Enterprise NTAuth store —
NTAuthCertificatesand certificate logon.
Templates and autoenrollment:
- Certificate template concepts — template versions, built-in templates and their versions, editing only copies;
- msPKI-Template-Schema-Version, msPKI-Certificate-Name-Flag and msPKI-Enrollment-Flag — template attributes and the meaning of their bits (MS-CRTD);
- Add-CATemplate and Get-CATemplate — publishing templates on the CA;
- Configure Group Policy to Autoenroll and Deploy Certificates and Configure Certificate Auto-Enrollment for Network Policy Server — the autoenrollment policy and its options;
- Get-Certificate — a manual request from a template,
-Url ldap:, pending requests; - Enable LDAP over SSL — requirements for a controller's LDAPS certificate.
Revocation, backup and auditing:
- RFC 5280 — X.509, CRLs and delta CRLs, the CDP and AIA extensions;
- RFC 6960 — OCSP;
- RFC 4556 — PKINIT;
- Backup-CARoleService and Restore-CARoleService — backing up the CA database and key;
- Audit Certification Services — the audit policy and events 4868–4900.
Security:
- Certificates security posture assessment — ESC1, ESC2, ESC3, ESC4, ESC6, ESC7, ESC8, ESC11 and ESC15: what counts as vulnerable and how to fix it;
- KB5014754: Certificate-based authentication changes on Windows domain controllers — the SID extension, strong mapping and its enforcement timeline;
- KB5005413: Mitigating NTLM Relay Attacks on AD CS — ESC8: HTTPS and EPA for web enrollment and CES.
Every computer and server now has a certificate. The next step is to use them for what they do better than a password: prove who is connecting to the network before letting them in.
Next — part 26: NPS and RADIUS. The RADIUS protocol and its place between an access point, a switch or a VPN gateway and the directory; the Network Policy Server role, clients and shared secrets, connection request policies and network policies and the order they are evaluated in; EAP types — PEAP-MSCHAPv2 and EAP-TLS — and a server certificate from our CA; and how to read the NPS log.
Only registered users can leave a comment.
Sign in or register to leave a comment.