Automatically sign powershell script using Get-PfxCertificate - powershell

I have to sign remote scripts with a certificate from the remote machine from which I have a .pfx file.
I would like to automate the scripting by supplying the password to the Get-PfxCertificate programmatically.
So the question is:
Is it possible to somehow supply programmatically the required password to
Get-PfxCertificate?

$CertPath = "my.pfx"
$CertPass = "mypw"
$Cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($CertPath, $CertPass)
Set-AuthenticodeSignature -Certificate $Cert -TimeStampServer http://timestamp.verisign.com/scripts/timstamp.dll -FilePath $OutputFilename
Make sure you have the proper permissions otherwise you won't be able to create an instance of the X509Certificate2 object.

I did a bit of checking around on this and couldn't find a clean way to provide the password programmatically. I suspect it is meant to be this way for security reasons. Either that or the PowerShell development team just blew it by not including a Credential parameter for this cmdlet. The only other option I can think of is to use someting like SendKeys to send the individual password character key presses to the PowerShell console at the right time via a background job (blech - just threw up in my mouth a little). :-)

Another way of doing this is by loading your certificate directly from your certificate store using PS Providers. Use Get-PSProviders to determine available PSProviders on your machine.
Once you have cert provider loaded, you can now get the certificate using Get-ChildItem
Launch certmgr.msc from run to launch the certificate store
Assuming that your certificate is stored under Personal folder in your cert store and has "Company Name" set in the subject property of the certificate, and there is only certificate in that folder with Company Name in the subject - you can get the certificate like so
$my_cert = Get-ChildItem cert:\CurrentUser\My | ? {$_.Subject -match "Company Name"}
$my_cert will be your certificate object that you can pass directly to Set-AuthenticodeSignature cmdlet
Set-AuthenticodeSignature -Certificate $my_cert -FilePath fqn_to_dll.dll -Timestampserver "http://timestampurl"
post signing, you can retrieve the sign status by querying on the Status property for "Valid" or not like
$result = Set-AuthenticodeSignature -Certificate $my_cert -FilePath fqn_to_dll.dll -Timestampserver "http://timestampurl" | Select Status
if(-Not ($result -eq "Valid")){
Write-Output "Error Signing file: Status: $($result.Status)"
}

Related

Add certificates the same way the Certificate Import Wizard does [Powershell]

I have configured a powershell script, which creates a vpn conection profile.
To make it work i need to add proper certificate.
Everything works fine when i add a certificate manually to local machine:
More detailed regarding importing certificate manualy:
Info
I'm trying to perform this task via powershell, but it doesn't work (script seems to work, but i am not sure to which stores should i copy certificate). In contrary to manual method - the certificate added by my powershell script is invisible for vpn connection.
#add certificate
$cert_name=$env:USERNAME+"#vpn.contoso.com.p12"
$cert_loc="\\ad\deploy\other\certs\"+$cert_name
$secure_pwd = ConvertTo-SecureString "contoso987%#" -AsPlainText -Force
Import-PfxCertificate -FilePath $cert_loc -CertStoreLocation Cert:\LocalMachine\My -Password $secure_pwd
# Add vpn connection
Add-VpnConnection -Name "Example VPNX" -ServerAddress "vpn.example.com" -AuthenticationMethod "MachineCertificate" -TunnelType "IKEv2" -EncryptionLevel "Maximum" -SplitTunneling $True
I would like to do it the same way the certificate import wizard does. Does anyone have experience in that ?
PS
I've changed addresses in codes etc.
Kind Regards,
Tamara
I've decided to post the solution. Although it is not developed in powershell it solves the problem completely. It is possible to import these kind of certificates from command prompt:
certutil -f -p Some_password -importpfx "\\ad\somepath\certificate.p12"

Creating a PowerShell script to automatically renew user certificates

We use user certificates for authenticating to various services, but the certificates expire after a year unless renewed manually. I am attempting to create a logon script that will detect if the certificate is about to expire and renew it proactively.
The manual process we use currently is having the user log in, launching certmgr.msc, expanding Personal > Certificates, right-clicking the certificate, All Tasks > Renew Certificate with New Key (or Request New if it's already expired).
cd cert:\
$certs = Get-ChildItem -Recurse -ExpiringInDays 180 | Where subject -Like "*(foo)*"
if ($certs)
{
ForEach ($cert in $certs)
{
certreq -enroll -user -q -policyserver * $cert.thumbprint renew
}
}
I ran this successfully once, but I get the following error when I run the script:
Certificate Request Processor: The parameter is incorrect. 0x80070057
(WIN32: 87 ERROR_INVALID_PARAMATER)
I get the same result if enter garbage data or identify the certificate by serial number or thumbprint. The script is able to reliably find the certificate I want, but the certreq command is failing.
Any advice is greatly appreciated.
-cert parameter missing
Get-ChildItem cert:\ -Recurse -ExpiringInDays 180 | Where subject -Like "*(foo)*" | % {
certreq -enroll -user -q -policyserver * -cert $($_.thumbprint) renew
}

Visual studio team services deploymen/buildt certificate error

I am trying to build a click-once application using the Continuous integration and deployment feature in VSTS (Visual studio team services Online)We are trying to build this using the Hosted agent Visual studio 2015 We had difficulties signing the strong name key file with an error of
MSB3326: Cannot import the following key file: xxxx.snk. The key file may be password protected. To correct this, try to import the certificate again or import the certificate manually into the current user's personal certificate store.
And after that
MSB3321: Importing key file "xxxx.pfx" was canceled.
I have tried to both select from store and from file changed the location and made sure to commit but with no success.
Any ideas how i can overcome this errors or what am doing wrong.
Clerification on answer selected
Just wanted to make a clarification if anyone else has the same issue, in addition to the answer i had to place my certificate in my source control code and commit it. Then to select its location add a global variable on the VSTS Build
$cert.Import("$(CertPath)", $password, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]"PersistKeySet")
Where $(CertPath) would be something like $(Build.SourcesDirectory)\SharedSolutionFiles\CertificateName.pfx
You can create a PowerShell script and add a PowerShell Script step in your build definition to import the certificate file before the VSBuild step.
Build failed without PowerShell Import Certificate Step:
Build passed with PowerShell Import Certificate Step:
The PowerShell Script I used:
$pfxpath = 'pathtoees.pfx'
$password = 'password'
Add-Type -AssemblyName System.Security
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.Import($pfxpath, $password, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]"PersistKeySet")
$store = new-object system.security.cryptography.X509Certificates.X509Store -argumentlist "MY", CurrentUser
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]"ReadWrite")
$store.Add($cert)
$store.Close()
The better way is that you can setup a on premise build agent and import the certificate to certificate store, then change build agent service account to the same account.
Instead of using either an on premise build or loading the certificates on to the certificate stores on the build agent (which could be considered insecure) it is possible to overwrite the build task FileSign and construct one that uses a certificate file and password.
I have outlined the steps here:
https://stackoverflow.com/a/55313239/2068626
After failing to use methods in other answers, I found another way to use PowerShell script to import pfx certificate. my script was written for GitHub Actions but you can easily change the syntax to VSTS or Azure pipeline (using 'task: PowerShell#2' for azure pipeline for example). You may also like to update file path from github to your devops path. Also password can be replaced with a secured variable.
- name: Import certificate from the command-line
shell: pwsh
run: |
$Secure_String_Pwd = ConvertTo-SecureString "<password>" -AsPlainText -Force
Import-PfxCertificate -FilePath '${{github.workspace}}\<path>\project1_TemporaryKey.pfx' -CertStoreLocation Cert:\CurrentUser\My -Password $Secure_String_Pwd
Then build your Visual Studio ClickOnce project. The error should be gone.
After the build, you may like to remove the certificate from the machine. Here is a PowerShell example provided by Microsoft:
https://learn.microsoft.com/en-us/answers/questions/360772/powershell-commands-to-delete-personal-certificate.html
$users = "user1","user2","user3","user4","user5"
Get-ChildItem Cert:\CurrentUser\My | ForEach-Object {
$ifkeep = $false
foreach($user in $users){
if($_.Subject -match $user){
$ifkeep = $true
break
}
}
if($ifkeep -eq $false){
Remove-Item $_
}
}

Export-PfxCertificate Not Protecting Private Key

I am trying to export a certificate public and private key to a PFX file via a powershell script. I am currently using the following code
Get-ChildItem -Path Cert:\CurrentUser\My\$Thumbprint | Export-PfxCertificate -FilePath $OutputFile -Password $privateKeyPass -ChainOption EndEntityCertOnly
However, when I work with the resulting PFX file in something like certutil, it doesn't ask for a private key password. For example here is an example of what I get when i dump the cert with certutil:
> certutil -dump cert.pfx
Certificates: Not Encrypted
================ Certificate 0 ================
[cert data removed]
---------------- End Nesting Level 1 ----------------
Key Container = PfxContainer
Provider = PfxProvider
Encryption test FAILED
CertUtil: -dump command completed successfully.
If I use the certificates MMC snapin to export the cert I can select the "Enable certificate privacy" option and it will export an encrypted certificate.
My question is...
Is there a way to tell the export-pfxcertificate cmdlet to enable certificate privacy so that it is encypted? If not, what other solution do I have?

Powershell Script to Install Certificate Into Active Directory Store

I'm trying to write a powershell script to install a certificate into the active directory certificate store,
Here are the steps to do this manually, any help would be greatly appreciated.
On a Windows 2008R2 domain controller,
Click Start -> Run
type MMC
click ok
Click File -> Add/Remove Snap-In
Select "Certificates" -> Add
Select "Service Account"
Click Next
Select "Local Computer"
Click Next
Select "Active Directory Domain Services"
Click Finish
Click Ok
I want the script to install the certificate into :
NTDS\Personal
I would post an image but I don't have enough "reputation" apparently, so I can only provide text instructions.
So basically what I've tried is, I've used this powershell function below to import a certificate into the Local Machine -> Personal Store, which is where most certificates go, and the code works.
But I need to install the certificate into the "NTDS\Personal" store on a domain controller, but the $certRootStore only accepts localmachine or CurrentUser, so I'm stuck : /
function Import-PfxCertificate
{
param
(
[String]$certPath,
[String]$certRootStore = "localmachine",
[String]$certStore = "My",
$pfxPass = $null
)
$pfx = new-object System.Security.Cryptography.X509Certificates.X509Certificate2
if ($pfxPass -eq $null)
{
$pfxPass = read-host "Password" -assecurestring
}
$pfx.import($certPath,$pfxPass,"Exportable,PersistKeySet")
$store = new-object System.Security.Cryptography.X509Certificates.X509Store($certStore,$certRootStore)
$store.open("MaxAllowed")
$store.add($pfx)
$store.close()
}
Import-PfxCertificate -certPath "d:\Certificate.pfx"
Regards Alex
Using a combination of what you already had above and the registry keys for the two certificate stores this works.
The only other thing is that I don't know how NTDS determines which certificate to use when there are multiple in the certificate store.
function Import-NTDSCertificate {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$PFXFile,
[Parameter(Mandatory)]
[string]$PFXPassword,
#Remove certificate from LocalMachine\Personal certificate store
[switch]$Cleanup
)
begin{
Write-Verbose -Message "Importing PFX file."
$PFXObject = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2
$PFXObject.Import($PFXFile,$PFXPassword,[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)
$thumbprint = $PFXObject.Thumbprint
}
process{
Write-Verbose -Message "Importing certificate into LocalMachine\Personal"
$certificateStore = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store('My','LocalMachine')
$certificateStore.Open('MaxAllowed')
$certificateStore.Add($PFXObject)
$certificateStore.Close()
Write-Verbose -Message "Copying certificate from LocalMachine\Personal to NTDS\Personal"
$copyParameters = #{
'Path' = "HKLM:\Software\Microsoft\SystemCertificates\MY\Certificates\$thumbprint"
'Destination' = "HKLM:\SOFTWARE\Microsoft\Cryptography\Services\NTDS\SystemCertificates\My\Certificates\$thumbprint"
'Recurse' = $true
}
Copy-Item #copyParameters
}
end{
if ($Cleanup){
Write-Verbose -Message "Removing certificate from LocalMachine\Personal"
$removalParameters = #{
'Path' = "HKLM:\SOFTWARE\Microsoft\SystemCertificates\MY\Certificates\$thumbprint"
'Recurse' = $true
}
Remove-Item #removalParameters
}
}
}
Alright, first the bad news. The only managed certificate stores are LocalMachine and CurrentUser, as we have all seen in powershell.
Now, the not so bad news. We know that the 'physical' location store (physical is MS' word, not mine) exists in the registry on the ADDS server, HKLM\Software\Microsoft\Cryptography\Services\NTDS\SystemCertificates. This was dually verified by both
Using procmon while importing a certificate into the store using the mmc snap-in
Scavenging msdn for this nugget
The link in #2 shows that all physical stores for services are stored in the path mentioned above, substituting NTDS for . The real service name, not the display name.
However,
Because of the bad news. Trying to map it in powershell with that reg key as the root and -PSProvider Certificate will prove disappointing, it was the first thing I tried.
What one can try, is using the X509Store constructor that takes an IntPtr to a SystemStore, as described here. Yes, that invovles some unmanaged code, and mixing the two is something I do rarely, but this and googling for HCERTSTORE C# should get you there.
Even though this post is years old, it is still helpful and turns up in searches, so to address the question of "I don't know how NTDS determines which certificate to use when there are multiple in the certificate store", the answer is that you will get unreliable results when there are two or more valid certificates installed that meet the requested criteria so it is recommended to remove the old/unneeded certificate(s) and just leave the newest/best one for the server auth.