I inherited a Windows PKI setup that had the Root CA installed on a Windows Server 2008 R2 Domain Controller, with the root certificate signed with a SHA1 hash. That DC was in the process of being decommissioned, and I also wanted to move to a better PKI design.
I’d previously set up 2-tier Windows PKI infrastructures with offline Root CAs, so I knew that this was the route I was going to take again (note that this is for an SMB environment).
There are plenty of good guides on configuring a 2-tier Windows PKI. In my opinion the best of the crop at the time of writing is probably Timothy Gruber’s 7-part guide to deploying a PKI on Windows Server 2016.
I would, however, highly recommend reading up on the topic before blindly following a guide. PKI is a complex topic, and you want to make the correct decisions up-front to avoid issues later on. Some additional recommended reading:
- MS Directory Services Team Blog: Moving Your Organization from a Single Microsoft CA to a Microsoft Recommended PKI
- Best Practices for Implementing a Microsoft Windows Server 2003 Public Key Infrastructure (Still contains some useful info)
- Aaron Parker: Deploying an Enterprise Root Certificate Authority
- Andrzej Kaźmierczak: The DOs and DON’Ts of PKI – Microsoft ADCS
- Microsoft Docs: Server 2012/2012 R2 Certification Authority Guidance
There are many recommendations around where to publish/advertise the AIA and CDP. Some of these include:
- In the default location – LDAP and locally via HTTP on the CA server
- To an internally-hosted web server, and then reverse-proxy connections from the Internet
- To an externally-hosted web server
I’d already used Azure Blob Storage to store some other small files, so I thought I’d have a go at seeing if it’s able to be used for AIA and CDP storage. As it turns out, it’s quite easy to do, and you don’t even need to mess around with double-escaping like you would need to if you hosted on IIS or an Azure Web App:
TLDR; The CA saves the CRL files to the default location of C:\Windows\System32\CertSrv\CertEnroll, and AzCopy then copies them up to an Azure Blob Storage account that’s configured with a custom domain of pki.yourdomain.com
Here are the requirements to get this all set up:
- CDP and AIA on Enterprise/issuing CA configured to save to the default C: location, and also advertise availability at http://pki.yourdomain.com
- AzCopy installed on the Enterprise CA
- Allow outbound HTTPS/443 from the Enterprise CA to Azure Blob Storage
- An Azure Storage Account with blob storage configured for HTTP access. I’d recommend at least Zone Redundant Storage for availability.
- A custom domain name for the above storage account
- A folder in the blob storage named ‘pki’ (not necessary, but you’ll need to adjust the script if you don’t use this folder)
- A SAS key with read/write/change access to blob storage only (don’t assign more access than necessary)
- A scheduled task running hourly as NETWORK SERVICE to call the below PowerShell script
- Ensure that NETWORK SERVICE has modify permissions to the log location (default is %ProgramData%\ScriptLogs\Invoke-UpdateAzureBlobPKIStorage.log)
You’ll need to manually copy your offline root CA certificate and CRL to the blob storage location. This script is designed to copy the much more frequent CRLs and Delta CRLs from your Enterprise CA to blob storage.
As it turns out, AzCopy is perfect for this because it supports the /XO parameter to only copy new files. That allows us to schedule the script to run hourly without incurring additional data transfer costs for files that already exist in the storage account.
I wrote a PowerShell script that does the following:
- Checks that AzCopy is installed
- Determines if the C:\Windows\System32\CertSrv\CertEnroll folder exists
- Copies only changed files with extension .CRL to to the blob storage account
- Logs successful and failed transfers to %ProgramData%\ScriptLogs\Invoke-UpdateAzureBlobPKIStorage.log
You can find the script on my Github repo here: https://github.com/dstreefkerk/PowerShell/blob/master/PKI/Invoke-UpdateAzureBlobPKIStorage.ps1

Use pkiview.msc on a domain-joined machine to check the status of your CDP and AIA

Generating a SAS with least-privilege for AzCopy to use. Note that you’ll need to set Allowed Protocols to HTTPS and HTTP, not HTTPS only

The script’s archive log, showing the successful transfer of the CRL and Delta CRL
As always, use this at your own risk and your mileage may vary. Please drop me a comment below if you have any questions, feedback, or run into issues with the script.
Can you elaborate on the adjustments you need to make on the Extensions tab of the Certification Authority Properties? I’ve got the Blob setup and the script working to copy files to the Blob. However, when a client does revocation checks, it successfully contacts my Blob for the Base CRL check, but then fails the CRL Delta check by attempting to contact my internal server.
LikeLike
Hi Tony. It has been a while and unfortunately I no longer have access to that setup due to a change of jobs. I made sure that I removed all references to the internal AIA and CDP, and replaced them with the blob storage URL.
LikeLike
Hello Daniel,
Thanks for the amazing script! Always stay hungry and stay foolish 😉
I think I keep running in a minor challenge with the creation of Log files using the default values.
Im always getting:
“Remove-Item : Cannot find path ‘C:\windows\Temp\AzCopy-PKI.log’ because it does not exist.
At line:84 char:5”
The error wont come when I create a File called “AzCopy-PKI.log” but on the second run the error comes again.
I don’t understand how I actually get the script to create the log file in the first place as it wont do so.
Do you might can point me to the right direction?
PS. I used your idea with the Blob Storage and used an Azure CDN to publish my CRL and following Timothy Grubers approach (just put the issuing CA into Azure and connected a Azure CDN to publish my CRL)
LikeLike
Hi, I’ll have to take a look at the script later in the week when I get some time. Quite likely a stupid bug introduced by me. Azure CDN is a good idea, I like it.
LikeLike
I think I found the underlying issue – I tried it with AZCop v10 – which seems to be fundamental new designed. E.g. AZCopy with /XO switch doesnt work anymore (and as far I know no good alternative has been added yet) I will go back to Azcopy v8 and check again with your script 🙂
Thanks again!
LikeLike
Came across an issue when migrating my existing PKI to Blob Storage and thought it might help some of the people who are killing their Server 2008 R2 boxes at the moment.
My CRL had the url CertEnroll which of course doesn’t work due to lowercase requirements on Blob Container names.
Using the Azure CDN I was able to re-write /CertEnroll to /certenroll for pennies a month which allowed me to mothball my old Windows webserver (when combined with your script).
Thanks!
LikeLike
Hi Daniel, thanks for this informative post.
We’re in a situation which requires a similar solution. Do you have any thoughts on offloading an OCSP responder to Azure? We’re considering dropping OCSP entirely in favour of purely CRL checking because we cannot think of a way to relocate this from the Enterprise CA.
Thanks
LikeLike
Hi Mark, sorry, no. I haven’t had to do that myself before, and I no longer look after the PKI infrastructure in question.
LikeLike