Import-PfxCertificate no-ops, but no error or anything - powershell

I'm trying to import a PFX file into the local certificate store. However, Import-PfxCertificate just does nothing at all. No return value, no error, nothing:
I can double click on the PFX file in Explorer and import it with the same password, which works. Something about the PowerShell CmdLet isn't working. I've also tried other stores, such as Cert:\LocalMachine\My and TrustedPeople. Running it with -Verbose -Debug doesn't show anything extra. Nothing in the Application or Security event logs either. I'm also running as an admin. Ideas?

The Pfx file might have a cert chain. Treating it as a collection would be a better way of handling the certificate store. See installing cert chain for the C# this was based off;
[string] $certPath = '.\test.pfx';
[string] $certPass = 'MyPassword';
# Create a collection object and populate it using the PFX file
$collection = [System.Security.Cryptography.X509Certificates.X509Certificate2Collection]::new();
$collection.Import($certPath, $certPass, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet);
try {
# Open the Store My/Personal
$store = [System.Security.Cryptography.X509Certificates.X509Store]::new('My');
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite);
foreach ($cert in $collection) {
Write-Host ("Subject is: '{0}'" -f $cert.Subject )
Write-Host ("Issuer is: '{0}'" -f $cert.Issuer )
# Import the certificate into an X509Store object
# source https://support.microsoft.com/en-au/help/950090/installing-a-pfx-file-using-x509certificate-from-a-standard-net-applic
if ($cert.Thumbprint -in #($store.Certificates | % { $_.Thumbprint } )) {
Write-Warning "Certificate is already in the store"
# Force the removal of the certificate so we have no conflicts, not required if this is the first install
$store.Remove($cert)
}
# Add in the certificate
$store.Add($cert);
}
} finally {
if($store) {
# Dispose of the store once we are done
$store.Dispose()
}
}

Related

Export certificate from object with private key Export-Clixml

I'm trying to store a few objects as Clixml from PowerShell.
I can successfully store my array and certificate, but the private key is not exported with it.
Example:
PS > $cert.HasPrivateKey
true
PS > $outObject = $array
PS > $outObject += $cert
PS > $outObject | Export-Clixml -Path .\file.xml
PS > $inObject = Import-Clixml -Path .\file.xml
PS > $newcert = $inObject | Where-Object { $_.GetType().Name -like "*X509Certificate2" }
PS > $newcert.HasPrivateKey
false
I noted that there is a method for $cert.PrivateKey:
ExportParameters Method System.Security.Cryptography.RSAParameters ExportParameters(bool includePrivateParameters)
This script is not specifically running in Windows and the certificate isn't installed in the CABI store, only the variable imported from Get-PfxCertificate.
Long story short, I'm building a module that connects to an API with client authentication. I'm trying to pull client authentication from the Clixml file.
The private key is not a part of X509Certificate2 object, thus it is not exported along with the public certificate. The private key is linked to the public certificate.
In order to export a certificate with a private key, you have to serialize the certificate and private key object before passing it to Export-CliXml.
Use the X509Certificate2.Export(X509Content​Type, Secure​String) method to export the certificate with the associated private key to PFX (PKCS#12 container). The private key material is password-protected.
Use the X509Certificate2.Import(Byte[], Secure​String, X509Key​Storage​Flags) method to import the certificate and associated private key after calling the Import-CliXml cmdlet.
This is the only option you have. Also, be aware that this approach works only when the private key is exportable. If the private key is non-exportable, the Export method will fail.
By converting the certificate object to PFX format (as suggested by Crypt32) and saving my objects in a hash table I was able to successfully export and import the array and certificate with private key.
PS > $cert.HasPrivateKey
true
PS > $pfx = $cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx,'Pa$$w0rd')
PS > $outObject = #{
>> myArray = $array
>> myCert = $pfx
>> }
PS > Export-Clixml -InputObject $outObject -Path .\file.xml
PS > $inObject = Import-Clixml -Path .\file.xml
PS > $newCert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::New($inObject.myCert,'Pa$$w0rd')
PS > $newCert.HasPrivateKey
true

Check if string is X509Store path or PFX file

I need to check if a string is a certificate store (eg. "Cert:\CurrentUser\My") or a pfx file path (eg. "D:\\PFXfiles\self-signed.pfx")
Which method is better to use and why? What are the cons/pros for each? Is there a better method?
Method 1:
if ($certLocation.ToUpper().Contains(".PFX"))
{
#it's a .pfx file
}
else
{
#it's a cert store
}
Method 2:
if ((Resolve-Path -LiteralPath $certLocation).Provider.Name -eq "FileSystem")
{
#it's a .pfx file
}
elseif ((Resolve-Path -LiteralPath $certLocation).Provider.Name -eq "Certificate"
{
#it's a cert store
}
I'd use Split-Path:
switch ((Split-Path $certLocation -Qualifier)) {
'cert:' { 'cert store' }
'c:' { 'file path' }
default { Write-Error "invalid provider: $_" }
}
Check the extension inside the 'file path' script block if required.
you should see magic number of file , I recommend to you use file command exist in linux and the programmer provide for windows see this link
see my example
C:\Program Files (x86)\GnuWin32\bin>file.exe c:\Users\soheil\Desktop\1.pfx
c:\Users\soheil\Desktop\1.pfx; data
C:\Program Files (x86)\GnuWin32\bin>file.exe c:\Users\soheil\Desktop\2.pfx
c:\Users\soheil\Desktop\2.pfx; empty
or like this
C:\Program Files (x86)\GnuWin32\bin>file.exe c:\a.txt
c:\a.txt; UTF-8 Unicode (with BOM) English text, with very long lines, with CRLF
line terminators
first 1.pfx i create self sign with IIS
second 2.pfx i rename txt file to 2.pfx
if you want exactly understand what file is you should use file command for see magic number
To me, testing the string would be better because it's more efficient to just manipulate the string vs resolving the path, creating another object and then reading a property of on that object, but in reality it's not going to change anything. I'd do it a little differently though.
if ($certLocation.Split(":")[0] -like "cert") {
#it's a cert store
}
else {
#it's a pfx
}
I'll chime in... If you are testing a string to see where the path lies use the Resolve-Path cmdlet, and select the Provider property.
$StringPath = "Cert:\CurrentUser\my","C:\Temp\fakecert.pfx"
Switch($StringPath){
{(Resolve-Path $_|Select -Expand Provider).tostring() -eq "Microsoft.PowerShell.Security\Certificate"} {"$_ is in the Certificate Store";continue}
{(Resolve-Path $_|Select -Expand Provider).tostring() -eq "Microsoft.PowerShell.Core\FileSystem"} {"$_ is in the File System"}
}
Cert:\CurrentUser\my is in the Certificate Store
C:\Temp\fakecert.pfx is in the File System
That way PowerShell will tell you who it used to resolve the path. This will throw errors if you provide invalid paths, but should give you accurate info as to where items are stored. Error catching could be added to catch invalid paths, but that's up to you.

Renewing a Certificate using PowerShell

I am trying to renew a certificate (on my local machine) that is going to expire shortly. I know to do this manually but I can't find a way to do this using Powershell. I've looked up PKIPS and QAD but they don't seem to have any cmdlets with regard to renewing a certificate. Could anyone point me to any other library that achieves this task?
This is the function I used to renew a certificate that was generated from an Active Directory template.
function Renew-Certificate {
[CmdletBinding()]
Param([Parameter(Mandatory=$true, ValueFromPipeline=$false)] [ValidateNotNullOrEmpty()] [string]$Thumbprint,
[Parameter(Mandatory=$false, ValueFromPipeline=$false)] [switch]$MachineStore)
Process {
#https://msdn.microsoft.com/en-us/library/windows/desktop/aa379399(v=vs.85).aspx
#X509CertificateEnrollmentContext
$ContextUser =0x1
$ContextMachine =0x2
$ContextAdministratorForceMachine=0x3
#https://msdn.microsoft.com/en-us/library/windows/desktop/aa374936(v=vs.85).aspx
#EncodingType
$XCN_CRYPT_STRING_BASE64HEADER =0
$XCN_CRYPT_STRING_BASE64 =0x1
$XCN_CRYPT_STRING_BINARY =0x2
$XCN_CRYPT_STRING_BASE64REQUESTHEADER=0x3
$XCN_CRYPT_STRING_HEX =0x4
$XCN_CRYPT_STRING_HEXASCII =0x5
$XCN_CRYPT_STRING_BASE64_ANY =0x6
$XCN_CRYPT_STRING_ANY =0x7
$XCN_CRYPT_STRING_HEX_ANY =0x8
$XCN_CRYPT_STRING_BASE64X509CRLHEADER=0x9
$XCN_CRYPT_STRING_HEXADDR =0xa
$XCN_CRYPT_STRING_HEXASCIIADDR =0xb
$XCN_CRYPT_STRING_HEXRAW =0xc
$XCN_CRYPT_STRING_NOCRLF =0x40000000
$XCN_CRYPT_STRING_NOCR =0x80000000
#https://msdn.microsoft.com/en-us/library/windows/desktop/aa379430(v=vs.85).aspx
#X509RequestInheritOptions
$InheritDefault =0x00000000
$InheritNewDefaultKey =0x00000001
$InheritNewSimilarKey =0x00000002
$InheritPrivateKey =0x00000003
$InheritPublicKey =0x00000004
$InheritKeyMask =0x0000000f
$InheritNone =0x00000010
$InheritRenewalCertificateFlag=0x00000020
$InheritTemplateFlag =0x00000040
$InheritSubjectFlag =0x00000080
$InheritExtensionsFlag =0x00000100
$InheritSubjectAltNameFlag =0x00000200
$InheritValidityPeriodFlag =0x00000400
$X509RequestInheritOptions=$InheritDefault+$InheritRenewalCertificateFlag+$InheritTemplateFlag
if ($MachineStore.IsPresent) {
$Path="Cert:\LocalMachine\My\$Thumbprint"
$Context=$ContextAdministratorForceMachine
}
else {
$Path="Cert:\CurrentUser\My\$Thumbprint"
$Context=$ContextUser
}
$Cert=Get-Item -Path $Path
$PKCS10=New-Object -ComObject X509Enrollment.CX509CertificateRequestPkcs10
$PKCS10.Silent=$true
$PKCS10.InitializeFromCertificate($Context,[System.Convert]::ToBase64String($Cert.RawData), $XCN_CRYPT_STRING_BASE64, $X509RequestInheritOptions)
$PKCS10.AlternateSignatureAlgorithm=$false
$PKCS10.SmimeCapabilities=$false
$PKCS10.SuppressDefaults=$true
$PKCS10.Encode()
#OK=$InheritTemplateFlag+$InheritNewSimilarKey
#OK=$InheritSubjectFlag+$InheritTemplateFlag+$InheritNewSimilarKey
#OK=$InheritDefault+$InheritRenewalCertificateFlag+$InheritTemplateFlag
#BAD=$InheritNewSimilarKey+$InheritRenewalCertificateFlag
#BAD=$InheritDefault+$InheritRenewalCertificateFlag (Template required)
#https://msdn.microsoft.com/en-us/library/windows/desktop/aa377809(v=vs.85).aspx
$Enroll=New-Object -ComObject X509Enrollment.CX509Enrollment
$Enroll.InitializeFromRequest($PKCS10)
Write-Verbose "Renewing..."
$Error.Clear()
Try { $Enroll.Enroll() }
Catch {
Write-Verbose "Unable to renew"
$Errors=$Error.Clone()
$Errors | ForEach-Object { Write-Error $_.Exception.Message }
$result="0"
}
if ($Error.Count -eq 0) {
$Cert=New-Object Security.Cryptography.X509Certificates.X509Certificate2
$Cert.Import([System.Convert]::FromBase64String($Enroll.Certificate(1)))
$result=$Cert.Thumbprint
Write-Verbose "New Thumbprint is $result"
}
$result
}
}
I was looking for a Powershell solution as well, but I found that in the end, the Windows certreq command just provided more out-of-the-box operations.
Renewing a certificate with certreq then goes like:
$cert = (ls Cert:\LocalMachine\My | Where-Object { $_.Subject -eq 'CN=myserver' })[0]
&certreq #('-Enroll', '-machine', '-q', '-cert', $cert.SerialNumber, 'Renew', 'ReuseKeys')

Unable to delete a directory with square brackets in name using WinSCP and PowerShell

I am trying to write a PowerShell script that automatically deletes empty directories on our FTP server. I don't have any direct access to the server on which the directories reside - I can only access them via FTP. For this reason, I have written a PowerShell script that uses WinSCP .NET assembly to (try to) delete the empty directories. However, I have a problem, in that many of the directories on the server have square brackets in the directory name.
For example, the directory name might be called: [a]153432
There are lots of these directories, hence my desire to write a script to delete them. This occurs because they've been created by a program that uses a number to create the directories it requires. In order to work on this program, I created an empty directory
/latest/[b]test
My program goes like this:
# program to test deletion of directories with square-brackets in the name.
$HOSTNAME="myftpservername"
$USERNAME="myftpusername"
$PASSWORD="myftppassword"
$DLL_LOCATION="C:\Program Files (x86)\WinSCP\WinSCPnet.dll"
$LOCAL_DIRECTORY="C:\testdir\extended\baseroot"
# Load WinSCP .NET assembly
[Reflection.Assembly]::LoadFrom($DLL_LOCATION) | Out-Null
# Session.FileTransferred event handler
try
{
$sessionOptions = New-Object WinSCP.SessionOptions
$sessionOptions.Protocol = [WinSCP.Protocol]::ftp
$sessionOptions.HostName = $HOSTNAME
$sessionOptions.UserName = $USERNAME
$sessionOptions.Password = $PASSWORD
$session = New-Object WinSCP.Session
try
{
# Connect
$session.Open($sessionOptions)
$remoteFileWithUnixPath = "/latest/[b]test"
$removalResult = $session.RemoveFiles($remoteFileWithUnixPath)
if ($removalResult.IsSuccess)
{
Write-Host ("Removal of remote file {0} succeeded" -f $remoteFileWithUnixPath)
}
else
{
Write-Host ("Removal of remote file {0} failed" -f $remoteFileWithUnixPath)
}
}
finally
{
# Disconnect, clean up
$session.Dispose()
}
exit 0
}
catch [Exception]
{
Write-Host $_.Exception.Message
exit 1
}
When I run it, it displays the following message:
PS C:\Users\dbuddrige\Documents\dps> .\delete-squarebracket-dir.ps1
Removal of remote file /latest/[b]test succeeded
However, the directory is not deleted.
I have also tried specifying the directory name with delimiters such as:
$remoteFileWithUnixPath = "/latest/\[b\]test"
But this also fails (but at least it says so):
PS C:\Users\dbuddrige\Documents\dps> .\delete-squarebracket-dir.ps1
Removal of remote file /latest/\[b\]test failed
BUT, if I change the directory name [and the variable $remoteFileWithUnixPath] to something like
/latest/foo
And then re-run the program, it deletes the directory /latest/foo just fine.
Does anyone have any ideas what I need to do to get this to work?
The answer to this question was pointed out by Martin Prikryl [Thanks!]
The fully working code follows:
# program to test deletion of directories with square-brackets in the name.
$HOSTNAME="myftpservername"
$USERNAME="myftpusername"
$PASSWORD="myftppassword"
$DLL_LOCATION="C:\Program Files (x86)\WinSCP\WinSCPnet.dll"
$LOCAL_DIRECTORY="C:\testdir\extended\baseroot"
# Load WinSCP .NET assembly
[Reflection.Assembly]::LoadFrom($DLL_LOCATION) | Out-Null
# Session.FileTransferred event handler
try
{
$sessionOptions = New-Object WinSCP.SessionOptions
$sessionOptions.Protocol = [WinSCP.Protocol]::ftp
$sessionOptions.HostName = $HOSTNAME
$sessionOptions.UserName = $USERNAME
$sessionOptions.Password = $PASSWORD
$session = New-Object WinSCP.Session
try
{
# Connect
$session.Open($sessionOptions)
$remoteFileWithUnixPath = "/latest/[b]test"
$removalResult =
$session.RemoveFiles($session.EscapeFileMask($remoteFileWithUnixPath))
if ($removalResult.IsSuccess)
{
Write-Host ("Removal of remote file {0} succeeded" -f $remoteFileWithUnixPath)
}
else
{
Write-Host ("Removal of remote file {0} failed" -f $remoteFileWithUnixPath)
}
}
finally
{
# Disconnect, clean up
$session.Dispose()
}
exit 0
}
catch [Exception]
{
Write-Host $_.Exception.Message
exit 1
}
Some methods of the WinSCP .NET assembly, including the the Session.RemoveFiles, accept a file mask, not a simple path.
Square brackets have special meaning in the file mask.
You should always use the RemotePath.EscapeFileMask method on file paths, before passing them to assembly methods.
I'd use the escape sequence in the commandline:
Remove-Item 'C:\Scripts\Test`[1`].txt'
Or use the -LiteralPath parameter.
Remove-Item -LiteralPath C:\scripts\test[1].txt
The -LiteralPath parameter does not try to convert the string.

Grant IIS 7.5 Application Pool Read Permission to Certificate Private Key by Using PowerShell

I searched all around and could not find a lot of information, basically I have Windows 2008 R2, I created PowerShell script to load a PFX file to certificate store of Local Machine already.
Now I need to grant permission of my app pool to read the private key of the certificate by using PowerShell.
In the old way Windows 2003, I just need to get the actual file sitting in C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\ folder, but it looks like Win 2008 uses a different folder.
Anybody has some solution?
-- Update my version of code --
function Grant-CertificatePermissions([string]$certSubject,[string]$user,[string]$permissionType,[string]$permission = $args[3])
{
$getCert = Get-LocalMachineCertificate $certSubject
$keypath = Get-CertificateStorePath
$certHash = $getCert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
$certFullPath = $keypath+$certHash
$certAcl = Get-Acl -Path $certFullPath
try
{
$accessRule=new-object System.Security.AccessControl.FileSystemAccessRule $user, $permissionType, $permission
$certAcl.AddAccessRule($accessRule)
}
catch [System.Exception]
{
throw "Invalid User Id Or Permission"
}
Set-Acl $certFullPath $certAcl
}
function Get-LocalMachineCertificate([string]$subject, [string]$certificateStoreLocation, [string]$certificateStoreName)
{
$getCert = Get-ChildItem -Recurse Cert:\$certificateStoreLocation\$certificateStoreName | Where-Object {$_.Subject -eq $subject}
if(!$getCert)
{
throw "Certificate Not Found"
}
return $getCert
}
function Get-CertificateStorePath
{
$commonCertPathStub = "\Microsoft\Crypto\RSA\MachineKeys\"
$programData = $Env:ProgramData
if(!$programData)
{
$programData = $Env:ALLUSERSPROFILE + "\Application Data"
}
$keypath = $programData + $commonCertPathStub
return $keypath
}
In my Get-CertificateStorePath function I get value as C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\, after I get certificate hash, the complete file looks like C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\d82829f7770ea5d85ef978dea67f302d_4cca7190-7e9f-46d7-b180-6656fec432e2, when I execute Get-Acl line I have exception Cannot find path 'C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\d82829f7770ea5d85ef978dea67f302d_4cca7190-7e9f-46d7-b180-6656fec432e2' because it does not exist..
I browsed that folder, I indeed could not find such a file.
-- Update --
function Import-PfxCertificate ([String]$certPath,[String]$certificateStoreLocation ,[String]$certificateStoreName, $pfxPassword)
{
$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
}
2008 R2 uses C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys
Via PowerShell you can see the certs available to IIS here:
cert:\LocalMachine\My
You can cd into that location and look for your cert. Once you find it you can view its private key ID using:
$cert = get-item 2779B37AE3625FD8D2F9596E285C7CDC15049D87
$cert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
This will contain the long hexadecimal filename from the MachineKeys folder.
You can then change the file permissions using the Set-Acl cmdlet.
You can also view the permissions via the Certificates MMC mmc/add snapin/certificates/computer account/local computer and then certificates/personal/certificates/[your cert]/all tasks/manage private keys
If you drag and drop a certificate from User Store\Personal to Computer Store\Personal, the $getCert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName value is wrong. There is no file with that value. Import the certificate again in the right store and it will work.
here is a complete powershell script to give permission to any user on certificate's private key.
How to Grant permission to user on Certificate private key using powershell?
Above link has script and example how to run it on powershell console window.
If you are using ApplicationPoolIdentity then you username will be 'IIS AppPool\AppPoolNameHere'
Note: You will need to use ' ' as there is a space between IIS and AppPool.