S
Home Cryptography · Updated 2026-07-12

Overview

Active Directory Certificate Services (AD CS) is the Windows Server role that provides Public Key Infrastructure (PKI) services — issuing X.509 certificates for authentication, encryption, code signing, and TLS. It integrates tightly with Active Directory, enabling domain-joined devices and users to receive certificates automatically via Group Policy.

A well-designed AD CS deployment is foundational security infrastructure. A misconfigured one is a lateral-movement goldmine — ESC1 through ESC13 vulnerabilities (as catalogued by SpecterOps) allow domain privilege escalation through abusable certificate templates.


PKI Hierarchy Design

Two-Tier PKI Hierarchy

Why keep the Root CA offline?
If the issuing CA is compromised, the attacker can only mis-issue certificates that chain to that CA — they cannot forge the root. Taking the Root CA offline eliminates its attack surface to near-zero. Rotate the Root CA certificate every 10–20 years; the issuing CA certificate every 5 years.

Three-Tier Hierarchy

Add a Policy CA between Root and Issuing when you need to operate multiple issuing CAs under different policy regimes (e.g., internal vs. external issuance, or separate business units with different assurance levels). Most organisations do not need this complexity.

Three-Tier PKI Hierarchy

A single online Enterprise Root CA is common in lab environments and small deployments. Avoid it in production: the Root CA being online and domain-joined exposes your trust anchor to compromise.


Installation

Offline Root CA

The Root CA should run on Windows Server Core with no network interface (or a permanently disabled NIC). It does not need to be domain-joined.

# Install the AD CS role (no management tools needed on Server Core)
Install-WindowsFeature ADCS-Cert-Authority

# Configure as Standalone Root CA
Install-AdcsCertificationAuthority `
    -CAType StandaloneRootCA `
    -CACommonName "Contoso Root CA" `
    -KeyLength 4096 `
    -HashAlgorithmName SHA256 `
    -ValidityPeriod Years `
    -ValidityPeriodUnits 20 `
    -CryptoProviderName "RSA#Microsoft Software Key Storage Provider" `
    -Force

Key parameters to set immediately after install:

# Root CA validity for certificates it issues (SubCA certs)
certutil -setreg CA\ValidityPeriodUnits 10
certutil -setreg CA\ValidityPeriod "Years"

# Force CRL overlap period (prevents gaps during slow publishing)
certutil -setreg CA\CRLOverlapPeriodUnits 12
certutil -setreg CA\CRLOverlapPeriod "Hours"

# Root CA CRL publication interval (long — root rarely revokes)
certutil -setreg CA\CRLPeriodUnits 1
certutil -setreg CA\CRLPeriod "Years"

Restart-Service certsvc

Enterprise Issuing CA

The issuing CA is domain-joined and always online. Install it on a dedicated member server — do not co-locate with a domain controller.

# Install role and management tools
Install-WindowsFeature ADCS-Cert-Authority, RSAT-ADCS-Mgmt

# Configure as Enterprise Subordinate CA
Install-AdcsCertificationAuthority `
    -CAType EnterpriseSubordinateCA `
    -CACommonName "Contoso Issuing CA 01" `
    -KeyLength 4096 `
    -HashAlgorithmName SHA256 `
    -CryptoProviderName "RSA#Microsoft Software Key Storage Provider" `
    -OutputCertRequestFile "C:\CARequest\IssuingCA.req" `
    -Force

This produces a certificate signing request (.req). Copy it to the offline Root CA on physical media, sign it there, then bring the signed certificate back and install it:

# On Root CA — sign the SubCA request
certreq -submit -attrib "CertificateTemplate:SubCA" IssuingCA.req IssuingCA.cer

# On Issuing CA — install the signed certificate
certutil -installcert C:\IssuingCA.cer
Restart-Service certsvc

CDP and AIA Configuration

CDP (CRL Distribution Point) and AIA (Authority Information Access) extensions tell certificate consumers where to:

  • Download the CRL to check for revocation (CDP)
  • Download the CA certificate chain (AIA)
  • Query OCSP (AIA)

These URLs must be reachable from all clients that will validate certificates issued by this CA — including clients outside the domain, partners, and internet-facing services if you issue public-facing TLS certs.

# On the Issuing CA — set CDP and AIA extensions
# Replace pki.contoso.com with your actual PKI web server FQDN

$crlURL   = "http://pki.contoso.com/pki/%3%8%9.crl"
$aiaURL   = "http://pki.contoso.com/pki/%2%3.crt"
$ocspURL  = "http://ocsp.contoso.com/ocsp"
$ldapCRL  = "ldap:///CN=%7%8,CN=%2,CN=CDP,CN=Public Key Services,CN=Services,%6%10"
$ldapAIA  = "ldap:///CN=%7,CN=AIA,CN=Public Key Services,CN=Services,%6%11"

# Remove defaults, then add clean URLs
certutil -setreg CA\CRLPublicationURLs "1:$env:SystemRoot\System32\CertSrv\CertEnroll\%3%8%9.crl\n10:$ldapCRL\n2:$crlURL"
certutil -setreg CA\CACertPublicationURLs  "1:$env:SystemRoot\System32\CertSrv\CertEnroll\%2%3.crt\n3:$ldapAIA\n32:$aiaURL\n2:$ocspURL"

Restart-Service certsvc

# Publish CRL immediately after restart
certutil -crl

Variable substitution tokens (certutil):

Token Value
%1 Server DNS name
%2 CA sanitised name
%3 CRL name suffix
%6 Config DN
%7 CA short name
%8 CRL name suffix (delta)
%9 Delta CRL suffix
%10 CDP container
%11 AIA container

PKI Web Server (IIS)

Host CRL and CA certificate files on a simple IIS site. HTTP is required — HTTPS for CDP/AIA creates a chicken-and-egg problem because clients need to validate the cert on the HTTPS site to download the CRL they need to validate it.

# On your PKI web server
Install-WindowsFeature Web-Server, Web-Mgmt-Console

# Create virtual directory pointing to CertEnroll share
# IIS Manager → Default Web Site → Add Virtual Directory
# Alias: pki   Physical path: \\IssuingCA\CertEnroll

# Allow double escaping (required for delta CRL filenames with '+')
# In IIS, select the /pki vdir → Request Filtering → Edit Feature Settings
# → Allow double escaping: True

In web.config for the pki virtual directory:

<configuration>
  <system.webServer>
    <security>
      <requestFiltering allowDoubleEscaping="true" />
    </security>
    <staticContent>
      <mimeMap fileExtension=".crl" mimeType="application/pkix-crl" />
      <mimeMap fileExtension=".crt" mimeType="application/pkix-cert" />
    </staticContent>
  </system.webServer>
</configuration>

CRL Configuration

The CRL (Certificate Revocation List) is the signed list of revoked certificate serial numbers that relying parties download to check revocation status.

# Issuing CA — recommended CRL settings
certutil -setreg CA\CRLPeriodUnits 1
certutil -setreg CA\CRLPeriod "Weeks"          # Base CRL published weekly

certutil -setreg CA\CRLDeltaPeriodUnits 1
certutil -setreg CA\CRLDeltaPeriod "Days"      # Delta CRL published daily

certutil -setreg CA\CRLOverlapPeriodUnits 12
certutil -setreg CA\CRLOverlapPeriod "Hours"   # Overlap prevents gaps

Restart-Service certsvc
certutil -crl   # Publish immediately
Setting Recommended Notes
Base CRL validity 1 week Shorter = lower staleness risk; longer = lower bandwidth
Delta CRL validity 1 day Only contains changes since last base CRL
Overlap period 12 hours New CRL published before old one expires

OCSP (Online Certificate Status Protocol)

OCSP provides real-time revocation checking without downloading the full CRL. It is preferred for modern applications and is required for short-lived certificates.

# Install OCSP responder role on a separate server (or the issuing CA)
Install-WindowsFeature ADCS-Online-Cert, RSAT-ADCS-Mgmt

# Configure via Server Manager GUI or:
Add-CAOnlineResponder `
    -OCSPUri "http://ocsp.contoso.com/ocsp" `
    -CAName "Contoso Issuing CA 01" `
    -UseAllCACerts

OCSP Signing Certificate: The OCSP responder needs an OCSP Signing certificate issued by the CA it serves. Create a template for this (see Certificate Templates section) and configure the responder to auto-enroll for it.

OCSP signing certificates should have short validity (7 days) and auto-renew — this limits the window if the OCSP server is compromised.


Certificate Templates

Certificate templates define what certificates can be issued: key usage, validity period, subject name source, enrollment permissions, and issuance requirements.

Viewing and Managing Templates

# List templates published on the issuing CA
certutil -catemplates

# Open the Certificate Templates MMC snap-in
certtmpl.msc

# Publish a template to the issuing CA
Add-CATemplate -Name "WebServer" -Force

Web Server (TLS)

Setting Value
Compatibility Windows Server 2012 R2 / Windows 8.1
Subject name Supplied in request (requester provides SAN)
Key usage Digital Signature, Key Encipherment
Extended key usage Server Authentication (1.3.6.1.5.5.7.3.1)
Key size (minimum) 2048-bit RSA or P-256 ECDSA
Validity 1 year
Renewal 6 weeks before expiry
Enroll permissions Authenticated Users (or specific service account group)
CA manager approval Not required (use with caution for automation)

User Authentication (Smart Card / Certificate)

Setting Value
Subject name Built from AD (UPN in Subject Alternative Name)
Key usage Digital Signature
Extended key usage Client Authentication, Smart Card Logon
Validity 1 year
Private key User protected (smart card) or exportable (never for auth certs)
Enroll permissions Domain Users (auto-enrollment)

Computer (Machine Authentication)

Setting Value
Subject name Built from AD (DNS name)
Key usage Digital Signature, Key Encipherment
Extended key usage Client Authentication, Server Authentication
Validity 1 year
Enroll permissions Domain Computers
Auto-enrollment Enabled

Code Signing

Setting Value
Subject name Supplied in request
Key usage Digital Signature
Extended key usage Code Signing (1.3.6.1.5.5.7.3.3)
Validity 2 years
CA manager approval Required — code signing certs are high-value targets
Enroll permissions Restricted group (Developers, CI/CD service accounts)

Dangerous Template Settings to Avoid

These are the settings that underpin most AD CS privilege escalation techniques:

Setting Risk Mitigation
CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT Allows requester to specify any Subject / SAN including Domain Admin UPNs (ESC1) Only enable on templates where the requester must supply the subject, and restrict enroll permissions tightly
Any Purpose EKU or no EKU Certificate can be used for any purpose, including authentication (ESC2) Always specify explicit, minimal EKU set
Agent enrollment allowed Allows one account to enrol on behalf of another (ESC3) Only enable for specific managed enrolment scenarios; restrict permissions
Owner = low-privilege user Allows template modification (ESC4) Template owner should always be Domain Admins or Enterprise Admins
Enroll permissions = Domain Users + CA approval not required Any domain user can obtain a cert with dangerous settings Always scope enroll ACL to minimum required group
# Audit for ESC1-vulnerable templates (supply subject + low-privilege enroll)
# Requires PSPKI module or Certify/Certipy tools
Import-Module PSPKI
Get-CertificateTemplate | Where-Object {
    $_.Settings.SubjectName -match 'EnrolleeSupplied' -and
    ($_.Security | Where-Object { $_.Principal -match 'Domain Users' -and $_.Rights -match 'Enroll' })
}

Auto-Enrollment

Auto-enrollment via Group Policy allows domain-joined computers and users to automatically receive and renew certificates without manual intervention.

Configuring Auto-Enrollment GPO

Group Policy Management → Create or Edit a GPO linked to the domain or OU

Computer Configuration → Windows Settings → Security Settings
  → Public Key Policies → Certificate Services Client - Auto-Enrollment
    → Enroll certificates automatically: Enabled
    → Renew expired certificates, update pending certificates: Checked
    → Update certificates that use certificate templates: Checked

User Configuration → Windows Settings → Security Settings
  → Public Key Policies → Certificate Services Client - Auto-Enrollment
    → (Same settings as above)

Verifying Auto-Enrollment

# Force GPO update and trigger certificate enrollment
gpupdate /force
certutil -pulse

# Check enrolled certificates
Get-ChildItem Cert:\LocalMachine\My
Get-ChildItem Cert:\CurrentUser\My

# View auto-enrollment events
Get-WinEvent -LogName "Microsoft-Windows-CertificateServicesClient-AutoEnrollment/Operational" |
    Select-Object TimeCreated, Message -First 20

NDES (Network Device Enrollment Service)

NDES implements the Simple Certificate Enrollment Protocol (SCEP), allowing network devices (routers, switches, firewalls, MDM-managed mobile devices) to enrol for certificates without domain membership.

# Install NDES
Install-WindowsFeature ADCS-Device-Enrollment, Web-Server, Web-Asp-Net45

# Configure NDES
Install-AdcsNetworkDeviceEnrollmentService `
    -ApplicationPoolIdentity `
    -RAName "Contoso NDES RA" `
    -RAEmail "pki@contoso.com" `
    -RACompany "Contoso" `
    -RADepartment "IT Security" `
    -RACity "London" `
    -RAState "England" `
    -RACountry "GB" `
    -SigningProviderName "Microsoft Strong Cryptographic Provider" `
    -EncryptionProviderName "Microsoft Strong Cryptographic Provider" `
    -Force

NDES security considerations:

  • Place NDES in a DMZ or on a dedicated server — it exposes an unauthenticated HTTP endpoint
  • Integrate with Intune or a third-party MDM for device identity verification before certificate issuance
  • Require CA Manager approval on the NDES template for high-assurance device certificates
  • Monitor NDES for unusual certificate request volumes (potential abuse)

Key Archival

Key archival lets the CA escrow the private key of encryption certificates, allowing recovery if the user loses their key. Never archive signing or authentication private keys.

# Enable key archival on the CA
certutil -setreg CA\UseDefinedCACertInRequest 0

# Create a Key Recovery Agent (KRA) certificate
# Use the built-in "Key Recovery Agent" template
# The KRA account should be a dedicated service account, not a personal account

# Enable archival on an encryption certificate template
# Template Properties → Request Handling → Archive subject's encryption private key: Checked

Key Recovery

# Find the archived key by requester or serial number
certutil -getkey <serial_number> output.blob

# Recover the key (requires KRA certificate and private key)
certutil -recoverkey output.blob recovered_key.pfx

Auditing & Monitoring

AD CS generates events in the Security and Application event logs. Enable CA auditing for all operations in a production deployment.

# Enable all CA audit events
certutil -setreg CA\AuditFilter 127
Restart-Service certsvc

CA Audit Filter values:

Bit Value Event
0 1 Start/stop AD CS
1 2 Back up/restore CA
2 4 Issue/revoke certificate
3 8 Store revoked certificate to CA database
4 16 Store certificate request to CA database
5 32 Revoke certificate
6 64 Publish CRL

Set AuditFilter 127 to enable all events.

Key Event IDs to Monitor

Event ID Source Meaning
4886 Security Certificate request received
4887 Security Certificate issued
4888 Security Certificate request denied
4889 Security Certificate request set to pending
4890 Security CA settings changed
4896 Security One or more rows deleted from CA database
4898 Security CA template loaded
4899 Security CA template updated
4900 Security CA template security updated

Alert on 4890 (CA settings changed) and 4896 (database rows deleted) — these should almost never occur in production.

Certificate Expiry Monitoring

# List all issued certificates expiring within 60 days
certutil -view -restrict "NotAfter>=now,NotAfter<=(now+60:00)" `
    -out "RequestID,RequesterName,CommonName,NotAfter,CertificateTemplate"

# Export expiry report to CSV
certutil -view -restrict "NotAfter>=now,NotAfter<=(now+60:00)" `
    -out "RequestID,RequesterName,CommonName,NotAfter" csv > expiring.csv

Backup & Recovery

Backing Up the CA

# Back up the CA database and private key (interactive — prompts for password)
certutil -backupca -password P@ssw0rd! C:\CABackup

# Non-interactive version (for scheduled tasks)
certutil -backupdb C:\CABackup\DB
certutil -backupkey -password P@ssw0rd! C:\CABackup

# Alternatively, back up via CA MMC:
# certsrv.msc → Right-click CA → All Tasks → Back up CA

The backup includes:
- CA database (issued certs, pending requests, revocation info)
- CA private key and certificate (as a PKCS#12 / PFX file)

Scheduled Backup Script

# Save as Backup-CA.ps1 and schedule via Task Scheduler
$BackupRoot = "\\fileserver\PKIBackups"
$Date       = Get-Date -Format "yyyy-MM-dd"
$BackupPath = "$BackupRoot\$Date"
$Password   = (Get-Secret -Name "CABackupPassword").Password  # from Windows Credential Manager

New-Item -ItemType Directory -Path $BackupPath -Force | Out-Null

certutil -backupdb "$BackupPath\DB"
certutil -backupkey -password $Password "$BackupPath"

# Verify backup integrity
certutil -verifystore "$BackupPath\*.p12" | Out-File "$BackupPath\verify.log"

# Retain 30 days of backups
Get-ChildItem $BackupRoot -Directory |
    Where-Object { $_.CreationTime -lt (Get-Date).AddDays(-30) } |
    Remove-Item -Recurse -Force

Restoring the CA

# Stop the CA service
Stop-Service certsvc

# Restore database
certutil -restoredb C:\CABackup\DB

# Restore private key
certutil -restorekey C:\CABackup\<CA>.p12 -password P@ssw0rd!

# Restart
Start-Service certsvc

Security Hardening

Role Separation

AD CS supports four roles. Assign them to separate accounts in high-assurance environments:

Role Default Purpose
CA Administrator Domain Admins Configure the CA
Certificate Manager CA Administrators Issue, revoke, and manage certificates
CA Auditor CA Administrators Manage CA audit log
CA Backup Operator CA Administrators Backup/restore the CA
# Assign roles via certutil or CA MMC
# certsrv.msc → Right-click CA → Properties → Security

# Add a dedicated Certificate Manager account
certutil -administer -addacct "CONTOSO\CA-CertMgr" "Certificate Managers"

Hardening Checklist

☐ Root CA is offline and not domain-joined
☐ Issuing CA is on a dedicated Server Core (no GUI) member server
☐ CA server is not a domain controller
☐ All CA administrative roles are assigned to dedicated service accounts (not personal accounts)
☐ CA server has no internet access (outbound firewall rule)
☐ Physical access to CA server is restricted (server room, no KVM-over-IP)
☐ BitLocker enabled on CA server OS and data volumes
☐ TPM-bound BitLocker on the issuing CA (keys unlocked at boot by TPM)
☐ Uninstall Protection token stored in PAM vault, not in scripts
☐ CA audit logging enabled (AuditFilter = 127)
☐ CA database backup runs nightly; backup tested for restore quarterly
☐ CDP and AIA URLs resolve from all client networks
☐ CRL overlap period configured to prevent gaps
☐ No templates with CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT + open enroll ACL
☐ Template owners are Domain Admins or Enterprise Admins
☐ ESC1-8 assessment run (Certipy, Certify, or PSPKI audit)
☐ NDES server (if deployed) is in a separate tier, not the issuing CA
☐ Expiry alerting configured for CA certificate (flag at 90 and 30 days)
☐ Windows Firewall configured to allow only: RPC (for CA), LDAP (for AD queries), HTTP (CDPs), and management ports

Firewall Rules for Issuing CA

# Allow inbound certificate enrollment (RPC endpoint mapper + dynamic ports)
New-NetFirewallRule -DisplayName "AD CS — RPC Endpoint Mapper" `
    -Direction Inbound -Protocol TCP -LocalPort 135 -Action Allow

New-NetFirewallRule -DisplayName "AD CS — RPC Dynamic" `
    -Direction Inbound -Protocol TCP -LocalPort 49152-65535 -Action Allow

# Allow inbound from PKI web server only (for CRL file sync)
New-NetFirewallRule -DisplayName "AD CS — CRL Sync" `
    -Direction Inbound -Protocol TCP -LocalPort 445 `
    -RemoteAddress "10.10.10.50" -Action Allow  # PKI web server IP

# Block all other inbound
New-NetFirewallRule -DisplayName "AD CS — Block All Other Inbound" `
    -Direction Inbound -Action Block -Priority 9000

Assessing Your PKI with Certipy

Certipy is the standard open-source tool for auditing AD CS deployments. Run it from a domain-joined machine or via authenticated LDAP queries.

# Find all certificate templates and assess for known misconfigurations
certipy find -u 'user@contoso.com' -p 'Password' -dc-ip 10.10.10.1

# Check for ESC1 (template allows subject supply + low-privilege enroll)
certipy find -vulnerable -u 'user@contoso.com' -p 'Password' -dc-ip 10.10.10.1

# Output a BloodHound-compatible JSON for visualisation
certipy find -u 'user@contoso.com' -p 'Password' -dc-ip 10.10.10.1 -bloodhound

Common ESC findings and remediations:

ESC Vulnerability Remediation
ESC1 Template allows SAN supply + low-priv enroll Remove CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT or restrict enroll ACL
ESC2 Any-purpose EKU or no EKU Add explicit, minimal EKU; remove Any Purpose
ESC3 Certificate Request Agent template + delegated enroll Restrict enrollment agent permissions
ESC4 Template owner is low-privilege Set owner to Domain Admins / Enterprise Admins
ESC6 CA has EDITF_ATTRIBUTESUBJECTALTNAME2 flag certutil -setreg CA\EditFlags -EDITF_ATTRIBUTESUBJECTALTNAME2
ESC7 Low-privilege user has CA Manage CA right Audit and tighten CA security ACL
ESC8 AD CS HTTP endpoints with NTLM auth (relay target) Disable HTTP enrollment endpoints or require HTTPS+EPA
# Fix ESC6 — remove the dangerous flag that allows SAN in any request
certutil -setreg CA\EditFlags -EDITF_ATTRIBUTESUBJECTALTNAME2
Restart-Service certsvc

Common Administrative Tasks

Revoking a Certificate

# Revoke by serial number (get from CA database or certutil -view)
certutil -revoke <serial_number> 0   # 0 = Unspecified
certutil -revoke <serial_number> 1   # 1 = Key Compromise
certutil -revoke <serial_number> 5   # 5 = Cessation of Operation

# Publish updated CRL immediately after revocation
certutil -crl

Viewing the CA Database

# List all issued certificates
certutil -view -restrict "Disposition=20" -out "RequestID,RequesterName,CommonName,NotAfter"

# Find certificates for a specific user
certutil -view -restrict "RequesterName=CONTOSO\jsmith"

# Find all valid computer certificates
certutil -view -restrict "CertificateTemplate=Machine,Disposition=20"

Renewing the CA Certificate

The issuing CA certificate must be renewed before it expires. The new certificate must have a validity that falls within the root CA certificate's remaining validity.

# On the Issuing CA — generate a renewal request
certutil -renewCert ReuseKeys   # Keep same key pair
# or
certutil -renewCert             # Generate new key pair (preferred)

# Submit the .req to the Root CA (offline), install the returned .cer
certutil -installcert IssuingCA_Renewed.cer
Restart-Service certsvc

See Also

The Security Architecture Site — for internal reference use. Back to contents