Some time ago one guy asked me for a script that will do the following:

  1. Extracts all archived keys from CA database;
  2. decrypts these keys (by using key recovery agent certificate)
  3. saves decrypted keys in a PFX (PKCS#12) format;
  4. creates simple log files: one with serial numbers that were decrypted successfully and another were decryption process was unsuccessful.

This scenario is common when an organization decided to move to a new PKI with new CA database. However it is highly recommended to move archived private keys from old to a new CA server. This is because even if new PKI is used, there might be a lot of encrypted stuff (encrypted files or outlook mails). And if user looses his/her encryption private keys he/she still should have an access to encrypted content. As the result you should move archived keys to a new CA for key recovery purposes only.

Our start point is ICertView2 and ICertAdmin2 interfaces. ICertView2 interface is used to go through CA database and retrieves request row IDs with archived private keys. After this I'm using ICertAdmin2::GetArchivedKey method to extract archived key as a signed PKCS#7 message. Currently there are no any .NET/COM interfaces to recover (decrypt) archived and I'm using certutil.exe utility to do that. Here is a code with my comments:

# specify CA server configuration string. The string is in the following format:
# CAComputerName\CAName
$ConfigString = "dc2\contoso-dc2-ca"
# specify a folder to store recovered keys and log files
$Path = "C:\certs"
# specify password for recovered keys
$password = "P@ssw0rd"
# specify log file paths
$Ok = "$Path\Ok.log"
$Failed = "$Path\Failed.log"
# create above specified folder
md $Path -Force
# instantiate ICertView2 interface
$CaView = New-Object -ComObject CertificateAuthority.View
# instantiate ICertAdmin2 interface
$CertAdmin = New-Object -ComObject CertificateAuthority.Admin
# open connection to a CA database
try {$CaView.OpenConnection($ConfigString)}
catch {Write-Warning "Unable to connect to '$configString'"; $_; throw}
# set CA lookup filter. We will search archived keys in Issued Certificates
# node only. Thus we will filter requests with Disposition = 20
$RColumn = $CAView.GetColumnIndex($False, "Disposition")
$CaView.SetRestriction($RColumn,1,0,20)
# set output columns
$properties = "RequestID","Request.RawArchivedKey","CommonName","SerialNumber"
$CaView.SetResultColumnCount($properties.Count)
$properties | %{$CAView.SetResultColumn($CAView.GetColumnIndex($False, $_))}
# open CA database view and iterate through request rows
$Row = $CaView.OpenView()
while ($Row.Next() -ne -1) {
    $cert = New-Object psobject
    $Column = $Row.EnumCertViewColumn()
    while ($Column.Next() -ne -1) {
        $current = $Column.GetName()
        $Cert | Add-Member -MemberType NoteProperty $($Column.GetName()) -Value $($Column.GetValue(1)) -Force
    }
    # determine whether current request contains archived keys. Otherwise iterate to a next row.
    if ($Cert."Request.RawArchivedKey" -like "AA==*") {
        # if current row contains archived keys, extract them to a Base64 format without X.509
        # header and footer.
        $Base64 = $CertAdmin.GetArchivedKey($ConfigString,$cert.RequestID,1)
        # extract and sanitize certificate common name. This sanitized name will be
        # used as a part of recovered file name
        $cert.CommonName = $cert.CommonName -replace '[\\/:\*?"<>|\n\r]', " "
        # append serial number to a certificate holder's name
        $FileName = $cert.commonname + "_" + $cert.SerialNumber
        # save extracted PKCS7 message to a file.
        Set-Content -Path "$Path\$FileName" -Value $Base64 -Encoding Ascii
        # this part is used to determine whether current user has corresponding private key
        # to perform key recovery.
        $Certs = New-Object Security.Cryptography.X509Certificates.X509Certificate2Collection
        # import PKCS7 file to a certificate collection
        [void]$Certs.Import([Convert]::FromBase64String($Base64))
        # and retrieve all KRA certificates
        $KRAsInPKCS7 = $Certs | ?{($_.extensions |?{$_.Oid.FriendlyName -eq "Enhanced Key Usage"}) | %{$_.format(0) -match "Key Recovery Agent"}} | %{$_.serialnumber}
        # lookup personal store to find any matching certificate with private key.
        if ((dir cert:\currentuser\my | ?{$KRAsInPKCS7 -contains $_.SerialNumber -and $_.HasPrivateKey -eq $true})) {
            # if KRA certificate with corresponding private key is found, perform key recovery process
            $output = certutil -p $password -recoverkey $Path\$FileName "$Path\$FileName.pfx" | Select-String "certutil:" | %{$_.Tostring()}
            # if certutil succeeds or fails, write serial number to a corresponding log file
            if ($output -eq "CertUtil: -RecoverKey command completed successfully.") {
                Add-Content -Path $Ok -Value $cert.SerialNumber
                # if succeeds, PKCS7 file is no longer necessary. Otherwise PKCS7 file
                # is leaved for future key recovery procedure (PKCS7 file must be transfered to
                # a KRA that was used for this certificate request key archival)
                Remove-Item $Path\$FileName -Force
            } else {
                Add-Content -Path $Failed -Value $cert.SerialNumber
            }
        } else {
            # if no KRA certificate was found in Personal store, write to a log file
            Write-Warning "There is no suitable certificates for archived cert: $($cert.SerialNumber)"
            Add-Content -Path $Failed -Value $cert.SerialNumber
        }
    }
    $Column.Reset()
}
$Row.Reset()
# end

You can use this example as a start point for your archived key migration process and you may modify it as you need. To import recovered keys to a new CA database, at first you must configure key recovery agents on it as described here: http://technet.microsoft.com/en-us/library/ee449464(WS.10).aspx

and use one of the following method:


Share this article:

Comments:

Stan

Is there a more updated version of this procedure, using features from newer releases of PSPKI?

I'm going to need to reencode all my archived keys with new certificate to be able to... simplify some operating procedures.

Vadims Podāns

No, there is no updated version of this script. This is somewhat a one-time script and not included in PSPKI.

Stan

If I need to update the encryption key on a CA, is it enough to download-reencode-upload, or something special needs to be done to remove keys with old KRA key?

Vadims Podāns

Why you need to update the encryption key? If existing KRA key is expired, just assign new KRA key and use it for subsequent encryption operations. There is no much need in re-encryption with new KRA key. Just maintain the history of KRA keys for decryption.


Post your comment:

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