Concepts & Overview
Before generating certificates, understand the trust hierarchy and what a CA root certificate actually does.
Prerequisites
Ensure your environment is ready before generating any key material.
$ sudo apt-get update && sudo apt-get install -y openssl
# RHEL/CentOS/Fedora
$ sudo dnf install openssl
# Verify version
$ openssl version
OpenSSL 3.0.2 15 Mar 2022
% brew install openssl@3
# Add to PATH
% echo 'export PATH="/opt/homebrew/opt/openssl@3/bin:$PATH"' >> ~/.zshrc
% source ~/.zshrc
% openssl version
OpenSSL 3.1.4
PS> winget install ShiningLight.OpenSSL
# Option 2: via Chocolatey
PS> choco install openssl
# Restart terminal then verify
PS> openssl version
Create a dedicated directory with appropriate permissions. The root CA's private key should only be readable by its owner.
$ chmod 700 ~/myCA/private
$ touch ~/myCA/index.txt
$ echo 1000 > ~/myCA/serial
$ cd ~/myCA
% chmod 700 ~/myCA/private
% touch ~/myCA/index.txt
% echo 1000 > ~/myCA/serial
PS> New-Item -ItemType File -Path C:\myCA\index.txt
PS> Set-Content -Path C:\myCA\serial -Value "1000"
PS> cd C:\myCA
certs/ โ issued certificates ยท private/ โ private keys (700 perms) ยท crl/ โ revocation lists ยท index.txt โ certificate database ยท serial โ next serial number
Generate the Root Private Key
The private key is the most sensitive file in your CA. We generate a 4096-bit RSA key protected with AES-256 encryption.
We use genrsa with the -aes256 flag to encrypt the key at rest. You'll be prompted for a strong passphrase โ this is required every time you use the key to sign certificates.
$ openssl genrsa -aes256 -out private/ca.key.pem 4096
Enter PEM pass phrase: โโโโโโโโโโโโโโโโ
Verifying - Enter PEM pass phrase: โโโโโโโโโโโโโโโโ
# Lock down key permissions (Linux/macOS only)
$ chmod 400 private/ca.key.pem
# Verify key was created
$ ls -la private/
-r-------- 1 user user 3414 Jan 15 10:30 ca.key.pem
openssl ecparam -genkey -name secp384r1 | openssl ec -aes256 -out private/ca.key.pem
Inspect the key structure to confirm it was generated correctly and the encryption is in place.
Enter pass phrase for private/ca.key.pem:
RSA key ok
Create the Root CA Certificate
Generate a self-signed X.509 certificate. This is the public-facing artifact that will be distributed and trusted.
An openssl.cnf config file lets us bake in proper CA extensions โ specifically basicConstraints: CA:true and key usage flags that mark this as a signing authority.
default_bits = 4096
default_md = sha256
prompt = no
distinguished_name = req_distinguished_name
x509_extensions = v3_ca
[ req_distinguished_name ]
C = US
ST = California
L = San Francisco
O = My Organization
OU = IT Security
CN = My Organization Root CA
emailAddress = ca@myorg.example
[ v3_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
openssl.cnf
Use req -x509 to generate a self-signed certificate directly from the private key and config. The -days 7300 flag sets a 20-year validity period, appropriate for root CAs.
-config openssl.cnf \
-key private/ca.key.pem \
-out certs/ca.cert.pem \
-days 7300
Enter pass phrase for private/ca.key.pem:
Certificate created successfully.
# Set read-only permissions
$ chmod 444 certs/ca.cert.pem
certs/ca.cert.pem
Certificate:
Data:
Version: 3 (0x2)
Subject: C=US, ST=California, O=My Organization, CN=My Organization Root CA
Issuer: C=US, ST=California, O=My Organization, CN=My Organization Root CA
X509v3 Basic Constraints: critical
CA:TRUE
X509v3 Key Usage: critical
Digital Signature, Certificate Sign, CRL Sign
Install into Trusted Root Store
Add your root certificate to the system trust store so applications on this machine will trust certificates signed by your CA.
$ sudo cp certs/ca.cert.pem /usr/local/share/ca-certificates/my-org-ca.crt
# Update the CA trust bundle
$ sudo update-ca-certificates
Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
done.
$ sudo cp certs/ca.cert.pem /etc/pki/ca-trust/source/anchors/my-org-ca.pem
# Rebuild trust bundle
$ sudo update-ca-trust extract
Trust store updated.
% sudo security add-trusted-cert \
-d \
-r trustRoot \
-k /Library/Keychains/System.keychain \
certs/ca.cert.pem
# Verify it's in the store
% security find-certificate -c "My Organization Root CA" /Library/Keychains/System.keychain
.pem file into System keychain โ Double-click the cert โ Expand Trust โ Set "When using this certificate" to Always Trust.
PS> Import-Certificate \
-FilePath "C:\myCA\certs\ca.cert.pem" \
-CertStoreLocation Cert:\LocalMachine\Root
PSParentPath: Microsoft.PowerShell.Security\Certificate::LocalMachine\Root
Thumbprint Subject
A1B2C3D4... CN=My Organization Root CA
# Verify it's in the store
PS> Get-ChildItem Cert:\LocalMachine\Root | Where-Object { $_.Subject -like "*My Organization*" }
mmc.exe โ File โ Add Snap-in โ Certificates โ Computer Account โ Local Computer โ Expand Trusted Root Certification Authorities โ Right-click Certificates โ All Tasks โ Import.
Verification & Testing
Confirm the CA is correctly installed and trusted by the system before issuing any certificates from it.
$ openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt certs/ca.cert.pem
certs/ca.cert.pem: OK
# Or use update-ca-certificates bundle path directly
$ openssl x509 -in /etc/ssl/certs/my-org-ca.pem -noout -subject
subject=C=US, O=My Organization, CN=My Organization Root CA
% security find-certificate -c "My Organization Root CA" -p /Library/Keychains/System.keychain | openssl x509 -noout -subject
subject=CN=My Organization Root CA, O=My Organization
# Test cert verification
% openssl verify certs/ca.cert.pem
certs/ca.cert.pem: OK
PS> Get-ChildItem Cert:\LocalMachine\Root |
Where-Object { $_.Subject -like "*My Organization*" } |
Select-Object Subject, Thumbprint, NotAfter
Subject Thumbprint NotAfter
CN=My Organization.. A1B2C3D4... 01/15/2045
The true test: issue a leaf certificate from your root CA and verify the chain validates cleanly.
$ openssl genrsa -out requests/test.key.pem 2048
# 2. Create a CSR for test.example.com
$ openssl req -new -sha256 \
-key requests/test.key.pem \
-subj "/CN=test.example.com/O=My Organization/C=US" \
-out requests/test.csr.pem
# 3. Sign the CSR with your root CA
$ openssl x509 -req -sha256 -days 365 \
-in requests/test.csr.pem \
-CA certs/ca.cert.pem \
-CAkey private/ca.key.pem \
-CAcreateserial \
-out certs/test.cert.pem
# 4. Verify the chain
$ openssl verify -CAfile certs/ca.cert.pem certs/test.cert.pem
certs/test.cert.pem: OK
Knowledge Check
Test your understanding before completing the module.
basicConstraints: CA:true do in a root certificate?/usr/local/share/ca-certificates/?chmod 400 on Linux?