Microsoft, Windows

Backup Microsoft Certificate Authority

To recover Microsoft Certificate Authority, backup:

  1. Private key and CA certificate
  2. Certificate database
  3. Certificate database log
  4. CA configuration registry settings

Input

function Backup-CertificationAuthority {
[CmdletBinding()]
	param(
		[Parameter(Mandatory = $true)]
		[IO.DirectoryInfo]$Path,
		[ValidateSet("Full","Incremental")]
		[string]$Type = "Full",
		[string]$Password,
		[switch]$BackupKey,
		[switch]$KeepLog,
		[switch]$Extended,
		[switch]$Force
	)

	if ($PSBoundParameters.Verbose) {$VerbosePreference = "continue"}
	if ($PSBoundParameters.Debug) {
		$Host.PrivateData.DebugForegroundColor = "Cyan"
		$DebugPreference = "continue"
	}
#region Defining low-level APIs

$cadmsignature = @"
[DllImport("Certadm.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern bool CertSrvIsServerOnline(
	string pwszServerName,
	ref bool pfServerOnline
);
[DllImport("Certadm.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern int CertSrvBackupPrepare(
	string pwszServerName,
	uint grbitJet,
	uint dwBackupFlags,
	ref IntPtr phbc
);
[DllImport("Certadm.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern int CertSrvBackupGetDatabaseNames(
	IntPtr hbc,
	ref IntPtr ppwszzAttachmentInformation,
	ref uint pcbSize
);
[DllImport("Certadm.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern int CertSrvBackupGetBackupLogs(
	IntPtr hbc,
	ref IntPtr ppwszzBackupLogFiles,
	ref uint pcbSize
);
[DllImport("Certadm.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern int CertSrvBackupGetDynamicFileList(
	IntPtr hbc,
	ref IntPtr ppwszzFileList,
	ref uint pcbSize
);
[DllImport("Certadm.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern int CertSrvBackupOpenFile(
	IntPtr hbc,
	string pwszAttachmentName,
	int cbReadHintSize,
	ref Int64 pliFileSize
);
[DllImport("Certadm.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern int CertSrvBackupRead(
	IntPtr hbc,
	IntPtr pvBuffer,
	int cbBuffer,
	ref int pcbRead
);
[DllImport("Certadm.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern int CertSrvBackupClose(
	IntPtr hbc
);
[DllImport("Certadm.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern int CertSrvBackupTruncateLogs(
	IntPtr hbc
);
[DllImport("Certadm.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern int CertSrvBackupEnd(
	IntPtr phbc
);
[DllImport("Certadm.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern int CertSrvBackupFree(
	IntPtr pv
);
[DllImport("Certadm.dll", CharSet=CharSet.Auto, SetLastError=true)]
public static extern int CertSrvRestoreGetDatabaseLocations(
	IntPtr hbc,
	ref IntPtr ppwszzDatabaseLocationList,
	ref uint pcbSize
);
"@
#endregion

#region add defined types
	try {Add-Type -MemberDefinition $cadmsignature -Namespace PKI -Name CertAdm}
	catch {break}
#endregion

#region Path checking
	if (Test-Path $Path) {
		if (Test-Path $Path\DataBase) {
			if ($Force) {
				try {
					Remove-Item $Path\DataBase -Recurse -Force -ErrorAction Stop
					$BackupDir = New-Item -Name DataBase -ItemType directory -Path $Path -Force -ErrorAction Stop
				} catch {
					Write-Error -Category InvalidOperation -ErrorId "InvalidOperationDeleteException" `
					-ErrorAction Stop -Message $Error[0].Exception
				}
			} else {
				Write-Error -Category ResourceExists -ErrorId "ResourceExistsException" `
				-ErrorAction Stop -Message "The path '$Path\DataBase' already exist."
			}
		} else {
			$BackupDir = New-Item -Name DataBase -ItemType directory -Path $Path -Force -ErrorAction Stop
		}
	} else {
		try {$BackupDir = New-Item -Name DataBase -ItemType directory -Path $Path -Force -ErrorAction Stop}
		catch {
			Write-Error -Category ObjectNotFound -ErrorId "PathNotFoundException" `
			-ErrorAction Stop -Message "Cannot create object in '$Path'"
		}
	}
#endregion

#region helper functions
	function Split-BackupPath ([Byte[]]$Bytes) {
		$SB = New-Object System.Text.StringBuilder
		$bytes1 = $bytes | ForEach-Object {"{0:X2}" -f $_}
		for ($n = 0; $n -lt $bytes1.count; $n = $n + 2) {
			[void]$SB.Append([char](Invoke-Expression 0x$(($bytes1[$n+1]) + ($bytes1[$n]))))
		}
		$SB.ToString().Split("`0",[StringSplitOptions]::RemoveEmptyEntries)
	}
	function __BackupKey ($Password) {
		$CertConfig = New-Object -ComObject CertificateAuthority.Config
		try {$local = $CertConfig.GetConfig(3)}
		catch { }
		if ($local -ne $null) {
			$name = (Get-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Services\CertSvc\Configuration' -Name Active).Active
			$StoreCerts = New-Object Security.Cryptography.X509Certificates.X509Certificate2Collection
			$Certs = New-Object Security.Cryptography.X509Certificates.X509Certificate2Collection
			$TempCerts = New-Object Security.Cryptography.X509Certificates.X509Certificate2Collection
			$Store = New-Object Security.Cryptography.X509Certificates.X509Store "My", "LocalMachine"
			$Store.Open("ReadOnly")
			$StoreCerts = $Store.Certificates
			$Store.Close()
			$Certs = $StoreCerts.Find("FindBySubjectName",$name,$true)
			$chain = New-Object Security.Cryptography.X509Certificates.X509Chain
			$chain.ChainPolicy.RevocationMode = "NoCheck"
			$Certs | ForEach-Object {
				[void]$chain.Build($_)
				if ($chain.ChainElements.Count -ge 1) {
					for ($n = 1; $n -lt $chain.ChainElements.Count; $n++) {
						[void]$TempCerts.Add($chain.ChainElements[$n].Certificate)
					}
				}
				$chain.Reset()
			}
			if ($TempCerts.Count -gt 0) {
				$Certs.AddRange([Security.Cryptography.X509Certificates.X509Certificate2[]]($TempCerts | Select-Object -Unique))
			}
			try {[IO.File]::WriteAllBytes("$Path\$Name.p12",$Certs.Export("pfx",$Password))}
			finally {$StoreCerts, $Certs, $TempCerts | ForEach-Object {$_.Clear()}}
		}
	}
	# helper function for backup routine
	function __BackupRoutine ($phbc,$File,$BackupDir,$pvBuffer, $cbBuffer, $FileType) {
		$n = 1
		Write-Debug "Read buffer address: $pvBuffer"
		$FileName = Get-Item $File -ErrorAction SilentlyContinue
		$pliFileSize = 0
		Write-Debug "Open current item: $file"
		# open DB file. I set 0 for cbReadHintSize to allow system to automatically select proper buffer size
		$hresult = [PKI.CertAdm]::CertSrvBackupOpenFile($phbc,$File,$cbBuffer,[ref]$pliFileSize)
		if ($hresult -ne 0) {
			$StatusObject.Status = 0x8007004
			__status $StatusObject
			break
		}
		Write-Debug "Current item size in bytes: $pliFileSize"
		$BackupFile = New-Item -Name $FileName.Name -ItemType file -Path $BackupDir -Force -ErrorAction Stop
		$FS = New-Object IO.FileStream $BackupFile,"append","write"
		[int]$pcbRead = 0
		$complete = 0
		$Name = (Get-Item $File -Force -ErrorAction SilentlyContinue).Name
		while (!$last) {
			$n++
			[int]$percent = $complete / $pliFileSize * 100
			Write-Progress -Activity "Backing up database file '$name' " -CurrentOperation InnerLoop -PercentComplete $percent `
			-Status "$percent% complete"
			$hresult = [PKI.CertAdm]::CertSrvBackupRead($phbc,$pvBuffer,$cbBuffer,[ref]$pcbRead)
			if ($hresult -ne 0) {
				$StatusObject.Status = 0x800701e
				__status $StatusObject
				break
			}
			if ($FileType -eq "database") {$script:Size += $pcbRead}
			Write-Debug "Reading $n portion of $pcbRead bytes"
			$uBuffer = New-Object byte[] -ArgumentList $pcbRead
			[Runtime.InteropServices.Marshal]::Copy($pvBuffer,$uBuffer,0,$pcbRead)
			$FS.Write($uBuffer,0,$uBuffer.Length)
			$complete += $pcbRead
			if ($pcbRead -lt $cbBuffer) {$last = $true}
		}
		Write-Debug "Closing current item: $file"
		$FS.Close()
		$hresult = [PKI.CertAdm]::CertSrvBackupClose($phbc)
		Write-Debug "Current item '$BackupFile' is closed: $(!$hresult)"
		# relelase managed and unmanaged buffers
		Remove-Variable uBuffer
	}
	function __status ($StatusObject) {
		try {$StatusObject.StatusMessage = [PKI.Utils.Error]::GetMessage($StatusObject.Status)}
		catch { }
		Write-Verbose "Clearing resources"
		$hresult = [PKI.CertAdm]::CertSrvBackupEnd($phbc)
		Write-Debug "Backup sent to end state: $(!$hresult)"
		$StatusObject.BackupEnd = [datetime]::Now
		$StatusObject
	}
#endregion

	$StatusObject = New-Object psobject -Property @{
		BackupType = $Type;
		Status = 0;
		StatusMessage = [string]::Empty;
		DataBaseSize = 0;
		LogFileCount = 0;
		BackupStart = [datetime]::Now;
		BackupEnd = [datetime]::Now
	}
	if ($BackupKey) {
		if ($Password -eq $null -or $Password -eq [string]::Empty) {
			$Password = Read-Host "Enter password"
		}
		__BackupKey $Password
	}
	$ofs = ", "
	Write-Verbose "Set server name to $($Env:computername)"
	$Server = $Env:COMPUTERNAME
	$ServerStatus = $false

	Write-Verbose "Test connection to local CA"
	$hresult = [PKI.CertAdm]::CertSrvIsServerOnline($Server,[ref]$ServerStatus)
	if (!$ServerStatus) {
		$StatusObject.Status = 0x800706ba
		__status $StatusObject
		break
	}

	Write-Debug "Instantiate backup context handle"
	[IntPtr]$phbc = [IntPtr]::Zero

	Write-Debug "Retrieve backup context handle for the backup type: $type"
	$hresult = switch ($Type) {
		"Full" {[PKI.CertAdm]::CertSrvBackupPrepare($Server,0,1,[ref]$phbc)}
		"Incremental" {[PKI.CertAdm]::CertSrvBackupPrepare($Server,0,2,[ref]$phbc)}
	}
	if ($hresult -ne 0) {
		$StatusObject.Status = $hresult
		__status $StatusObject
		break
	}
	Write-Debug "Backup context handle is: $phbc"
	
	$cbBuffer = 524288
	$pvBuffer = [Runtime.InteropServices.Marshal]::AllocHGlobal($cbBuffer)
	
	if ($Type -eq "Full") {
		Write-Debug "Retrieve restore map"
		$ppwszzDatabaseLocationList = [IntPtr]::Zero
		$pcbSize = 0
		$hresult = [PKI.CertAdm]::CertSrvRestoreGetDatabaseLocations($phbc,[ref]$ppwszzDatabaseLocationList,[ref]$pcbSize)
		Write-Debug "Restore map handle: $ppwszzDatabaseLocationList"
		Write-Debug "Restore map size in bytes: $pcbSize"
		$Bytes = New-Object byte[] -ArgumentList $pcbSize
		[Runtime.InteropServices.Marshal]::Copy($ppwszzDatabaseLocationList,$Bytes,0,$pcbSize)
		Write-Verbose "Writing restore map to: $BackupDir\certbkxp.dat"
		[IO.File]::WriteAllBytes("$BackupDir\certbkxp.dat",$Bytes)
		Remove-Variable Bytes -Force

		Write-Verbose "Retrieve DB file locations"
		$ppwszzAttachmentInformation = [IntPtr]::Zero
		$pcbSize = 0
		$hresult = [PKI.CertAdm]::CertSrvBackupGetDatabaseNames($phbc,[ref]$ppwszzAttachmentInformation,[ref]$pcbSize)
		Write-Debug "DB file location handle: $ppwszzAttachmentInformation"
		Write-Debug "DB file location size in bytes: $pcbSize"
		if ($hresult -ne 0) {
			$StatusObject.Status = $hresult
			__status $StatusObject
			break
		}
		if ($pcbSize -eq 0) {
			$StatusObject.Status = 0x80070012
			__status $StatusObject
			break
		}
		$Bytes = New-Object byte[] -ArgumentList $pcbSize
		[Runtime.InteropServices.Marshal]::Copy($ppwszzAttachmentInformation,$Bytes,0,$pcbSize)
		$DBPaths = Split-BackupPath $Bytes
		Write-Verbose "Unstripped DB paths:"
		$DBPaths | ForEach-Object {Write-Verbose $_}
		Remove-Variable Bytes
		# backup DB files
		# initialize read buffer
		Write-Debug "Set read buffer to: $cbBuffer bytes"
		$script:Size = 0
		foreach ($File in $DBPaths) {
			$File = $File.Substring(1,($File.Length - 1))
			Write-Verbose "Backing up file: $File"
			__BackupRoutine $phbc $File $BackupDir $pvBuffer $cbBuffer "database"
		}
		$StatusObject.DataBaseSize = $script:Size
		Remove-Variable DBPaths
	} else {
		Write-Verbose "Skipping CA database backup."
		Write-Debug "Skipping CA database backup. Logs only"
	}
	# retrieve log files
	$ppwszzBackupLogFiles = [IntPtr]::Zero
	$pcbSize = 0
	Write-Verbose "Retrieving DB log file list"
	$hresult = [PKI.CertAdm]::CertSrvBackupGetBackupLogs($phbc,[ref]$ppwszzBackupLogFiles,[ref]$pcbSize)
	Write-Debug "Log file location handle: $ppwszzAttachmentInformation"
	Write-Debug "Log file location size in bytes: $pcbSize"
	if ($hresult -ne 0) {
		$StatusObject.Status = 0x80070012
		__status $StatusObject
		break
	}
	$Bytes = New-Object byte[] -ArgumentList $pcbSize
	[Runtime.InteropServices.Marshal]::Copy($ppwszzBackupLogFiles,$Bytes,0,$pcbSize)
	$LogPaths = Split-BackupPath $Bytes
	$StatusObject.LogFileCount = $LogPaths.Length
	Write-Verbose "Unstripped LOG paths:"
	$LogPaths | ForEach-Object {Write-Verbose $_}
	Remove-Variable Bytes
	foreach ($File in $LogPaths) {
		$File = $File.Substring(1,($File.Length - 1))
		Write-Verbose "Backing up file: $File"
		__BackupRoutine $phbc $File $BackupDir $pvBuffer $cbBuffer "log"
	}
	[Runtime.InteropServices.Marshal]::FreeHGlobal($pvBuffer)
	Remove-Variable LogPaths
	Write-Debug "Releasing read buffer"
	# truncate logs
	if ($Type -eq "Full" -and !$KeepLog) {
		Write-Verbose "Truncating logs"
		Write-Debug "Truncating logs"
		$hresult = [PKI.CertAdm]::CertSrvBackupTruncateLogs($phbc)
		if ($hresult -ne 0) {
			$StatusObject.Status = 0x80070012
			__status $StatusObject
			break
		}
	}
	# retrieve and backup dynamic files
	if ($Extended) {
		$Now = Get-Date -Format dd.MM.yyyy
		Write-Verbose "Export CA configuration registry hive and CAPolicy.inf (if possible)."
		Write-Debug "Export CA configuration registry hive and CAPolicy.inf (if possible)."
		reg export "HKLM\SYSTEM\CurrentControlSet\Services\CertSvc\Configuration" "$Path\CAConfig-$($Now.ToString()).reg" /y | Out-Null
		Copy-Item $Env:windir\CAPolicy.inf -Destination $Path -Force -ErrorAction SilentlyContinue
	}
	__status $StatusObject
}

$Password = Read-Host -AsSecureString
Backup-CertificationAuthority -Type Full -Password $Password -BackupKey -KeepLog -Extended -Force 

Output

PKI recovery 44

Source: https://www.sysadmins.lv/blog-en/install-certification-authority-with-powershell.aspx

Three different ways for certification authority backup

Leave a Reply

Your email address will not be published. Required fields are marked *

Close Bitnami banner
Bitnami