Windows CA Backups in Powershell
The CA is an incredibly important piece of infrastructure, especially once you start issuing your own certificates. We are pushing our code signing certs, smart card certs and certs for VPN Authentication. A loss of our CA would be a very bad day.
Backup and restoration seem simple when first checking out the Backup-CARoleService
docs but there is no Microsoft documentation saying "This is everything" and that led me down a hole to find that it is indeed not everything.
C:\Windows\CAPolicy.inf controls root cert expiration length and several other critical factors, I also threw in a registry backup which I read about in this source but it does contain hostnames and such so I would not restore it on bare principle and check to see if not having the restored registry is an issue.
A critical thing I learned during testing the recovery is that Certificate Templates are not stored in the CA. They are stored in AD and then replicated to all DCs and CAs. The only thing stored of the CA itself is the list of "Templates to issue" which is not very critical and is basically just a text list.
I hit some of the issues that are listed in these microsoft docs so I recommend reading and being familiar with them.
I will need to run a restoration onto new bare metal to test out this process (as you should be doing anyway). I tested this in my Homelab to some degree but need to do it in a full DR.
Code
Backup
$log = 'C:\ca_backup.log'
Start-Transcript $log
#Requires -RunAsAdministrator
# --- Initializations --- #
If (Test-Path '.\send-mail\send-mail.ps1') {
. .\send-mail\send-mail.ps1
} Else {
Throw "send-mail is missing"
}
# --- Declarations --- #
#user vars, change these
$backupLocation = "\\dsk7\backups-smb\ca\"
$caBak = "C:\caBackup"
$archive = "C:\$(Get-Date -UFormat %Y-%m-%d)-$env:COMPUTERNAME.ca.zip"
$password = ConvertTo-SecureString "PASSWORD" -AsPlainText -Force
# system vars
$caPolicy = 'C:\Windows\CAPolicy.inf'
$reg = 'HKLM\SYSTEM\CurrentControlSet\Services\CertSvc\Configuration'
# use an array to catch bad things and put it in our email
$failureArray = @()
# --- Functions --- #
Function mail {
# stop must be here so that the file can be unlocked whenever we want to mail
Stop-Transcript
If ($failureArray.Count -gt 0) {
$result = 'failure'
} Else {
$result = 'success'
}
send-mail -to 'user@contoso.com' -subject "CA Backup on $env:COMPUTERNAME $result" -body "Failures: $failureArray" -attachment $log
}
# --- Execution --- #
Write-Host "Main backup of CARoleService..." -ForegroundColor Green
Try {
Backup-CARoleService -Path $caBak -Password $password
} Catch {
$failureArray += 'CA backup failure'
Write-Host $_
mail
Throw 'CA backup failure'
}
Write-Host "Copying CAPolicy.inf..." -ForegroundColor Green
Try {
Copy-Item -Path $caPolicy -Destination $caBak
} Catch {
Write-Warning 'CA policy failure'
$failureArray += 'CA policy failure'
Write-Host $_
}
Write-Host "Exporting issued templates..." -ForegroundColor Green
Try {
Get-CATemplate | Export-Csv -Path "$caBak\TemplatesToIssue.csv"
} Catch {
Write-Warning 'template failure'
$failureArray += 'template failure'
Write-Host $_
}
Try {
reg export $reg "$caBak\ca-configuration.reg"
} Catch {
Write-Warning 'registry export failure'
$failureArray += 'registry export failure'
Write-Host $_
}
# zip-em and move-em
$zipFiles = $caBak
Write-Host "Compressing archive $archive..." -ForegroundColor Green
Compress-Archive -Path $zipFiles -DestinationPath $archive
Write-Host "Moving $archive to $backupLocation..." -ForegroundColor Green
Try {
Move-Item -Path $archive -Destination $backupLocation
} Catch {
Write-Warning "move failure"
$failureArray += 'move failure'
Write-Host $_
}
Try {
Remove-Item -Path $caBak -Recurse
} Catch {
Write-Warning "failed to remove backup dir"
$failureArray += 'backup dir failed to remove'
Write-Host $_
}
# --- Ending Tasks --- #
mail
This script utilizes my email script submodule.
Recovery
Start-Transcript 'C:\ca_recovery.log'
#Requires -RunAsAdministrator
Function ca_recovery {
param (
[Parameter(Mandatory=$True)]
[Object]$archive
)
# use vars
$caBak = "C:\caBackup"
$password = ConvertTo-SecureString "PASSWORD" -AsPlainText -Force
# system vars
$caPolicy = 'C:\Windows\CAPolicy.inf'
$reg = 'HKLM\SYSTEM\CurrentControlSet\Services\CertSvc\Configuration'
If (!(Test-Path $archive)) {
Throw "$archive does not exist, check the path and try again"
}
If (Test-Path $caBak) {
Throw "$caBak exists, verify your goal and remove it"
}
Write-Host "Expanding $archive into C:\ it will be $cabBak" -ForegroundColor Green
Expand-Archive -Path $archive -DestinationPath 'C:\'
# start restoring things
# we need the service stopped before we can do anything
# not stopping is terminal
Write-HOst "Restoring DB and Keys..." -ForegroundColor Green
Try {
Stop-Service CertSvc
} Catch {
Throw "Failed to stop CertSvc service"
Write-Host $_
}
# To prevent issues this command does not have -Force by default
# to overwrite an existing CA you must add the -Force flag or
# swap the two commands below and use the commented one
Try {
Restore-CARoleService -Path $caBak -Password $password
# Restore-CARoleService -Path $caBak -Password $password -Force
} Catch {
Throw "Restoration failed, this could be because data exists. If you are overwriting then edit this script to include the -Force flag on Restore-CARoleService"
Write-Host $_
}
# import the capolicy.inf if it does not exist, we do not force overwrites
If (Test-Path $caPolicy) {
Write-Warning "$caPolicy exists, we will not overwrite it. If you wish to you can manually copy $caBackup\CAPolicy.inf"
} Else {
Copy-Item -Path "$caBak\CaPolicy.inf" -Destination $caPolicy
}
# import our issue templates
# nothing bad can happen, so we force it
Write-Host "To import the previous Templates to issue run the following after starting the CertSvc service" -ForegroundColor Green
Write-Host '$caBak ='$caBak
Write-Host '$templates = Import-Csv "$caBak\TemplatesToIssue.csv"'
Write-Host '$templates | Add-CATemplate -Force'
# handle registry notice, we won't do this automatically because bad stuff can happen
# this contains things like hostnames and could be dangerous to over-write
Write-Warning "Please manually import $caBak\ca-configuration.reg if you need to. BACKUP THE EXISITNG REGISTRY FIRST."
# No cleanup to do, we just have notices.
Write-Host "We are leaving $caBak in place. This dir contains your Templates, registry and CAPolicy.inf." -ForegroundColor Green
Write-Host "This script has finished." -ForgroundColor Green
}
ca_recovery
Stop-Transcript
References
Official docs
- https://docs.microsoft.com/en-us/powershell/module/adcsadministration/?view=windowsserver2022-ps
- https://docs.microsoft.com/en-us/windows-server/identity/ad-ds/manage/component-updates/ca-backup-and-restore-windows-powershell-cmdlets
Backup ideas and tips
- https://blog.ahasayen.com/certification-authority-backup/
- https://dimitri.janczak.net/2016/10/08/backup-of-a-windows-ca-configuration/
- https://redmondmag.com/Articles/2016/03/01/Securing-Windows-Enterprise-CAs.aspx
Cert Templates are stored in AD