export certificates using powershell Export-PfxCertificate : Cannot export non-exportable private key

Prins, Andre 116 Reputation points
2021-03-17T09:05:50.14+00:00

in my previous question I showed the script to create certificates - and after fixing the file with the server names, it works fine.
Now I also want to automatically export the certificates.
But I am running into the error Export-PfxCertificate : Cannot export non-exportable private key

does anyone have an idea WHY I get this error (it is exportable) and how to fix it ???

ODD: I can manually export the certificate (that fails with the above error) fine (in MMC Certificates).
I am running powershell with my adminaccount - disabled UAC - it finds the certificates.... but the export fails -and I run MMC also with that same account

ODD too: when I am creating the certificates manually via the website, and then use my script running with my adminaccount, it will successfully export the certificate !!
Soo... I think it has something to do with how I create my certificate. I compared one that i created manually against a certificate created by the script, and I did not find any differences that might explain it. the only difference is, that when I create certificates via the website, the certificates are imported in Current User\Personal.
using the script, they are added to Local Computer\Personal - but manual export works, so I don't think that is the cause.

78636-image.png
this is how they show under Local Computer - Personal

when I create the certificates, I am using these lines:

[NewRequest]  
Subject = "E=$E,CN=$CN,C=$c, S=$s, L=$l, O=$o, OU=$OU"  
MachineKeySet = TRUE  
UseExistingKeySet = False  
KeyLength = 2048  
KeySpec=1  
Exportable = TRUE  
RequestType = PKCS10  
ProviderName = "Microsoft Enhanced Cryptographic Provider v1.0"   
FriendlyName = "$FriendlyName"  
[RequestAttributes]  
CertificateTemplate = "$TemplateName  

I have seen some articles about this, but I have not been able to figure out the solution...

here is the export script, I have tried in 2 ways to export the certificate
like this: Export-PfxCertificate -Cert $cert -FilePath $outname -Password $mypwd -ChainOption EndEntityCertOnly -NoProperties -Verbose
and then commented it out, and added the other method below - but both give the same result

$outpath = "C:\Certificates"  
$servers = get-content "C:\Certificates\servers.txt"  
$server1 = @()  
#create an array with the servers which I want to export  
foreach ($S in $servers)  
{  
    if ($S.IndexOf(".") -gt 1)  
    {$server1 += $S.Substring(0,$S.IndexOf("."))}  
    else  
    {$server1 += $S}  
}  
  
$mypwd = ConvertTo-SecureString -String "P@ssw0rd" -Force -AsPlainText  
$certs = Get-ChildItem -Path cert:\LocalMachine\my    #currentuser  LocalMachine  
foreach ($cert in $certs)  
{  
    if ($cert.Issuer -eq "CN=Managed CA, DC=Domain, DC=com" -and $cert.FriendlyName -in $server1)  
    {  
        $name = $cert.DnsNameList.UniCode  
        $CertFileName = $cert.FriendlyName  
        $outname = $outpath + "\" + $CertFileName + ".pfx"  
        #Export-PfxCertificate -Cert $cert -FilePath $outname -Password $mypwd -ChainOption EndEntityCertOnly -NoProperties -Verbose  
        Get-ChildItem -Path ("Cert:\LocalMachine\my\" + $cert.Thumbprint)|Export-PfxCertificate -FilePath $outname -Password $mypwd -ChainOption EndEntityCertOnly -NoProperties  
        $contd = read-host ("do you want to delete: " + $cert.FriendlyName + " - " + $cert.Subject)  
        if ($contd -match 'y')  
        {  
            $path = "Cert:\LocalMachine\my\" + $cert.Thumbprint  
            Get-ChildItem $path | Remove-Item -Force  
        }  
          
    }  
}  

here the same certificate - but now manually exporting
78723-image.png

Windows Server PowerShell
Windows Server PowerShell
Windows Server: A family of Microsoft server operating systems that support enterprise-level management, data storage, applications, and communications.PowerShell: A family of Microsoft task automation and configuration management frameworks consisting of a command-line shell and associated scripting language.
5,389 questions
0 comments No comments
{count} votes

Accepted answer
  1. Prins, Andre 116 Reputation points
    2021-03-17T13:45:13.187+00:00

    OMG, I may just have found the root cause....
    the newly created certificate - I checked the option "manage private keys" and you just get the permissions to see - so I added my admin account....
    78814-image.png

    and suddenly, the export is successful ?!?!?!!! so it's a permissions issue...


1 additional answer

Sort by: Most helpful
  1. Prins, Andre 116 Reputation points
    2021-05-31T09:57:34.71+00:00

    in MMC, certificates, select a certificate, right click on it:

    101044-image.png

    but more importantly, I realized that I am using in powershell the option -runas which means it runs as local administrator.

    it is a bit complicated. I have to run powershell using run as different user, and use my admin account - because that account has rights on the certificate server to request a certificate.
    but on my local machine I have to run some commands as local administrator.... so the the certificate is imported locally using local administrator -when I call the certreq.exe in line 100,104 and 110 I use -runas
    i.e. line 100: Start-Process "certreq" -ArgumentList "-new $inf $req" -Verb "RunAs"
    And when I am exporting it is with my admin account.

    So I created a separate script to update the certificate permissions to grant my admin account rights

    Main script:

    clear  
    $servers = get-content "C:\Certificates\servers.txt"  
    $outpath = "C:\Certificates"  
    $password = "Password"  
      
    $CAName = "certenroll.domain.com"  
    $TemplateName = "operationsManagerCert"  
    $E = "andre.prins@company.com"  
    $OU = "CES"  
    $O = "company"  
    $L = "city"  
    $S = "state"  
    $C = "country"  
      
      
    ##############################################################################  
      
      
    function Remove-ReqTempfiles() {  
        param(  
            [String[]]$tempfiles  
        )  
        Write-Verbose "Cleanup temp files..."  
        Remove-Item -Path $tempfiles -Force -ErrorAction SilentlyContinue  
    }  
      
    Function TestReq  
    {  
        $Done=$False  
        Start-Sleep -Seconds 5  
        do  
        {  
            $proc = Get-Process -Name certreq -ErrorAction SilentlyContinue  
            if ($proc.count -ge 1)  
            {start-sleep -Seconds 1}  
            else  
            {$Done = $true}  
        } until ($Done)  
    }  
      
      
    ############################### Create Certificates and load in \LocalMachine\My ########################################################  
      
    #get the CA details from AD  
    $rootDSE = [System.DirectoryServices.DirectoryEntry]'LDAP://RootDSE'  
    $searchBase = [System.DirectoryServices.DirectoryEntry]"LDAP://$($rootDSE.configurationNamingContext)"  
    $CAs = [System.DirectoryServices.DirectorySearcher]::new($searchBase,'objectClass=pKIEnrollmentService').FindAll()  
      
    if($CAs.Count -eq 4)  
    {$CAName = "$($CAs[2].Properties.dnshostname)\$($CAs[2].Properties.cn)"}  
    else   
    {$CAName = ""}  
      
    if (!$CAName -eq "")   
    {$CAName = " -config `"$CAName`""}  
      
    $server1 = @()  
    foreach ($CN in $servers)  
    {  
        if ($CN -eq "" -or $CN -eq $Null) {continue}  #skip empty lines  
        if ($CN.IndexOf(".") -gt 1)  
        {  
     $FriendlyName = $CN.Substring(0,$CN.IndexOf("."))  
     $server1 += $CN.Substring(0,$CN.IndexOf("."))  
     }  
        else  
        {  
     $FriendlyName = $CN  
     $server1 += $CN  
     }  
      
    $file = @"  
    [NewRequest]  
    Subject = "E=$E,CN=$CN,C=$c, S=$s, L=$l, O=$o, OU=$OU"  
    MachineKeySet = TRUE  
    UseExistingKeySet = False  
    KeyLength = 2048  
    KeySpec=1  
    Exportable = TRUE  
    RequestType = PKCS10  
    ProviderName = "Microsoft RSA SChannel Cryptographic Provider"  
    FriendlyName = "$FriendlyName"  
    [RequestAttributes]  
    CertificateTemplate = "$TemplateName"  
    "@  
      
        try  
        {  
      
            $inf = [System.IO.Path]::GetTempFileName()  
            $req = [System.IO.Path]::GetTempFileName()  
            $cer = Join-Path -Path $env:TEMP -ChildPath "$CN.cer"  
            $rsp = Join-Path -Path $env:TEMP -ChildPath "$CN.rsp"  
      
            Remove-ReqTempfiles -tempfiles $inf, $req, $cer , $rsp  
            Set-Content -Path $inf -Value $file  
      
            Write-host "generate .req file with certreq.exe"  
            $error.Clear()  
            Start-Process "certreq" -ArgumentList "-new $inf $req" -Verb "RunAs"  
            TestReq  
      
            Write-host "certreq -submit $CAName `"$req`" `"$cer`""  
            Start-Process "certreq" -ArgumentList "-submit $CAName $req $cer" -Verb "RunAs"  
            TestReq  
      
            Write-host "request was successful. Result was saved to $cer"  
      
            write-host "retrieve and install the certificate"  
            Start-Process "certreq" -ArgumentList "-accept $cer -user" -Verb "RunAs"  
      
            TestReq  
      
            write-host "Done, cleaning up temp files"  
            Remove-ReqTempfiles -tempfiles $inf, $req, $cer, $rsp  
      
        }  
        catch  
        {  
            write-host "Error during request"  
            $error  
        }  
    }  
      
      
    #######################################################################################  
    # update security to allow $username to export the cert with private key - we have to use "RunAs" Administrator  
    # so the easiest is to create a seperate script.  
    # This script loads servers.txt and finds all the certificates in the store and updates security  
      
    Start-Process PowerShell.exe -Verb "RunAs" -ArgumentList "C:\Certificates\UpdateSecurity.ps1" -Wait  
      
    #######################################################################################  
    Write-Host "Start exporting the certificates"  
      
    $mypwd = ConvertTo-SecureString -String $password -Force -AsPlainText  
    $certs = Get-ChildItem -Path cert:\LocalMachine\my    #currentuser  LocalMachine  
    foreach ($cert in $certs)  
    {  
        if ($cert.Issuer -eq "CN=BHI WH Managed CA 1, DC=ent, DC=bhicorp, DC=com" -and $cert.FriendlyName -in $server1)  
        {  
            $name = $cert.DnsNameList.UniCode  
            $CertFileName = $cert.FriendlyName  
            $outname = $outpath + "\" + $CertFileName + ".pfx"  
            Export-PfxCertificate -Cert $cert -FilePath $outname -Password $mypwd -ChainOption EndEntityCertOnly -NoProperties -Verbose  
            write-host "Exported Certificate for " $cert.FriendlyName  
        }  
    }  
      
      
    #Remove the certificates from the store once exported.  
    Start-Process PowerShell.exe -Verb "RunAs" -ArgumentList "C:\Certificates\DeleteCerts.ps1" -Wait  
    write-host "Finished"  
    

    this is the script UpdateSecurity.ps1 - it also reads the same servers.txt, so it exactly knows which certificates to update....

    $ErrorActionPreference="continue"  
    $userName = "domain\Myadminaccount"  
    $permission = "FullControl"  
    $certStoreLocation = "\localmachine\My"  
    $servers = get-content "C:\Certificates\servers.txt"  
    $server1 = @()  
    foreach ($S in $servers)  
    {  
        if ($S.IndexOf(".") -gt 1)  
        {$server1 += $S.Substring(0,$S.IndexOf("."))}  
        else  
        {$server1 += $S}  
    }  
      
    $mypwd = ConvertTo-SecureString -String "Baker123" -Force -AsPlainText  
    $certs = Get-ChildItem -Path cert:\LocalMachine\my    #currentuser  LocalMachine  
    foreach ($cert in $certs)  
    {  
        if ($cert.Issuer -eq "CN=CERT CA 1, DC=Domain, DC=com" -and $cert.FriendlyName -in $server1)  
        {  
      
            $store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My","LocalMachine")  
            $store.Open("ReadWrite")  
            $rwCert = $store.Certificates | where {$_.Thumbprint -eq $cert.Thumbprint}  
            $rsaCert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)  
            $fileName = $rsaCert.key.UniqueName  
            $path = $env:ALLUSERSPROFILE + "\Microsoft\Crypto\RSA\MachineKeys\" + $fileName  
            $permissions = Get-Acl -Path $path  
            $rule = new-object security.accesscontrol.filesystemaccessrule $userName, $permission, allow  
            $permissions.AddAccessRule($rule)  
            Set-Acl -Path $path -AclObject $permissions  
        }  
    }  
    

    and to complete all the work, and delete the certificates at the end, DeleteCerts.ps1

    $servers = get-content "C:\Certificates\servers.txt"  
    $certs = Get-ChildItem -Path cert:\LocalMachine\my      
    foreach ($CN in $servers)  
    {  
        if ($CN -eq "" -or $CN -eq $Null) {continue}  #skip empty lines  
        if ($CN.IndexOf(".") -gt 1)  
        {  
     $server1 += $CN.Substring(0,$CN.IndexOf("."))  
     }  
        else  
        {  
     $server1 += $CN  
     }  
    }  
      
    foreach ($cert in $certs)  
    {  
        if ($cert.Issuer -eq "CN=Certs CA 1, DC=Domain, DC=com" -and $cert.FriendlyName -in $server1)  
        {  
                $path = "Cert:\LocalMachine\my\" + $cert.Thumbprint  
                Get-ChildItem $path | Remove-Item -Force  
        }  
    }  
    

    I run this now on a regular basis. all I do is populating servers.txt and run my script.

    and to overcome the annoying UAC prompts when running the CertReq.exe as local administrator, I have 2 batchfiles to disable and enable the UAC:

    disable:
    REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v ConsentPromptBehaviorAdmin /t REG_DWORD /d 0 /f

    Enable UAC:
    REG ADD HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v ConsentPromptBehaviorAdmin /t REG_DWORD /d 5 /f

    and while writing this last bit, I realize, I can add that to the main script ;-)
    so that's a last modification I will add myself, but you can figure that out yourself too :-)

    0 comments No comments