Lab - Certificate Authority Setup

Lab - Certificate Authority Setup

I know there are hundreds of posts out there on how to do this, but I really documented this for my future self as something that is really fast, easy, and repeatable when I need to stand up a lab for testing with Azure AD and Intune :)

I am not a CA expert, but I did do some research to make sure I'm not doing something dumb like using DES. TL;DR - don't use this for prod ;)

OpenSSL Offline Root CA

Before we start, I love using LXC containers in my lab, but obviously we wouldn't use this method in production. You could also use a VM, WSL, or maybe even OpenSSL on Windows or macOS for a lab, but production should use a hardware security module (and someone with expertise).

First, let's create the LXC container, hop into it, and stage our working directory:

lxc launch ubuntu:jammy rootca --profile default
lxc exec rootca bash

mkdir /root/ca
cd /root/ca
mkdir certs crl newcerts private
chmod 700 private
touch index.txt
echo 1000 > serial
echo 1000 > /root/ca/crlnumber
cp /usr/lib/ssl/openssl.cnf /root/ca/openssl.cnf
nano /root/ca/openssl.cnf

I think it's important to look through the various options in the default openssl configuration file as it leads to discovery, asking questions, and better learning how all of this stuff works :)

Now, you could go through and modify the settings based on what I've listed below, but you may find it easier delete openssl.cnf (rm openssl.cnf), modify the contents below (change certificate, crl, private_key, and the two URLs), and then paste it into a new blank openssl.cnf file (nano openssl.cnf, right click to paste, then control+o to save).

[ ca ]
default_ca       = CA_default

[ CA_default ]
dir              = /root/ca
certs            = $dir/certs
crl_dir          = $dir/crl
database         = $dir/index.txt
new_certs_dir    = $dir/newcerts
RANDFILE         = $dir/private/.rand
certificate      = $dir/certs/SMLRootCA2023.crt
serial           = $dir/serial
crlnumber        = $dir/crlnumber
crl              = $dir/crl/SMLRootCA2023.crl
private_key      = $dir/private/SMLRootCA2023.key
name_opt         = ca_default
cert_opt         = ca_default
crl_extensions   = crl_ext
default_days     = 3650
default_crl_days = 365
default_md       = sha256
preserve         = no
policy           = policy_match

[ policy_match ]
commonName       = supplied

[ req ]
default_bits       = 4096
distinguished_name = req_distinguished_name
x509_extensions    = v3_ca
string_mask        = utf8only

[ req_distinguished_name ]
countryName         = Country Name (2 letter code)
stateOrProvinceName = State or Province Name (full name)
0.organizationName  = Organization Name (eg, company)
commonName          = Common Name (e.g. server FQDN or YOUR name)
commonName_max      = 64

[ v3_ca ]
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always, issuer
basicConstraints       = critical, CA:true
keyUsage               = critical, digitalSignature, cRLSign, keyCertSign

[ v3_subca ]
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always, issuer
basicConstraints       = critical, CA:true, pathlen:0
keyUsage               = critical, digitalSignature, cRLSign, keyCertSign
authorityInfoAccess    = @v3_root_aia
crlDistributionPoints  = URI:

[ v3_root_aia ]

[ crl_ext ]
authorityKeyIdentifier = keyid:always
issuerAltName          = issuer:copy
Note: Be sure to change the URLs at the bottom to a FQDN where you can host the public certificate and CRL on a website (I will show how to use Azure Static Websites)

Next, we're going to create a random file, then use it in our private key generation, and then we're going to submit a request to self sign our CA certificate.

openssl rand -out /root/ca/private/.rand $RANDOM
openssl genrsa -out /root/ca/private/SMLRootCA2023.key -aes256 -rand /root/ca/private/.rand 4096
openssl req -out /root/ca/certs/SMLRootCA2023.crt -x509 -new -key /root/ca/private/SMLRootCA2023.key -sha256 -days 5475 -utf8 -extensions v3_ca -config /root/ca/openssl.cnf

Active Directory Certificate Services (Intermediate CA)

Now that we have a root CA in place, let's set up Active Directory Services as our Intermediate CA. We could use PowerShell (Add-WindowsFeature Adcs-Cert-Authority -IncludeManagementTools), but I think it's helpful to show the GUI for most folks who are just wanting to play in a lab:

Now, we could SCP the request file to our Linux box, but I like to cheat and just copy the contents of the .req file that was created in the root of the C:\ drive and paste the contents into a .req file in the /root/ca directory. Now we need to sign the Intermediate CA certificate by running the following:

openssl ca -batch -in /root/ca/SMLIntCA.req -extensions v3_subca -days 3650 -config /root/ca/openssl.cnf -notext

Next, we want to create the certificate revocation list by running the following:

openssl ca -gencrl -config /root/ca/openssl.cnf -out /root/ca/crl/SMLRootCA2023.crl

Finally, we want to create the certificate chains and get all the files copied over to our ADCS server :)

# Create PEM chain
cat /root/ca/newcerts/1000.pem /root/ca/certs/SMLRootCA2023.crt > /root/ca/certs/SMLIntCA2023.crt

# Create P7B chain
openssl crl2pkcs7 -certfile /root/ca/certs/SMLRootCA2023.crt -certfile /root/ca/newcerts/1000.pem -out /root/ca/certs/SMLIntCA2023.p7b -nocrl

# Create P7B chain with CRL
openssl crl2pkcs7 -in /root/ca/crl/SMLRootCA2023.crl -certfile /root/ca/certs/SMLRootCA2023.crt -certfile /root/ca/newcerts/1000.pem -out /root/ca/certs/SMLIntCA2023withCRL.p7b

To get the files off the server, we can use SCP, WinSCP, or my favorite, copy the file through Tailscale. I zipped the entire /root/ca folder and sent it over.


While we could technically host the public root CA certificate and CRL on our ADCS server or some internal server, I really wanted to play with Azure Static Sites generated from content in GitHub :p

First I'll create a new repository on GitHub, and because I don't want it cluttering up my repositories when others are looking for something, I'll mark it as private.

On the next page, click the uploading an existing files link.

Now add the root CA public cert, intermediate CA public cert, and the root CA CRL. You can also come back and add the intermediate CA CRL if you want later :)

Additionally, we need to add a fake index.html file so the GitHub Actions workflow will recognize it as HTML and build correctly. I don't know if any content needs to be in the file, but I did this and it worked ;)

Azure Static Website

Now we'll come over to Azure and search for Static Web Apps, select Static Web Apps and click Create.

Select a subscription and resource group for the Static Web App, make sure the plan type is set to Free, and set your region. If you haven't before, you'll have to authorize Azure to access your GitHub account, then select the repository you created. When finished, click Review + create and create the site.

Once created, click Go to resource, then go to Custom domains, click Add and add a custom domain on other DNS.

Enter the domain name you chose in your root CA CRL, click next, then add the CNAME to your DNS (internal example here, but could also add externally).

Once complete, you should be able to request the CRL from the ADCS server, and now we can

Install CA Certificate

Now that the CRL can be verified, we can install the CA certificate. Back on our ADCS server, open the Certificate Authority MMC, right click on the server, hover over All Tasks, then click Install CA Certificate...

Select the P7B with CRL that we copied from the root CA server.

This should complete without any errors. If you see CRL couldn't be validated, check DNS from the ADCS box to your Azure Static Website ;)

Finally, we can start the service, and we should now have a working CA!

Add Root CA to Trusted Root CAs

The last thing we might want to do here is create a Group Policy object to push out the Root CA public certificate into Trusted Root Certification Authorities.

Stay tuned as I'll have some posts on Microsoft Tunnel and EAP-TLS certificates, hopefully both PacketFence and Cisco ISE :)

Buy Me a Coffee at Mastodon