Note to the reader: this document is for educational and lab purposes only. A more fully featured document with more detail around setting up environment and infrastructure in a more inherently secure way is in the works.
Thanks!
Why should I care about PKI?
If you found this guide we can assume you are interested in establishing a PKI. Great! PKI is an important technology that allows us to cryptographically secure identities of things like people and computers. This is important because we need to know who is on the other end of the pipe and have confidence that our information is protected. To do this we create a Chain of Trust from computers we verify. We set up a base of authority, called a Root Certificate Authority, and base our trust from there. From the Root Certificate Authority we trust our Issuing Certificate Authority. Once we set up this infrastructure the Issuing Certificate Authority [CA from this point forward] will create trust for other objects in the form of Certificates. With proper controls we can be reasonably sure that if a computer or person presents a Public Certificate and proof of ownership of that certificates Private Key they are in fact who they claim to be.
Notes before proceeding
This guide is written with the intention of setting up a lab environment. This guide does not cover all needed steps for a secure offline CA, which if you are working in a production environment you should be considering the most secure solution. A more detailed list of considerations can be found at the bottom of this guide.
Setting up a Two-Tiered Windows PKI
In this guide I will run you through the steps to install and configure a two-tiered Windows PKI in a typical Active Directory supported domain. We are assuming you have the following items:
- A freshly imaged Windows Server 2016 OS to function as the Root Certificate Authority and CRL Distribution Point
- A freshly imaged Windows Server 2016 OS to function as the Issuing Certificate Authority
- Windows Domain Services installed and configured with access to Domain Admin and Enterprise Admin accounts
- A client server we wish to issue a certificate to
Net Diagram

Configuring your Root CA
The first step after OS deployment to the Root CA is configuring the ADCS [Active Directory Certificate Services] Role on the Root CA.
In an elevated PowerShell prompt use the command:
Add-WindowsFeature ADCS-Cert-Authority -IncludeManagementTools
Once we have confirmed the ADCS role has been installed we can begin configuration. Prepare your PowerShell script to deploy the CA. My example command is:
$CAName = "LaGrassa.org Root CA 01"
$CryptoProvider = “RSA#Microsoft Software Key Storage Provider”
$HashAlgo = "SHA256"
$KeyLength = "2048"
$YearsValid = "30"
Install-ADCSCertificationAuthority -CACommonName $CAName -CAType StandaloneRootCA -CryptoProviderName $CryptoProvider -HashAlgorithmName $HashAlgo -KeyLength $KeyLength -ValidityPeriod Years -ValidityPeriodUnits $YearsValid
Variable Breakdown
$CAName – The name you want to give your Root Certification Authority
$CryptoProvider – The cryptographic provider to store the private keys for your Root CA certificates. If you are using a Hardware Security Module [HSM] you will need to install and configure the HSM on the Root CA before this step. Refer to your HSM documentation for that process as well as the name of your cryptographic storage provider to input in this variable. We are using the default Microsoft software cryptographic provider for the purpose of this guide.
$HashAlgo – This is the algorithm we are using to generate our Root CA certificate. SHA256 is currently considered a secure industry standard hashing algorithm. Do not use older or less secure hashing algorithms.
$KeyLength – The number (in bits) in our encryption key. Longer does not always necessarily mean better or more secure. 2048 is a currently accepted industry standard for RSA256 key encryption length.
$YearsValid – The number (in years) our RootCA certificate will be valid for. Typically we want these periods to be short but for Root CAs this is different. We want the certificate to be generates and to bring the server offline, meaning we want a key that is valid essentially in perpetuity. The only time this key should ever be changed is in the event the algorithm used to generate becomes insecure (See SHA1) or the Root CA has been compromised. Either situation will require manual intervention which will require regeneration of the Root CA certificate under new parameters.
After running the Install-ADCSCertificateAuthority you will be promped to confirm the action. Click “Yes“

Ensure the returned output ErrorID is 0 (Zero)
Our RootCA is a standalone CA not joined to any domain. Despite this there are items to configure in AD to get the full range of tools available to Windows Certificate Services. The options we must configure is as follows:
DSConfigDN – Because we have specific Distinguished Names in our Root CA we need the Root CA to be aware of our domain information to properly publish certificates.
We can configure the registry to update our DN [Distinguished Name] configs.
On your Root CA open PowerShell as an administrator and run:
certutil.exe –setreg ca\DSConfigDN CN=Configuration,DC=lagrassa,DC=org
Be sure to update your DC information unless you are planning a hostile takeover of my homelab.
Once you have updated and run the command ensure the output shows success.

Be sure to restart Certificate Services on the Root CA. This can be done with PowerShell using the command:
Get-Service CertSVC | Restart-Service
Define CDP Location – We need to define where computers can check certificate revocation status, or CRLs. This is done through Active Directory or through clear web traffic [HTTP on port 80. [Note: CRLs MUST be published accessible by HTTP on port 80. using HTTPs on port 443 will not work] For our guide we will setting up a Web Server directly on the Root CA.
To install the Web Server role on the Root CA open an Administrative PowerShell prompt and use the command:
Install-WindowsFeature Web-WebServer -IncludeManagementTools
Ensure the output of the command shows Success = True

We must now configure a directory to serve as the Web Server virtual directory to publish the CRL files. This can be done by using the following command:
mkdir C:\CertEnroll
Create a new SMB [Windows] share using the following command.
New-SMBshare -name CertEnroll C:\CertEnroll
Once the SMB Share has been created open IIS [Internet Information Services] on the Root CA. Navigate to $Server > Sites > Default Website and under Actions click View Virtual Directories. Click Add Virtual Directory… and enter the alias as CertEnroll and the physical path as C:\CertEnroll.
Now we need to allow directory browsing via IIS in order for clients to read the CRL files that will be published on the Root CA Web Server. On Root CA open IIS [Internet Information Services] and navigate to $Server > Sites > Default Web Site > CertEnroll and double click Directory Browsing.
Under the actions pane click Enable.
Let’s test to make sure we can add files to serve. On the Root CA open File Explorer and navigate to C:\CertEnroll. Add a file. For this case I am creating Test.txt.
Restart IIS Services by opening an Administrative PowerShell prompt and entering the command
IISReset
Once the IIS Services have been restarted check the web server by navigating to http://$ServerIP/CertEnroll in a web browser

We can see that along side web.config is the test file we have just created. This confirms we can add new data to the web server to be served to clients.
Configure DNS – If you have not already done so we must configure the DNS settings for your Root CA. Ensure good network hygiene by setting a static address and DHCP reservation for your Root CA, as well a FQDN for the Root CA. For this example my root CA’s FQDN will be RootCA-01.lagrassa.org. Because the Root CA is not joined to the domain you may need to manually create an A record that resolves your FQDN to the IP address of the Root CA.
Confirm you can access the web share by using the FQDN of the Root CA with http://$FQDN/CertEnroll before continuing.
Configure CDP – We are now ready to configure the CRL Distribution Point. In an Administrative PowerShell prompt enter the following command, updating your CDP URL.
certutil -setreg CA\CRLPublicationURLs "1:C:\Windows\system32\CertSrv\CertEnroll\%3%8%9.crl \n10:ldap:///CN=%7%8,CN=%2,CN=CDP,CN=Public Key Services,CN=Services,%6%10\n2:http://RootCA-01.lagrassa.org/CertEnroll/%3%8%9.crl"
Confirm the output is successful and restart the Certificate Services service using PowerShell.
Get-Service CertSVC | Restart-Service
Update AIA (Authority Information Access) – This information is included in each certificate and defined where a computer or application can check the Issuing CA’s certificate. We can define this to be the same path as the CDP using PowerShell
certutil -setreg CA\CACertPublicationURLs "1:C:\Windows\system32\CertSrv\CertEnroll\%1_%3%4.crt\n2:ldap:///CN=%7,CN=AIA,CN=Public Key Services,CN=Services,%6%11\n2:http://RootCA-01.lagrassa.org/CertEnroll/%1_%3%4.crt"
Confirm the output is successful and restart the Certificate Services service using PowerShell.
Get-Service CertSVC | Restart-Service
Setting CA Time Limits – Because the Root CA will only be issuing certificates to Issuing CAs we need to configure longer time periods for issued certificates. We can configure this using CertUtil. In an Administrative PowerShell prompt enter the following commands:
certutil -setreg ca\ValidityPeriod "Years"
certutil -setreg ca\ValidityPeriodUnits 5
This will set certificates issued from the Root CA to be valid for 5 years.
We will also need to configure CRL time limits. Here are the example settings we will be using. Please note; if this CA is configured to be offline the Delta CRL should be disabled.
Certutil -setreg CA\CRLPeriodUnits 13
Certutil -setreg CA\CRLPeriod "Weeks"
Certutil -setreg CA\CRLDeltaPeriodUnits 0
Certutil -setreg CA\CRLOverlapPeriodUnits 6
Certutil -setreg CA\CRLOverlapPeriod "Hours"
Confirm the output is successful and restart the Certificate Services service using PowerShell.
Get-Service CertSVC | Restart-Service
Generating the first CRL – We are now ready to generate our first CRL. This can be done simply by using the following command in an administrative PowerShell prompt:
certutil -crl
Verify the the command completed sucessfully and check the following directory: C:\Windows\System32\CertSrv\CertEnroll
We should see two items created; a CRL for the Root CA and the Root CA Certificate itself.

Copy CRL To Web Server – Now that we have our Root CA Certificate and CRL we can copy them over to the Web Server. You can do this via GUI or use the following PowerShell command:
$Source = "C:\Windows\System32\CertSrv\CertEnroll\*"
$Destination = "C:\CertEnroll"
Get-ChildItem -Path $Source -Recurse -Include *.* -File | Copy-Item -Destination $Destination
Confirm the command completed sucessfully and check the web server once more. We should see the newly added Root CA Certificate and CRL.
Publish the Root CA Certificate and CRL in AD – We are now ready to distribute the Root CA certificate. Log on to a Domain Controller with at least Domain Admin credentials and copy the Root CA Certificate as well as the CRL to the Domain Controller.
To import the Root CA certificate open an Administrative Powershell prompt and use the following command:
certutil –f –dspublish "PATH\TO\Cert.crt" RootCA
Verify the output as successful and move on to the CRL:
certutil –f –dspublish "PATH\TO\CRL.crl"
Confirm the output as successful. At this point we have a functioning Root CA. It is time to move on to setting up our Issuing CA.
Configuring your Issuing CA
Your issuing CA should be a normal domain-joined server. Log on to your Issuing CA as a Domain Administrator.
We will install the ADCS Role similarly to how we did for the Root CA. Launch an administrative PowerShell prompt and enter the following:
Add-WindowsFeature ADCS-Cert-Authority -IncludeManagementTools
Confirm the output comes back as successful. We can now configure the role using the following command. Make sure to update your variables:
$CAName = "LaGrassa.org Issuing CA 01"
$CryptoProvider = “RSA#Microsoft Software Key Storage Provider”
$HashAlgo = "SHA256"
$KeyLength = "2048"
Install-ADcsCertificationAuthority -CACommonName $CAName -CAType EnterpriseSubordinateCA -CryptoProviderName $CryptoProvider -HashAlgorithmName $HashAlgo -KeyLength $KeyLength
Confirm the install action by clicking Yes
You will receive a warning that the installation is incomplete. We now need to do two things; Install the ADCS Web Enrollment service and issue the certificate for the Issuing CA.

Navigate to the C:\ directory and copy the .REQ file that was created to your Root CA. On your Root CA open the Certificate Services MMC console and right click the name of your Root CA. Hover over All Tasks and click Submit new request…

Once the request has been submitted navigate to Root CA > Pending Requests
We should see the request for the Issuing CA listed. Right click the request and click All Tasks > Issue
Under the Issued Certificates tab we should see the newly issued certificate.
Double click the certificate to inspect it. Click the Details tab and click Copy to File…
Navigate through the wizard to export your Issuing CA certificate. Select Base-64 encoded X.509 as the format.
Once you have the exported certificate copy it to your Issuing CA.
On the Issuing CA start the Certificate Services MMC.
Right click the name of your Issuing CA and click All Tasks > Start Service
You will recieve a notification that the certificate for the Issuing CA is missing and you will be asked to install it. Click Yes.
A File Explorer window will open. Navigate to the Issuing CA certificate you previously copied over. You may need to change the listed file extensions to see the certificate.
You will get a warning that the certificate is from an Untrusted Provider. This is normal. Click OK.
Ensure ADCS started and the MMC populates with your Issuing CA.
Installing and Configuring ADCS Web Enrollment
On your issuing CA open an Administrative PowerShell prompt and enter the following:
Add-WindowsFeature ADCS-web-enrollment
Ensure the command completed successfully, then install the role:
Install-ADCSwebenrollment
Click Yes in the confirmation window and ensure the command completed successfully.
Issuing CA Post Configuration Tasks
Now that we have the Issuing CA spun up with a valid certificate it is time to prepare it for issuance to other clients. We are going to perform similar tasks as we did for the Root CA, including determining the CDP location, AIA location, and Certificate Time Limits.
CDP Location Configuration
On the Issuing CA we must set the web location for the CDP. We can do this using CertUtil. Enter the following command in an Administrative PowerShell prompt, updating your URL
certutil -setreg CA\CRLPublicationURLs "1:$ENV:WINDIR\system32\CertSrv\CertEnroll\%3%8%9.crl\n2:http://RootCA-01.lagrassa.org/CertEnroll/%3%8%9.crl\n3:ldap:///CN=%7%8,CN=%2,CN=CDP,CN=Public Key Services,CN=Services,%6%10"
AIA Location Configuration
Similar to CDP, enter the following CertUtil command in an Administrative PowerShell prompt, updating your URL information.
certutil -setreg CA\CACertPublicationURLs "1:$ENV:WINDIR\system32\CertSrv\CertEnroll\%1_%3%4.crt\n2:http://RootCA-01.lagrassa.org/CertEnroll/%1_%3%4.crt\n3:ldap:///CN=%7,CN=AIA,CN=Public Key Services,CN=Services,%6%11"
CA/CRL Time Limit Configuration
We can also use CertUtil to configure the CRL settings. We will be configuring certificates to be valid for one year with a one week CRL vality period, with the Delta CRL overlap being configured to 3 days. In an Administrative PowerShell prompt:
certutil -setreg CA\CRLPeriodUnits 7
certutil -setreg CA\CRLPeriod “Days”
certutil -setreg CA\CRLOverlapPeriodUnits 3
certutil -setreg CA\CRLOverlapPeriod “Days”
certutil -setreg CA\CRLDeltaPeriodUnits 0
certutil -setreg ca\ValidityPeriodUnits 1
certutil -setreg ca\ValidityPeriod “Years”
After confirming no errors in the output be sure to restart the Certificate Service.
Get-Service CertSVC | Restart-Service
Generate Issuing CA CRLs
Just like the Root CA the Issuing CA will have CRLs we need to generate. In an Administative PowerShell prompt run:
certutil -crl
The CRLs are now published under C:\Windows\System32\CertSrv\CertEnroll
Publish the CRLs
Now we can copy the issuing CA certificate as well as Issuing CA CRL to the Web Server on the Root CA. Copy the items into the Web Server configuration folder and ensure the items appear when browsing to the configured CRL URL.
Configuring your CA Template
Congrats! We now have a functioning PKI. That doesn’t mean a whole lot unless we tell it to do something though. We will now configure and publish our first template to use our new PKI. We will be duplicating an existing Computer template to issue a certificate to a group of computers on our domain.
Log on to your Issuing CA as a Domain Administrator
Open your Certification Authority MMC and right click Certificate Templates and click Manage.
In the newly opened Certificate Templates console right click the Computer template and select Duplicate Template.
We now have some settings to fill out. I will give my examples but you will need to modify them to fit your environment.
Click the General tab.
Update Template display name to LagrassaOrg Servers
Click the Security tab.
Click Add…
Enter the name of the security group that contains the servers you would like to issue certificates to.
Ensure the group has Read, Enroll, and Autoenroll permissions.
Click the Subject Name tab.
Ensure Build from this Active Directory information radio button is selected and the Subject name format is listed as Fully distinguished name. Ensure DNS name is selected under Include this information in alternate subject name.
Click Apply
Go back to your Certification Authority MMC on Issuing CA 1. Right click Certificate Templates and select New > Certificate Template to Issue
Select LaGrassaOrg Servers and click OK.
Testing Manual Enrollment
It’s now time to issue our first certificate. Log on to a server included in the group we gave permissions to the CA template to.
Update Group Policy by opening an Administrative PowerShell prompt and entering
gpupdate /force
After ensuring Group Policy updated correctly open up the Computers Certificate MMC
certlm.msc
Click the Personal certificate store and right click Certificates > All Tasks > Request New Certificate
Click Next
Click Active Directory Enrollment Policy and click Next
You should see our newly published template LaGrassaOrg Servers. Click the radio button next to our template and click Enroll
Once the enrollment process is completed you should get a notice that the STATUS: is Succeeded.
In the Local Computer Certificate Services MMC we should see the newly issued cert. Clicking the Certification Path tab shows us our newly issued certificate chaining up from the Issuing CA to the Root CA.

Testing Automated Enrollment Using Group Policy
We have a working template! Great! The problem now is that issuing certificates one at a time is both tedious and time consuming. We can automate this process using Group Policy.
To automatically deploy certificates log on to a Domain Controller and open the Group Policy Management MMC.
Right click Group Policy Objects and select New
Give the new GPO a descriptive name. For this case I am using LaGrassaOrg Certificate Autoenrollment
Right click the new GPO and click Edit
Navigate to Computer Configuration > Policies > Windows Settings > Security Settings > Public Key Policies.
Double click Certificate Services Client – Certificate Enrollment Policy
Change Configuration Model to Enabled and click OK.
Right click an OU you would like to configure Autoenrollment for and click Link an Existing GPO…
Select the Autoenrollment GPO you just configured.
Log on to a server in the contained OU that is a member of the Autoenrollment security group we configured earlier.
Update Group Policy by opening an Administrative PowerShell prompt and entering
gpupdate /force
Wait a few moments for Group Policy to update and for the server to automatically request the certificate.
Open the Local Machine Certificates MMC
certlm.msc
Under the Personal store we should see our newly automatically issued certificate. Clicking the Certification Path tab shows us the certificate is properly chaining from our client to the Issuing CA to our Root CA.

Congrats! We are finally at the end of our PKI journey to set up a two-tiered Windows PKI and issue certificates from it.
Final Considerations – Or why we’re not done if we’re setting up a production environment
While we have a working PKI there are a few things that need to be addressed before moving this to a production environment.
- Issues:
- The Root CA is a Virtual Machine. While not necessarily a bad thing a Root CA should be designed to be airgapped and offline when not generating new CRLs. While not ideal a cost effective solution is to install the Windows Server OS that will be running the Root CA on a laptop with the network cards permanently disabled. Proper controls such as enabling BitLocker with a TPM PIN and regular encrypted backups to an external physical device make this solution cost effective, relatively easy to manage, and survivable.
- The Root CA is the CRL web server. This is NOT a good idea for production for the reasons listed above. One of the major points of having a two-tiered PKI infrastructure is that our Root CA does not need to be online and can be airgapped. In an ideal world your Root CA would be a separate physical server with secured physical access. With this model it is possible that the Root CA could be attacked/compromised on the exposed web server even if all other considerations are taken into account. A proper setup would involve an independent web server that would have the CRLs manually loaded on to them. This will be explored in a future guide.
- CRL Publication is not automatic. We need our CRLs from both the Issuing and Root CAs available on a public web server. Automation should be explored to automatically generate the CRLs using certutil -crl and copying them over to the web server in one step if possible. Stronger controls [manually generating and moving CRLs] are possible but require manual intervention by definition, and a missed CRL can cause massive issues for any environment that relies on it’s PKI as clients and servers will be unable to verify certificate revocation status.