Firstly, I know that very similar questions have been asked, but having read through the solutions and modifying my own workflow has not produced the correct results.
I am hoping that by showing the code I am using, someone may be able to point out where I am going wrong. I am trying to import a priate key from my windows 10 machine to my legacy windows 7 with powershell version 2 installed.
The error I am facing is that $cert.hasprivatekey returns true but $cert.privatekey always returns null. However this happens on the windows 10 machine as well so it seems to be a problem generating a key but every guide I have found uses the same syntax!
Here is the code to create the key:
$store = "cert:\CurrentUser\My"
$params = #{
CertStoreLocation = $store
Subject = "CN=Test1"
KeyLength = 2048
KeyAlgorithm = "RSA"
KeyUsage = "DataEncipherment"
Type = "DocumentEncryptionCert"
KeyExportPolicy = 'Exportable'
}
# generate new certificate and add it to certificate store
$cert = New-SelfSignedCertificate #params
Get-ChildItem -path $store
$pwd = ("P#ssword" | ConvertTo-SecureString -AsPlainText -Force)
$privateKey = "$home\Documents\Test1.pfx"
$publicKey = "$home\Documents\Test1.cer"
# Export private key as PFX certificate, to use those Keys on different machine/user
Export-PfxCertificate -FilePath $privateKey -Cert $cert -Password $pwd
# Export Public key, to share with other users
Export-Certificate -FilePath $publicKey -Cert $cert
I have aso tried this to no avail:
$TestCertificate = New-SelfSignedCertificate -Subject 'TestCertificate' -KeyExportPolicy 'Exportable'
Export-PfxCertificate -Cert $TestCertificate -FilePath .\TestCertificate.pfx -Password (ConvertTo-SecureString 'TestPassword' -AsPlainText -Force)
I am then trying to import the private pfx certificate on windows 7 using the following:
$PfxFilePath = "C:\Users\user\Desktop\Test1.pfx"
$pwd = ("P#ssword" | ConvertTo-SecureString -AsPlainText -Force)
$Password = $pwd
$absolutePfxFilePath = Resolve-Path -Path $PfxFilePath
Write-Output "Importing store certificate '$absolutePfxFilePath'..."
Add-Type -AssemblyName System.Security
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.Import($absolutePfxFilePath, $Password, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]"PersistKeySet")
$Store = New-Object System.Security.Cryptography.X509Certificates.X509Store(
[System.Security.Cryptography.X509Certificates.StoreName]::My,
[System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine)
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::"ReadWrite")
$store.Add($cert)
$store.Close()
All ideas welcome!
The key is stored in Key Storage Provider which is not supported by PrivateKey property which is completely obsolete. Instead you need to use newer syntax to obtain private key:
$key = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
Related
I used following PowerShell function to import PFX to my Windows 2008 R2 server's certificate store
function Import-PfxCertificate ([String]$certPath,[String]$certificateStoreLocation = "CurrentUser",[String]$certificateStoreName = "My",$pfxPassword = $null)
{
$pfx = new-object System.Security.Cryptography.X509Certificates.X509Certificate2
$pfx.Import($certPath, $pfxPassword, "Exportable,PersistKeySet")
$store = new-object System.Security.Cryptography.X509Certificates.X509Store($certificateStoreName,$certificateStoreLocation)
$store.open("MaxAllowed")
$store.add($pfx)
$store.close()
return $pfx
}
The caller of the function looks like $importedPfxCert = Import-PfxCertificate $pfxFile "LocalMachine" "My" $password I installed it to local machine's My store. Then I granted read permission to my IIS Application pool.
I have a WCF service which needs to use it
<behaviors>
<serviceBehaviors>
<behavior>
<serviceCredentials>
<serviceCertificate findValue="MyCertName" x509FindType="FindBySubjectName" />
<userNameAuthentication userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="MyValidator" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
When I use a client to call the service, I got exception from WCF It is likely that certificate 'CN=MyCertName' may not have a private key that is capable of key exchange or the process may not have access rights for the private key.
If I remove it from MMC, and manually import the same PFX file from Certificate MMC, to same store and grant same permission, my client can call the service without problem.
So it leads me to think, for some reason if I use PowerShell the private key is screwed somehow.
The funny thing is in either way, I go to MMC and double click on my installed certificate I can see You have a private key that corresponds to the certificate. so it looks like private key is loaded even in PowerShell. permission settings are identical.
Any clue or experience?
Have same issue. Next script work:
function InstallCert ($certPath, [System.Security.Cryptography.X509Certificates.StoreName] $storeName)
{
[Reflection.Assembly]::Load("System.Security, Version=2.0.0.0, Culture=Neutral, PublicKeyToken=b03f5f7f11d50a3a")
$flags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($certPath, "", $flags)
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store($storeName, [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine)
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite);
$store.Add($cert);
$store.Close();
}
I updated Sergey's answer to the following. Note that the using namespace ... syntax is only valid for PS 5.0 and later. If you need this for an earlier version, you will have to add the full namespace, System.Security.Cryptography.X509Certificates, as needed.
using namespace System.Security
[CmdletBinding()]
param (
[parameter(mandatory=$true)] [string] $CertificateFile,
[parameter(mandatory=$true)] [securestring] $PrivateKeyPassword,
[parameter(mandatory=$true)] [string] $AllowedUsername
)
# Setup certificate
$Flags = [Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet `
-bor [Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet `
-bor [Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable
$Certificate = New-Object Cryptography.X509Certificates.X509Certificate2($CertificateFile, $PrivateKeyPassword, $Flags)
# Install certificate into machine store
$Store = New-Object Cryptography.X509Certificates.X509Store(
[Cryptography.X509Certificates.StoreName]::My,
[Cryptography.X509Certificates.StoreLocation]::LocalMachine)
$Store.Open([Cryptography.X509Certificates.OpenFlags]::ReadWrite)
$Store.Add($Certificate)
$Store.Close()
# Allow read permission of private key by user
$PKFile = Get-ChildItem "$env:ProgramData\Microsoft\Crypto\RSA\MachineKeys\$($Certificate.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName)"
$PKAcl = $PKFile.GetAccessControl("Access")
$ReadAccessRule = New-Object AccessControl.FileSystemAccessRule(
$AllowedUsername,
[AccessControl.FileSystemRights]::Read,
[AccessControl.AccessControlType]::Allow
)
$PKAcl.AddAccessRule($ReadAccessRule)
Set-Acl $PKFile.FullName $PKAcl
Save this script to InstallCertificate.ps1, then run it as Administrator:
PS C:\Users\me> .\InstallCertificate.ps1
cmdlet InstallCertificate.ps1 at command pipeline position 1
Supply values for the following parameters:
CertificateFile: c:\my\path\mycert.pfx
PrivateKeyPassword: *********************
AllowedUsername: me
PS C:\Users\me> ls Cert:\LocalMachine\My
<Observe that your cert is now listed here. Get the thumbprint>
PS C:\Users\me> (ls Cert:\LocalMachine\My | ? { $_.Thumbprint -eq $Thumbprint }).PrivateKey
After rebooting, the last line should show that the private key is still installed even as non-Administrator.
Edited to add the ACL step as described in https://stackoverflow.com/a/37402173/7864889.
I had a similar issue on one of our dev servers when importing a certificate through the MMC. My problem was that the Administrators group did not have any permissions on the MachineKeys folder.
C:\Users\All Users\Microsoft\Crypto\RSA\MachineKeys
I added full control on the MachineKeys folder to Administrators and it was able to successfully create the private key when importing the certificate.
Make sure the user you're running Powershell under has access to write to the MachineKeys folder.
The following code referenced below, by Sergey Azarkevich, is what did the trick for me:
$flags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet
I landed on this SO thread as the built in Import-PfxCertificate was not importing CAPI certificates correctly. Unfortunately powerdude's answer cmdlet didnt work for me as it threw The property 'CspKeyContainerInfo' cannot be found on this object. Verify that the property exists. when setting the permissions.
After combining it with this wonderful gist from
milesgratz it worked.
Here is the final modified version made to look like a Import-PfxCertificate substitution.
# Import-CapiPfxCertificate.ps1
# for CNG certificates use built in Import-PfxCertificate
using namespace System.Security
[CmdletBinding()]
param (
[parameter(mandatory=$true)] [string] $FilePath,
[parameter(mandatory=$true)] [securestring] $Password,
[parameter(mandatory=$true)] [string] $CertStoreLocation,
[parameter(mandatory=$false)] [string] $AllowedUsername
)
if (-not ($CertStoreLocation -match '^Cert:\\([A-Z]+)\\([A-Z]+)$')) {
Write-Host "Incorrect CertStoreLocation. See usage in the Import-PfxCertificate documentation" -ForegroundColor Red
exit 1;
}
$StoreName = $Matches.2
$StoreLocation = $Matches.1
# Setup certificate
$Flags = [Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet `
-bor [Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet `
-bor [Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable
$Certificate = New-Object Cryptography.X509Certificates.X509Certificate2($FilePath, $Password, $Flags)
# Install certificate into the specified store
$Store = New-Object Cryptography.X509Certificates.X509Store(
$StoreName,
$StoreLocation)
$Store.Open([Cryptography.X509Certificates.OpenFlags]::ReadWrite)
$Store.Add($Certificate)
$Store.Close()
if (-not ([string]::IsNullOrEmpty($AllowedUsername))) {
# Allow read permission of private key by user
$PKUniqueName = ([System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($Certificate)).key.UniqueName
$PKFile = Get-Item "$env:ProgramData\Microsoft\Crypto\RSA\MachineKeys\$PKUniqueName"
$PKAcl = Get-Acl $PKFile
$PKAcl.AddAccessRule((New-Object AccessControl.FileSystemAccessRule($AllowedUsername, "Read", "Allow")))
Set-Acl $PKFile.FullName $PKAcl
}
I am building ARM-templates to set up test-environments in Azure. I am using DSC to set up the different machines. One thing I want to automate is to import a certificate to the group-policies. You can do it like this manually on the domain-controller (Active-Directory server):
Group Policy Management -> Forest: mydomain.net -> Domains -> mydomain.net -> Group Policy Objects -> Default Domain Policy
Right click -> Edit
Default Domain Policy -> Computer Configuration -> Policies -> Windows Settings -> Security Settings -> Public Key Policies -> Trusted Root Certification Authorities
Right click -> Import
I have laborated with Import-PfxCertificate, CertUtil.exe and .NET C# to accomplish it but haven’t succeeded. What I have tested you can see below, I have put some comments about my thoughts.
Can anyone help me? How should I do this?
First we create a certificate and export it and finally we delete it (we keep the exported one):
$certificateStoreLocation = "CERT:\LocalMachine\My";
$password = ConvertTo-SecureString -String "P#ssword12" -Force -AsPlainText;
$certificate = New-SelfSignedCertificate -CertStoreLocation $certificateStoreLocation -DnsName "Test-Certificate";
$certificateLocation = "$($certificateStoreLocation)\$($certificate.Thumbprint)";
$result = Export-PfxCertificate -Cert $certificateLocation -FilePath "C:\Data\Certificates\Test-Certificate.pfx" -Password $password;
Get-ChildItem $certificateLocation | Remove-Item;
List the certificate stores
foreach($item in Get-ChildItem "CERT:\")
{
Write-Host " - CERT:\$($item.Location)\";
foreach($store in $item.StoreNames.GetEnumerator())
{
Write-Host " - CERT:\$($item.Location)\$($store.Name)";
}
}
PowerShell – Import-PfxCertificate
$certificateStoreLocation = "CERT:\LocalMachine\Root";
$password = ConvertTo-SecureString -String "P#ssword12" -Force -AsPlainText;
Import-PfxCertificate -CertStoreLocation $certificateStoreLocation -FilePath "C:\Data\Certificates\Test-Certificate.pfx" -Password $password;
Get-ChildItem $certificateStoreLocation;
# Now you can find the certificate in the MMC Certificate Snapin:
# [Console Root\Certificates (Local Computer)\Trusted Root Certification Authorities\Certificates]
# Now you can find the certificate in the registry.
# Get-ChildItem "REGISTRY::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates\Root\Certificates\";
# I want to put the certificate here:
# Get-ChildItem "REGISTRY::HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\SystemCertificates\Root\Certificates\";
PowerShell – CertUtil
CertUtil -p "P#ssword12" -ImportPfx "Root" "C:\Data\Certificates\Test-Certificate.pfx";
Get-ChildItem "CERT:\LocalMachine\Root";
CertUtil -p "P#ssword12" -ImportPfx -GroupPolicy "Root" "C:\Data\Certificates\Test-Certificate.pfx"; # No error but the same result as CertUtil -p "P#ssword12" -ImportPfx "Root" "C:\Data\Certificates\Test-Certificate.pfx".
.NET C#
using(var certificate = new X509Certificate2(#"C:\Data\Certificates\Test-Certificate.pfx", "P#ssword12"))
{
// We only have StoreLocation.CurrentUser and StoreLocation.LocalMachine.
// Can I use System.Management.Automation.Security.NativeMethods+CertStoreFlags.CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY
// somehow to create/open a store by calling new X509Store(IntPtr storeHandle).
using (var store = new X509Store(StoreName.Root, StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadWrite);
store.Add(certificate);
}
}
My imaginary solution
Thought this was a possible solution:
Import the certificate to “CERT:\LocalMachine\Root”
Move the registry-key “HKLM:\SOFTWARE\Microsoft\SystemCertificates\Root\Certificates\THUMBPRINT” to “HKLM:\Software\Policies\Microsoft\SystemCertificates\Root\Certificates\THUMBPRINT”
Restart the machine
The registry-keys get correct but the localmachine-root-certificate is still in the certificate-mmc-snapin and no root-certificate is found in the Group Policy Management console.
$certificateRegistryKeyPathPrefix = "HKLM:\SOFTWARE\Microsoft\SystemCertificates\Root\Certificates\";
$certificateStoreLocation = "CERT:\LocalMachine\Root";
$password = ConvertTo-SecureString -String "P#ssword12" -Force -AsPlainText;
$pfxCertificatePath = "C:\Data\Certificates\Test-Certificate.pfx";
$policyCertificateRegistryKeyPathPrefix = "HKLM:\Software\Policies\Microsoft\SystemCertificates\Root\Certificates\";
# Get the thumbprint from the pfx-file so we can check if it's already in the registry.
$certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2;
$certificate.Import($pfxCertificatePath, $password, "DefaultKeySet");
$policyCertificateRegistryKeyPath = "$($policyCertificateRegistryKeyPathPrefix)$($certificate.Thumbprint)";
$policyCertificateRegistryKey = Get-Item -ErrorAction SilentlyContinue -Path $policyCertificateRegistryKeyPath;
if(!$policyCertificateRegistryKey)
{
$certificateRegistryKeyPath = "$($certificateRegistryKeyPathPrefix)$($certificate.Thumbprint)";
$certificateRegistryKey = Get-Item -ErrorAction SilentlyContinue -Path $certificateRegistryKeyPath;
if(!$certificateRegistryKey)
{
$certificate = Import-PfxCertificate -CertStoreLocation $certificateStoreLocation -FilePath $pfxCertificatePath -Password $password;
$certificateRegistryKey = Get-Item -Path $certificateRegistryKeyPath;
}
Move-Item -Destination $policyCertificateRegistryKeyPath -Path $certificateRegistryKeyPath;
# And then we need to reboot the machine.
}
Instead of trying to directly modify the registry under the 'Policies' path, create or modify an 'Registry.pol' file to populate it.
You could use the 'PolicyFileEditor' module from the PowerShell Gallery to do this, but the easiest way is to use the native GroupPolicy module to create and set a Registry.pol file as part of a domain GPO, which will also push out the certificate to member servers.
On a domain controller, import/create a cert so it has a blob value stored in the registry, create a GPO, then run something like the following to configure the GPO (named "DistributeRootCerts" here):
$certsGpoName = 'DistributeRootCerts'
$certThumbprint = '3A8E60952E2CDB7A31713258468A8F0C7FB3C6F6'
$certRegistryKeyPath = 'HKLM:\SOFTWARE\Microsoft\SystemCertificates\MY\Certificates\{0}' -f $certThumbprint
$certBlob = Get-ItemProperty -Path $certRegistryKeyPath -Name 'Blob' | Select -Expand 'Blob'
$certPoliciesRegistryKey = 'HKLM\SOFTWARE\Policies\Microsoft\SystemCertificates\Root\Certificates\{0}' -f $certThumbprint
$null = Set-GPRegistryValue -Name $certsGpoName -Key $certPoliciesRegistryKey -ValueName 'Blob' -Type Binary -Value $certBlob
Then you just need to link the GPO into production with 'New-GPLink'.
I am working on power shell script to export certificate with private key which also includes all the certificates in the path. I wrote a script for that, it is not including the certificates in the path or the root certificate. Below is script. Kindly suggest me if there is any changes to make in my script.
Thanks in Advance.
$Password="#de08nt2128"; #password to access certificate after expting
$CertName="WMSvc-WIN-9KC7DG31JBV"; # name of the certificate to export
$RootCertName="WMSvc-WIN-9KC7DG31JBV"; # root certificate
$DestCertName="testcert"
$ExportPathRoot="C:\DestinationFolder"
$CertListToExport=Get-ChildItem -Path cert:\LocalMachine\My | ?{ $_.Subject -Like "*CN=$CertName*" -and $_.Issuer -eq "CN=$RootCertName" }
foreach($CertToExport in $CertListToExport | Sort-Object Subject)
{
$DestCertName=$CertToExport.Subject.ToString().Replace("CN=","");
$CertDestPath=Join-Path -Path $ExportPathRoot -ChildPath "$DestCertName.pfx"
$type = [System.Security.Cryptography.X509Certificates.X509Certificate]::pfx
$SecurePassword = ConvertTo-SecureString -String $Password -Force –AsPlainText
$bytes = $CertToExport.export($type, $SecurePassword)
[System.IO.File]::WriteAllBytes($CertDestPath, $bytes)
}
"Completed"
Updated script to export all certificates matching a particular name and issuer (along with the private key). Make sure you run this with admin privileges:
# Script to export certificate from LocalMachine store along with private key
$Password = "#de08nt2128"; #password to access certificate after exporting
$CertName = "WMSvc-WIN-9KC7DG31JBV"; # name of the certificate to export
$RootCertName = "WMSvc-WIN-9KC7DG31JBV"; # root certificate (the Issuer)
$ExportPathRoot = "C:\DestinationFolder"
$CertListToExport = Get-ChildItem -Path cert:\LocalMachine\My | ?{ $_.Subject -Like "*CN=$CertName*" -and $_.Issuer -Like "CN=$RootCertName*" }
foreach($CertToExport in $CertListToExport | Sort-Object Subject)
{
# Destination Certificate Name should be CN.
# Since subject contains CN, OU and other information,
# extract only upto the next comma (,)
$DestCertName=$CertToExport.Subject.ToString().Replace("CN=","");
$DestCertName = $DestCertName.Substring(0, $DestCertName.IndexOf(","));
$CertDestPath = Join-Path -Path $ExportPathRoot -ChildPath "$DestCertName.pfx"
$SecurePassword = ConvertTo-SecureString -String $Password -Force -AsPlainText
# Export PFX certificate along with private key
Export-PfxCertificate -Cert $CertToExport -FilePath $CertDestPath -Password $SecurePassword -Verbose
}
Updates from your scrip
For the check $_.Issuer -eq "CN=$RootCertName" to work you will have to include OU, O, S information as well so for it to work correctly so I modified it to be $_.Issuer -Like "CN=$RootCertName*" so that it matches all Issuer's who's name starts with variable $RootCertName
Using $CertToExport.Subject.ToString().Replace("CN=","") for generating pfx file name will cause the name to be of the format some-cert-name, OU=sometext, O=org, C=country.pfx so it is better to restrict upt o the next comma (,) so I added $DestCertName.Substring(0, $DestCertName.IndexOf(","))
Finally using Export-PfxCertifcate to export with private key
I have been following along on numerous how-to articles that explain how to decrypt an AES encrypted password. No matter how I try, the password ends up with $password displaying as System.Security.SecureString. I need the password to echo in plain text because I am calling a command line utility that does not use windows permissions (ADSI/LDAP). Here's my script:
$PasswordFile = "$PSScriptRoot\PowerShell\AESpassword.txt"
$KeyFile = "$PSScriptRoot\PowerShell\AES.key"
$key = Get-Content $KeyFile
$MyPassword = ConvertTo-SecureString (Get-Content $PasswordFile | ConvertTo-SecureString -Key $key) -AsPlainText -Force
$Marshal = [System.Runtime.InteropServices.Marshal]
$BSTR = $Marshal::SecureStringToBSTR($MyPassword)
$password = $Marshal::PtrToStringAuto($BSTR)
According to #MathiasRJessen the suggestion to use $MyPassword = Get-Content $PasswordFile | ConvertTo-SecureString -Key $key worked like a charm! Thank you so much! Make sure to post that comment as the answer so I can give you credit for it. :)
I use a command like this:
get-pfxcertificate C:\test.pfx
Enter password: *******
The command ask me to fill the prompt. But I can't do that in my script (test.ps1 for ex)
What I need is like this:
get-pfxcertificate C:\test.pfx -password "123456"
or something similar so I can run my script without fill in the prompt each time
I'm very thankful for any reply
There's no Password parameter, you can try with a .NET class:
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.Import('C:\test.pfx','123456','DefaultKeySet')
Another option is to extend the abilities of Get-PfxCertificate, essentially enabling the password to be passed in.
# create a backup of the original cmdlet
if(Test-Path Function:\Get-PfxCertificate){
Copy Function:\Get-PfxCertificate Function:\Get-PfxCertificateOriginal
}
# create a new cmdlet with the same name (overwrites the original)
function Get-PfxCertificate {
[CmdletBinding(DefaultParameterSetName='ByPath')]
param(
[Parameter(Position=0, Mandatory=$true, ParameterSetName='ByPath')] [string[]] $filePath,
[Parameter(Mandatory=$true, ParameterSetName='ByLiteralPath')] [string[]] $literalPath,
[Parameter(Position=1, ParameterSetName='ByPath')]
[Parameter(Position=1, ParameterSetName='ByLiteralPath')] [string] $password,
[Parameter(Position=2, ParameterSetName='ByPath')]
[Parameter(Position=2, ParameterSetName='ByLiteralPath')] [string]
[ValidateSet('DefaultKeySet','Exportable','MachineKeySet','PersistKeySet','UserKeySet','UserProtected')] $x509KeyStorageFlag = 'DefaultKeySet'
)
if($PsCmdlet.ParameterSetName -eq 'ByPath'){
$literalPath = Resolve-Path $filePath
}
if(!$password){
# if the password parameter isn't present, just use the original cmdlet
$cert = Get-PfxCertificateOriginal -literalPath $literalPath
} else {
# otherwise use the .NET implementation
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.Import($literalPath, $password, $X509KeyStorageFlag)
}
return $cert
}
And now you can call it
# tada: extended cmdlet with `password` parameter
Get-PfxCertificate 'C:\path\to\cert.pfx' 'password'
Also, if you still need the prompt, you can do something like this.
$pwd = Read-Host 'Please enter your SSL Certificate password.'
Get-PfxCertificate 'C:\path\to\cert.pfx' $pwd
There is now a Get-PfxData command in PowerShell that gets the certificate and chain. The command includes a -Password parameter that takes a SecureString object so you can avoid being prompted.
The EndEntityCertificates property contains an array of certificates at the end of the certificate chain and will contain the same certificate object created by the Get-PfxCertificate command.
The following example converts a normal string to a SecureString object, loads the certificates from a file, then assigns the first/only end certificate to the $SigningCert variable:
$SecurePassword=ConvertTo-SecureString -String "MyPassword" -AsPlainText -Force
$PfxData=Get-PfxData -FilePath ".\cert_filename.pfx" -Password $SecurePassword
$SigningCert=$PfxData.EndEntityCertificates[0]
You can now apply $SigningCert without being prompted for the password.
Thanks to Shay for pointing me in the right direction.
My need was getting the Thumbprint from the PFX file, so I used the non-persistent DefaultKeySet.
Testing under 2012 PS3 fails unless the key set is fully qualified.
Also, a blank space between Import and left parenthesis, i.e. "$cert.Import^^(Sys..." causes an error. Picky, picky parser.
My PFX password is encrypted in the source.
I decrypt it at runtime so it is not visible in the souce.
Set-StrictMode -Version Latest
[string] $strPW = '123456'
[string] $strPFX = 'C:\MyCert.pfx'
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.Import($strPFX,$strPW,[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]"DefaultKeySet")
$cert.Thumbprint
If you are forced to use Windows Powershell (v5.*), then previous solutions in the thread are for you.
However if you can use Powershell 7/Powershell Core, then its own Get-PfxCertificate command does have a -Password parameter.
After installing Powershell 7 and making sure to use pwsh and not powershell, you can load the certificate that way:
$certificate = (Get-PfxCertificate <certificate_path>.pfx -Password (ConvertTo-SecureString -String "<password>" -AsPlainText -Force))
This also works using native PowerShell instead of .NET:
$securePassword = ConvertTo-SecureString -String $strPW -Force -AsPlainText
$cert = Import-PfxCertificate -FilePath $strPFX cert:\LocalMachine\My -Password $securePassword