Hello, everyone! Today I’m feeling good as Flyers eventually managed more than 2 goals per game and Lecavalier set up a hat-trick against Isles. Big WIN! So, I’ll continue Certification Authority backup API coverage and usage in PowerShell.

In the previous post we learned what functions are used for CA backup, their sequence and ended with CA database location retrieval. Today we will continue the backup process and today’s post will cover:

  • Database backup
  • Identification of log files to be backed up
  • Log file backup
  • CA Configuration backup
  • CA key backup

    Database file backup

    Let’s move forward. We have a $DBPaths variable that holds CA database location. Note that database location is specified in an UNC form (\\serverNameFQDN\C$\Windows\CertLog, for example). We need to pass this path to three functions:

  • CertSrvBackupOpenFile – which is used to open DB file for backup;
  • CertSrvBackupRead – which is used to read backed bytes. This function will be called in a loop;
  • CertSrvBackupClose – which is used to close DB file.

    These functions (in the same sequence) will be used for log file backup. Since, these functions are used to backup multiple files, it is reasonable to put them in a function and call the function when needed. Here is a basic function. Read inline comments to understand how they are used:

    # set read buffer size to 512KB. This is the optimal buffer size for CertSrvBackupRead function.
    # you should not use buffers that are larger than 1MB.
    $cbBuffer = 524288
    # allocate the buffer in a umnanaged memory. CertSrvBackupRead function will write
    # backed bytes to this buffer.
    $pvBuffer = [Runtime.InteropServices.Marshal]::AllocHGlobal($cbBuffer)
    function __BackupRoutine ($phbc,$File,$BackupDir,$pvBuffer, $cbBuffer, $FileType) {
        # get curent file object
        $FileName = Get-Item $File -ErrorAction SilentlyContinue
        # initialize file size variable
        $pliFileSize = 0
        # open current file for backup. The function returns current file size in bytes which
        # isn't interesting to us, because it is used just for informative purposes.
        $hresult = [PKI.CertAdm]::CertSrvBackupOpenFile($phbc,$File,$cbBuffer,[ref]$pliFileSize)
        if ($hresult -ne 0) {
            End-Backup $phbc
            throw New-Object ComponentModel.Win32Exception $hresult
        }
        # create backup file in the backup destination folder
        $BackupFile = New-Item -Name $FileName.Name -ItemType file -Path $BackupDir -Force -ErrorAction Stop
        # instantiate FileStream object. Do not use built-in PowerShell cmdlets, as they
        # are too slow. The best write performance (which is not slower than certutil) I got
        # with FileStream.
        $FS = New-Object IO.FileStream $BackupFile,"append","write"
        # initialize variable that will store actual read buffer size.
        [int]$pcbRead = 0
        # enter the loop and put CertSrvBackupRead function calls there. We will read current file
        # in portions, where each portion is $pcbRead bytes. The loop ends when $last variable turns
        # to True.
        while (!$last) {
            # call CertSrvBackupRead to read a portion of baked bytes. Bytes are copied to a
            # unmanaged buffer specified in $pvBuffer.
            $hresult = [PKI.CertAdm]::CertSrvBackupRead($phbc,$pvBuffer,$cbBuffer,[ref]$pcbRead)
            if ($hresult -ne 0) {
                End-Backup $phbc
        throw New-Object ComponentModel.Win32Exception $hresult
            }
            # when backed bytes are written to unmanaged memory buffer, we need to copy them
            # to a managed buffer. Create managed buffer (byte array)
            $uBuffer = New-Object byte[] -ArgumentList $pcbRead
            # copy backed bytes between unmanged and managed memory buffers.
            [Runtime.InteropServices.Marshal]::Copy($pvBuffer,$uBuffer,0,$pcbRead)
            # write backed bytes to a file
            $FS.Write($uBuffer,0,$uBuffer.Length)
            # if $pcbRead is less than initial read buffer, then we are reached the end of
            # file and there is nothing to read anymore.
            if ($pcbRead -lt $cbBuffer) {$last = $true}
        }
        # close FileStream object and current file.
        $FS.Close()
        $hresult = [PKI.CertAdm]::CertSrvBackupClose($phbc)
        # free managed memory buffer.
        Remove-Variable uBuffer
    }

    I commented almost each line, so you understand what happens there. We open current file (database of log) and read it’s bytes in portions of 512KB and write them into a file. Since we read bytes in portions, CertSrvBackupRead function is placed in a loop. When the filed is backed up, close it and continue with subsequent steps.

    We just wrote a routine function that backs up the database file (EDB or log file). Since there could be a lot of files (up to 60) we placed this routine into a function.

    Since, $DBPaths variable contains all database file names, it is safely to call our function to backup them:

    # here we split
    $DBPaths = Split-BackupPath $Bytes
    # backup each DB files
    foreach ($File in $DBPaths) {
        $File = $File.Substring(1,($File.Length - 1))
        Write-Verbose "Backing up file: $File"
        __BackupRoutine $phbc $File $BackupDir $pvBuffer $cbBuffer "database"
    }
    Remove-Variable DBPaths

    When all database files are backed up, we need to backup log files.

    Log file backup

    Log files are necessary to initialize CA database and intermediate data (between CA next start) isn’t written to a database, instead, they are written to log files, therefore we must include them in the backup set. Not all log files are required, CA backup API automatically identifies which log files are necessary. To get the required log file list, we call CertSrvBackupGetBackupLogs function:

    # initialize variable to store unmanaged handle
    $ppwszzBackupLogFiles = [IntPtr]::Zero
    # initialize variable for unmanaged buffer.
    $pcbSize = 0
    # retrieve log file list. The list is copied in a big endian Unicode byte array
    $hresult = [PKI.CertAdm]::CertSrvBackupGetBackupLogs($phbc,[ref]$ppwszzBackupLogFiles,[ref]$pcbSize)
    if ($hresult -ne 0) {
        End-Backup $phbc
        throw New-Object ComponentModel.Win32Exception $hresult
    }
    # create managed buffer to store log file name bytes
    $Bytes = New-Object byte[] -ArgumentList $pcbSize
    # copy bytes between unmanaged and managed memory buffers
    [Runtime.InteropServices.Marshal]::Copy($ppwszzBackupLogFiles,$Bytes,0,$pcbSize)
    # convert byte array to an array of strings, where each string represents a path to a
    # log file.
    $LogPaths = Split-BackupPath $Bytes
    Remove-Variable Bytes
    # pass each log file name to our backup routine function
    foreach ($File in $LogPaths) {
        $File = $File.Substring(1,($File.Length - 1))
        Write-Verbose "Backing up file: $File"
        __BackupRoutine $phbc $File $BackupDir $pvBuffer $cbBuffer "log"
    }

    At this point we are almost done. Almost done, because we may choose to truncate log files after successive backup operation:

    # call CertSrvBackupTruncateLogs to truncate logs
    $hresult = [PKI.CertAdm]::CertSrvBackupTruncateLogs($phbc)
    if ($hresult -ne 0) {
        End-Backup $phbc
        throw New-Object ComponentModel.Win32Exception $hresult
    }

    Note: you can truncate logs only when Full backup (rather than incremental) was performed.

    End backup

    In order to end backup operation, call End-Backup function to release all unmanaged resources that are associated with current backup operation.

    Configuration export

    Many administrators perform CA database and CA key backup, however they do not backup CA configuration which is very important like any other backup part. Therefore we need to add some lines to export CA configuration and CAPolicy.inf:

    $Now = Get-Date -Format dd.MM.yyyy
    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

    Registry provider in PowerShell is not the good solution to backup and restore registry, therefore we will use reg.exe tool, which is shipped with all Windows operating system installations.

    CA key backup

    Obviously, CA database and configuration backup is useless if we do not have CA certificates and associated private keys. CA may contain multiple certificates, therefore we need to identify CA certificate list and export them to a PFX (pkcs#12) file.

    Note: this step is not necessary if CA certificates and keys are stored on Hardware Security Module (HSM).

    List of CA certificates is stored in registry:

    HKLM\SYSTEM\CurrentControlSet\Services\CertSvc\Configuration\<CAName>\CACertHash

    This registry entry (REG_MULTI_SZ) contains hashes of all CA certificates. Ok, let’s do some registry stuff:

    $active = (Get-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Services\CertSvc\Configuration\).Active
    $Hashes =(Get-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Services\CertSvc\Configuration\$active\).CACertHash

    First registry query returns CA name which is different on each CA. The next query returns an array of CA certificate hashes:

    PS C:\> $active = (Get-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Services\CertSvc\Configuration\).Active
    PS C:\> $Hashes =(Get-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Services\CertSvc\Configuration\$active\).CACertHash
    PS C:\> $Hashes
    ba 8f ec e9 91 65 e6 8c e2 7c 9f 0a f5 f0 66 4f da 39 f7 a2
    5d f3 95 af fb 9e 6f b6 2f 6e c5 8d f6 79 01 50 95 49 49 af
    PS C:\>

    in my case, there are two CA certificates. You see that each hash is lowercase and each hex octet is separated with a space, while PowerShell’s CERT:\ provider requires that all hash characters are uppercase and without any delimiter between hex octets:

    PS C:\> $Hashes = $Hashes | %{$_.ToUpper().Replace(" ",$null)}
    PS C:\> $hashes
    BA8FECE99165E68CE27C9F0AF5F0664FDA39F7A2
    5DF395AFFB9E6FB62F6EC58DF6790150954949AF
    PS C:\>

    Looks much better, isn’t? What next? Next, we will utilize Export method in X509Certificate2Collection class. This method allows to export multiple certificates into a single PFX file. So, we will find all CA certificates in Local Machine\Personal store and add them to a X509Certificate2Collection object and export to a file:

    $certs = New-Object Security.Cryptography.X509Certificates.X509Certificate2Collection
    $Hashes | ForEach-Object {[void]$certs.Add((Get-Item cert:\localmachine\my\$_))}
    # export method exports certificates and encrypted private keys to a byte array
    $Bytes = $certs.Export("PFX","P@ssw0rd1")
    # save byte array to a PFX file
    Set-Content .\$active.p12 $Bytes -Encoding byte
    PS C:\> $certs = New-Object Security.Cryptography.X509Certificates.X509Certificate2Collection
    PS C:\> $Hashes | ForEach-Object {[void]$certs.Add((Get-Item cert:\localmachine\my\$_))}
    PS C:\> $certs
    
    Thumbprint                                Subject
    ----------                                -------
    BA8FECE99165E68CE27C9F0AF5F0664FDA39F7A2  CN=Contoso CA, DC=contoso, DC=com
    5DF395AFFB9E6FB62F6EC58DF6790150954949AF  CN=Contoso CA, DC=contoso, DC=com
    
    
    PS C:\> $Bytes = $certs.Export("PFX","P@ssw0rd1")
    PS C:\> Set-Content .\$active.p12 $Bytes -Encoding byte
    PS C:\> gi '.\Contoso CA.p12'
    
    
        Directory: C:\
    
    
    Mode                LastWriteTime     Length Name
    ----                -------------     ------ ----
    -a---       2013.10.27.     15:43       5429 Contoso CA.p12
    
    
    PS C:\>

    Phinal

    In this two-post series we discovered the CryptoAPI usage to perform CA database backup. Also we discovered additional steps to backup additional CA-related information, like CA configuration, CAPolicy.inf and CA certificates with private keys. To summarize all information, I wrote a sample function (which is ready to use in your environment) and added progress-bar and which outputs the object that displays backup details and status.

    Script is wrapped into a Backup-CertificationAuthority function which defines the following parameters:

  • -Path – specifies the path to a folder where backup set is stored. Target folder must be empty if ‘Full’ backup type is specified. If the folder doesn’t exist, the script will create it.
  • -Type – specifies the backup type. Can by ‘Full’ or ‘Incremental’. By default ‘Full’ is selected, so you don’t need to specify this parameter. When ‘Full’ is selected, a full backup is performed, which includes CA database and log files. When ‘Incremental’ is selected, only log files are backed up.
  • -BackupKey – specifies whether to backup CA certificate and private keys.
  • -Password – specifies the password for PFX file when “BackupKey” switch is specified.
  • -KeepLog – by default, when Full backup is performed, the command truncates log files. Use this switch to keep log files.
  • -Extended – exports additional information: CA configuration and CAPolicy.inf.
  • -Force – deletes target backup folder it is not empty.

    And here are few useful examples:

    Backup-CertificationAuthority –Path C:\Backup –BackupKey –Password P@ssw0rd –Extended 

    Performs full CA backup, in addition, CA configuration, CA certificates and CAPolicy.inf are backed to the C:\Backup folder

    Backup-CertificationAuthority –Path C:\Backup –Type Incremental 

    Performs incremental backup of current CA server. Only log files are backed up.

    Download

    /content/scripts/Backup-CertificationAuthority.ps1

Share this article:

Comments:

Eddie

Hi Vadims,

Do you have a download location from where i can get the whole script?
This would be a terriffic addition since microsoft doesn't provide a one way solution to backup the entire CA.​

Thanks in advance

Edwin

Eddie

I found it through some luck:

http://www.sysadmins.lv/content/scripts/Backup-CertificationAuthority.ps1

Peter

I'm in need of a powershell CA backup script. Can you share the link to script download, plz?

Thank you in advance

Vadims Podans

Added download button.

Cactus

Unable to open the link - http://www.sysadmins.lv/content/scripts/Backup-CertificationAuthority.ps1 and couldn't find download button as well.

If it's avaible in new location. Can you please share the link?

Thank you!

 


Post your comment:

Please, solve this little equation and enter result below. Captcha