Signer un script PowerShell avec un certificat auto-signé

Sign a PowerShell script with a self-signed certificate

I. Presentation

In this tutorial, we will learn how to sign a PowerShell script from a self-signed certificate: an easy-to-implement method that ensures that no one has made any changes to your script. Indeed, if the script is modified, then it will refuse to run, because the signature will no longer be correct.

Previously, we saw how to sign a PowerShell script from a certificate obtained from a certification authority (ADCS). I recommend this method for production, because the certificate used will be automatically recognized by the servers and workstations of your information system. Conversely, the self-signed script must be deployed manually on the machines, and even if it can work in production and provides a minimum of security, it is rather recommended for test environments.

In the end, to sign a PowerShell script, there are three ways to obtain a certificate:

  • A certificate purchased from a recognized certificate authority such as DigiCert or GeoTrust, but this involves financial costs of several hundred euros. If your goal is to sign your PowerShell scripts for use in your business, this is probably not the right solution. For a wider distribution, why not.
  • A certificate generated from an enterprise CAsuch as ADCS linked to the Active Directory
  • A self-signed certificate

This digital signature will make it possible to sign the code and ensure its integrity: if it is modified, it will have to be signed again, otherwise it will not be able to run.

To fully understand the value of signing PowerShell scripts, I also recommend that you read this article:

II. New-SelfSignedCertificate: generate a self-signed certificate

To generate a self-signed certificate on Windows, we can use the cmdlet New-SelfSignedCertificate. This certificate can be stored in the personal store of the user or the computer (global availability on the machine). We will store it in the computer store, which corresponds to the following path: “Cert:\LocalMachine\My“.

Here is the command to use to generate a self-signed certificate:

# Création d'un certificat auto-signé
$SelfSignedCert = New-SelfSignedCertificate -Subject ScriptPowerShell -Type CodeSigningCert -CertStoreLocation Cert:\LocalMachine\My -FriendlyName "Signer scripts PowerShell" -NotAfter (Get-Date).AddYears(5)

The certificate information is stored in the $SelfSignedCert variable, as we will be importing this certificate into other stores on our machine. Otherwise, here is some additional information about the settings:

  • -Subject : name of the certificate as it will appear in the certificate management console
  • -Type CodeSigningCert : the type of certificate, so here a certificate for code signing
  • -CertStoreLocation : the store in which to store the certificate, here the personal store of the computer
  • -FriendlyName : the family name of the certificate
  • -NotAfter (Get-Date).AddYears(5) : validity of 5 years for the certificate, otherwise it is 1 year by default

You can list the “CodeSigningCert” type certificates present in the personal store of your computer with this command:

Get-ChildItem -Path Cert:\LocalMachine\My -CodeSigningCert

Verify the presence of the code signing certificate

Logically, the newly created certificate should appear in the list of results.

For the digital signature to be recognized, or rather the whole chain to be recognized, we need to add this certificate in two other stores: “Trusted Root Certification Authorities” et “Trusted publishers“. Otherwise, the script will not be able to run with the “AllSigned” execution policy.

Still in PowerShell, this operation is performed with the following lines:

# Ajouter le certificat dans "Autorités de certification racine de confiance"
# Créer un objet pour représenter le magasin de certificat LocalMachine\Root
$rootStore = [System.Security.Cryptography.X509Certificates.X509Store]::new("Root","LocalMachine")
# Ouvrir le magasin en lecture et écriture 
$rootStore.Open("ReadWrite")
# Ajouter le certificat dans le magasin, grâce au contenu de la variable $SelfSignedCert
$rootStore.Add($SelfSignedCert)
# Fermer le magasin de certificats
$rootStore.Close()

# Ajouter le certificat dans "Éditeurs approuvés"
# Créer un objet pour représenter le magasin de certificat LocalMachine\TrustedPublisher
$publisherStore = [System.Security.Cryptography.X509Certificates.X509Store]::new("TrustedPublisher","LocalMachine")
# Ouvrir le magasin en lecture et écriture 
$publisherStore.Open("ReadWrite")
# Ajouter le certificat dans le magasin, grâce au contenu de la variable $SelfSignedCert
$publisherStore.Add($SelfSignedCert)
# Fermer le magasin de certificats
$publisherStore.Close()

We could have used the command Import-Certificate, but that requires exporting it in CER format beforehand. If you open an MMC console, you add the component “Certificates” for the local computer, you will see the certificate in three places as in the example below.

Script PowerShell - Certificat auto-signé - Console MMC

III. Set-AuthenticodeSignature : signer le script PowerShell

Le cmdlet Set-AuthenticodeSignature allows signing PowerShell scripts, regardless of the nature of the certificate. Let’s start by storing the path to the script to sign in a variable:

$PathScriptToSign = "C:\TEMP\Script.ps1"

For information, this script contains a single line that writes a sentence to the console:

Write-Host "Ce script est exécuté sur la machine $($env:COMPUTERNAME)"

Then, the $PathCertToUse variable will contain the path to the certificate, within the computer’s personal store. In this command you have to adapt the name of the certificate unless you use the same as me (“ScriptPowerShell“).

$PathCertToUse = "Cert:\LocalMachine\My\" + (Get-ChildItem -Path Cert:\LocalMachine\My -CodeSigningCert | Where{ $_.Subject -eq "CN=ScriptPowerShell" }).Thumbprint

This variable will contain the following value (except that the fingerprint will be different for you):

Cert:\LocalMachine\My\CF5F933794EE29B1E4B9FDE63D30E8131915571D

Now all that remains is to sign the script, provided that the path to the script is valid, as is the path to the certificate, hence the “if” condition in the code below. If both paths are valid, the script is signed using the certificate information stored in the $DataCertToUse variable.

if((Test-Path $PathScriptToSign) -and ($PathCertToUse -ne "Cert:\CurrentUser\My\")){
  Write-Host "Le script $PathScriptToSign va être signé avec le certificat ($PathCertToUse)"
  $DataCertToUse = Get-Item -Path $PathCertToUse
  Set-AuthenticodeSignature -FilePath $PathScriptToSign -Certificate $DataCertToUse -TimestampServer "
}

The script is well signed, with the status “Valid” thanks to the fact that we imported the certificate in “Trusted Root Certification Authorities” et “Trusted publishers“. Otherwise, we would have had the status “UnknownError” (which does not prevent the script from being signed).

Sign a PowerShell script - Self-signed certificate

Here is some additional information about the order Set-AuthenticodeSignature :

  • -Certificate : code signing certificate information
  • -FilePath : path to the PS1 file of our PowerShell script
  • -TimestampServer : We rely on an external service that will allow us to date our signature, so when the certificate has expired the signature will remain valid, because it was made while the certificate was valid. Another possible value: “http://timestamp.digicert.com/

If we look at the contents of the PowerShell script, we can see that there is a new block named “SIG” corresponding to the digital signature :

PowerShell script - Embedded signature in script

Note : if you remove the SIG block from your script, the signature is removed and the script becomes unsigned again.

In the file properties, the “Digital signatures” gives information about the signature:

PowerShell Script - Digital Signature

With PowerShell and the cmdlet Get-AuthenticodeSignaturewe can also get information about the signature:

Get-AuthenticodeSignature -FilePath "C:\TEMP\Script.ps1" | fl

The script is signed, we will try to run it.

IV. Run a signed PowerShell script

To be sure that our machine only allows the execution of signed scripts, we can change the execution policy to “AllSigned”, like this:

Set-ExecutionPolicy AllSigned

Following this change, the local machine will only allow execution of signed scripts. If I use this execution policy and execute “Script.ps1” before it is signed, we can see that the execution is blocked:

AllSigned policy - Running script before signing

On the other hand, following the signature carried out via our certificate, it works!

AllSigned policy - Script execution after signing

Warning, if you use this signed script on another machine, the digital signature will not be recognized if the certificate is not imported (which requires additional manipulations)! This is a constraint of using a self-signed certificate.

In practice, from the server that contains the script, we can export the certificate (to generate the C:\TEMP\ScriptPowerShell.cer file):

$PathCertToUse = "Cert:\LocalMachine\My\" + (Get-ChildItem -Path Cert:\LocalMachine\My -CodeSigningCert | Where{ $_.Subject -eq "CN=ScriptPowerShell" }).Thumbprint
Export-Certificate -Cert $PathCertToUse -Type CERT -FilePath C:\Temp\ScriptPowerShell.cer

Then the “ScriptPowerShell.cer” file should be copied to the remote host. Once it’s done, in “C:\TEMP\”, for example, it must be imported into the three locations:

# Importer le certificat dans le magasin personnel de l'ordinateur local
Set-Location Cert:\LocalMachine\My\
Import-Certificate -FilePath "C:\temp\ScriptPowerShell.cer"

# Importer le certificat dans "Autorités de certification racine de confiance"
Set-Location Cert:\LocalMachine\Root\
Import-Certificate -FilePath "C:\temp\ScriptPowerShell.cer"

# Importer le certificat dans "Éditeurs approuvés"
Set-Location Cert:\LocalMachine\TrustedPublisher\
Import-Certificate -FilePath "C:\temp\ScriptPowerShell.cer"

Once this is done, it will be possible to run the signed script, as the signature can now be recognized by this machine. Also, you should keep in mind thateach time the script is modified, you will have to sign it again with your self-signed certificate.

V. Conclusion

We have just seen how to sign and run a PowerShell script on a Windows machine from a self-signed certificate! Keep in mind that you should verify the contents of PowerShell scripts downloaded from the Internet, whether it is a signed script or not.

Now it’s your turn to practice! 🙂

Powershell,Script,

#Sign #PowerShell #script #selfsigned #certificate

Leave a Comment

Your email address will not be published. Required fields are marked *