Signer un script PowerShell avec un certificat auto-signé

I. Présentation

Dans ce tutoriel, nous allons apprendre à signer un script PowerShell à partir d'un certificat auto-signé : une méthode facile à mettre en œuvre et qui permet de s'assurer que personne n'a effectué de modification dans votre script. En effet, si le script est modifié, alors il refusera de s'exécuter, car la signature ne sera plus correcte.

Précédemment, nous avons vu comment signer un script PowerShell à partir d'un certificat obtenu auprès d'une autorité de certification (ADCS). Je vous recommande cette méthode pour la production, car le certificat utilisé sera automatiquement reconnu par les serveurs et postes de travail de votre système d'information. A contrario, le script auto-signé doit être déployé manuellement sur les machines, et même s'il peut fonctionner en production et qu'il apporte un minimum de sécurité, il est plutôt recommandé pour les environnements de tests.

Au final, pour signer un script PowerShell, il y a trois manières d'obtenir un certificat :

  • Un certificat acheté auprès d'une autorité de certification reconnue comme DigiCert ou GeoTrust, mais cela implique des coûts financiers de plusieurs centaines d'euros. Si votre objectif est de signer vos scripts PowerShell pour les utiliser dans votre entreprise, ce n'est probablement pas la bonne solution. Pour une distribution plus large, pourquoi pas.
  • Un certificat généré auprès d'une autorité de certification d'entreprise, comme l'ADCS liée à l'Active Directory
  • Un certificat auto-signé

Cette signature numérique va permettre de signer le code et d'assurer son intégrité : s'il est modifié, il faudra le signer de nouveau, sinon il ne pourra pas s'exécuter.

Pour bien comprendre l'intérêt de la signature de scripts PowerShell, je vous recommande aussi de lire cet article :

II. New-SelfSignedCertificate : générer un certificat auto-signé

Pour générer un certificat auto-signé sous Windows, nous pouvons utiliser le cmdlet New-SelfSignedCertificate. Ce certificat peut être stocké dans le magasin personnel de l'utilisateur ou de l'ordinateur (disponibilité globale sur la machine). Nous allons le stocker dans le magasin de l'ordinateur, ce qui correspond au chemin suivant : "Cert:\LocalMachine\My".

Voici la commande à utiliser pour générer un certificat auto-signé :

# 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)

Les informations sur le certificat sont stockées dans la variable $SelfSignedCert, car nous allons importer ce certificat au sein de d'autres magasins de notre machine. Sinon, voici quelques informations supplémentaires sur les paramètres :

  • -Subject : nom du certificat tel qu'il apparaîtra dans la console de gestion des certificats
  • -Type CodeSigningCert : le type de certificat, donc ici un certificat pour la signature de code
  • -CertStoreLocation : le magasin dans lequel stocker le certificat, ici le magasin personnel de l'ordinateur
  • -FriendlyName : le nom familial du certificat
  • -NotAfter (Get-Date).AddYears(5) : validité de 5 ans pour le certificat, sinon c'est 1 an par défaut

Vous pouvez lister les certificats de type "CodeSigningCert" présents dans le magasin personnel de votre ordinateur avec cette commande :

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

Vérifier la présence du certificat de signature de code

Logiquement, le certificat fraîchement créé doit apparaître dans la liste des résultats.

Pour que la signature numérique soit reconnue, ou plutôt que toute la chaîne soit reconnue, nous devons ajouter ce certificat dans deux autres magasins : "Autorités de certification racine de confiance" et "Éditeurs approuvés". Sinon, le script ne pourra pas s'exécuter avec la politique d'exécution "AllSigned".

Toujours en PowerShell, cette opération s'effectue avec les lignes suivantes :

# 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()

On aurait pu utiliser la commande Import-Certificate, mais cela implique de l'exporter au format CER au préalable. Si vous ouvrez une console MMC, que vous ajoutez le composant "Certificats" pour l'ordinateur local, vous verrez le certificat à trois emplacements comme sur l'exemple ci-dessous.

Script PowerShell - Certificat auto-signé - Console MMC

III. Set-AuthenticodeSignature : signer le script PowerShell

Le cmdlet Set-AuthenticodeSignature permet de signer des scripts PowerShell, peu importe la nature du certificat. Commençons par stocker le chemin du script à signer dans une variable :

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

Pour information, ce script contient une seule ligne qui écrit une phrase dans la console :

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

Ensuite, la variable $PathCertToUse va contenir le chemin vers le certificat, au sein du magasin personnel de l'ordinateur. Dans cette commande, vous devez adapter le nom du certificat à moins que vous utilisiez le même que moi ("ScriptPowerShell").

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

Cette variable contiendra la valeur suivante (si ce n'est que l'empreinte sera différente chez vous) :

Cert:\LocalMachine\My\CF5F933794EE29B1E4B9FDE63D30E8131915571D

Maintenant, il ne reste plus qu'à signer le script, à condition que le chemin vers le script soit valide, tout comme le chemin vers le certificat, d'où la condition "if" dans le code ci-dessous. Si les deux chemins sont valides, on signe le script à l'aide des informations du certificat stockées dans la variable $DataCertToUse.

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 "http://timestamp.comodoca.com/authenticode"
}

Le script est bien signé, avec le statut "Valid" grâce au fait que l'on ait importé le certificat dans "Autorités de certification racines de confiance" et "Éditeurs approuvés". Sinon, nous aurions eu le statut "UnknownError" (qui n'empêche pas le script d'être signé).

Signer un script PowerShell - Certificat auto-signé

Voici quelques informations supplémentaires sur la commande Set-AuthenticodeSignature :

  • -Certificate : informations du certificat de signature du code
  • -FilePath : chemin vers le fichier PS1 de notre script PowerShell
  • -TimestampServer : On s'appuie sur un service externe qui va permettre de dater notre signature, ainsi, lorsque le certificat sera expiré la signature restera valable, car elle fût réalisée pendant que le certificat était valide. Autre valeur possible : "http://timestamp.digicert.com/"

Si l'on regarde le contenu du script PowerShell, on peut voir qu'il y a un nouveau bloc nommé "SIG" correspondant à la signature numérique :

Script PowerShell - Signature intégrée dans le script

Note : si vous supprimez le bloc SIG de votre script, la signature est retirée et le script redevient non signé.

Dans les propriétés du fichier, l'onglet "Signatures numériques" donne des informations sur la signature :

Script PowerShell - Signature numérique

Avec PowerShell et le cmdlet Get-AuthenticodeSignature, nous pouvons également obtenir des informations sur la signature :

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

Le script est signé, nous allons essayer de l'exécuter.

IV. Exécuter un script PowerShell signé

Pour être certain que notre machine autorise uniquement l'exécution des scripts signés, nous pouvons modifier la politique d'exécution sur "AllSigned", comme ceci :

Set-ExecutionPolicy AllSigned

Suite à cette modification, la machine locale autorisera uniquement l'exécution des scripts signés. Si j'utilise cette politique d'exécution et que j'exécute "Script.ps1" avant qu'il soit signé, nous pouvons constater que l'exécution est bloquée :

Politique AllSigned - Exécution du script avant la signature

Par contre, suite à la signature effectuée via notre certificat, cela fonctionne !

Politique AllSigned - Exécution du script après signature

Attention, si vous utilisez ce script signé sur un autre machine, la signature numérique ne sera pas reconnu si le certificat n'est pas importé (ce qui impose des manipulations supplémentaires) ! C'est une contrainte de l'utilisation d'un certificat auto-signé.

Dans la pratique, à partir du serveur qui contient le script, nous pouvons exporter le certificat (pour générer le fichier C:\TEMP\ScriptPowerShell.cer) :

$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

Ensuite, le fichier "ScriptPowerShell.cer" doit être copié sur l'hôte distant. Une fois que c'est fait, dans "C:\TEMP\", par exemple, il faut l'importer dans les trois emplacements :

# 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"

Une fois que c'est fait, il sera possible d'exécuter le script signé, car la signature peut maintenant être reconnue par cette machine. Par ailleurs, vous devez garder à l'esprit qu'à chaque modification du script, il faudra le signer à nouveau avec votre certificat autosigné.

V. Conclusion

Nous venons de voir comment signer et exécuter un script PowerShell sur une machine Windows à partir d'un certificat auto-signé ! Gardez à l'esprit que vous devez vérifier le contenu des scripts PowerShell téléchargés à partir d'Internet, que ce soit un script signé ou non.

Maintenant, c'est à vous de vous exercer ! 🙂

Partagez cet article Partager sur Twitter Partager sur Facebook Partager sur Linkedin Partager sur Google+ Envoyer par mail

Florian Burnel

Ingénieur système et réseau, cofondateur d'IT-Connect et Microsoft MVP "Cloud and Datacenter Management". Je souhaite partager mon expérience et mes découvertes au travers de mes articles. Généraliste avec une attirance particulière pour les solutions Microsoft et le scripting. Bonne lecture.

florian has 4076 posts and counting.See all posts by florian

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.