Dr Schizo
Dr Schizo

Reputation: 4366

Importing Certificate with private key

I am trying to import a certificate with a private key and although my script is "working" it is missing something which I can't seem to figure out.

I have an app which talks to KeyVault and uses this certificate for authorization. I have the password for said certificate and when I install the PFX manually along with assigning the NETWORK SERVICE account permission this works fine. However, using the following PowerShell I get the error Keyset does not exist. Everything seems normal when I check this under cert manager. Clearly, the UI is doing something different.. any ideas?

EDIT When I add the NETWORK SERVICE account manually this works fine. PowerShell is running in admin mode when I execute the script so perhaps its some weirdness in that?

The script

cls
$certName = "certname.pfx"
$path = "C:\Certificates\$certName"
$serviceAccount = 'NETWORK SERVICE'
$permissionType = 'Read'
$password = 'somepassword'

try {
    # Import the certificate maintaining the private key format
    $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
    $cert.Import($path, $password, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]"PersistKeySet")

    # Add the cert to personal store location
    $store = Get-Item Cert:\LocalMachine\My
    $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]"ReadWrite")
    $store.Add($cert)
    $store.Close()

    # Get SecurityIdentifier value from current username
    $currentUser = New-Object System.Security.Principal.NTAccount($env:USERNAME)
    $strSID = $currentUser.Translate([System.Security.Principal.SecurityIdentifier])
    $path = "$env:USERPROFILE\AppData\Roaming\Microsoft\Crypto\RSA\$($strSID.Value)\$($cert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName)"
    $acl = Get-Acl $path
    $rule = New-Object Security.AccessControl.FileSystemAccessRule $serviceAccount, 'FullControl', Allow
    $acl.AddAccessRule($rule)
    Set-Acl $path $acl

    Write-Host "Cert installed, press any key to continue" -ForegroundColor Green
    Read-Host
}
catch {
    Write-Error -Message $_
}

ANSWER For others experiencing the same issues as I was, simply provide multiple storage flags:

$flags = @([System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]"MachineKeySet", [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]"PersistKeySet")
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.Import($path, $password, $flags)

Upvotes: 1

Views: 5905

Answers (1)

bartonjs
bartonjs

Reputation: 33256

You probably want to import the certificate using both PersistKeySet and MachineKeySet. Looking at how you're doing things here you have

  • Open the PFX without specifying User vs Machine KeySet
  • Add it to the LocalMachine store.
  • Find the key file under the current user's SID.

The Windows Cert store, AFAIK, doesn't write down which user owns a key, just that this cert is paired with "user key {id}". So it needs to be the machine (shared) key store.

The "keyset does not exist" is because the Network Service user doesn't have a key named with that ID in their keystore.

So the difference, probably, is that the UI is asserting the MachineKeySet (equivalent) because it knows you're importing into a LocalMachine store.

Upvotes: 3

Related Questions