Private/Wissen/C_Advance/C08_ScripteSignieren.ps1

<#
 
# Scriptdateien signieren
 
Aufbau einer PKI, erzeugen von Zertifikaten und Skripte signieren und testen
 
- **Hashtags** Zertifikat CodeSigningCert ExecutionPolicy
- **Version** 2020.08.27
 
#>


# ! WARUM SIGNIEREN ?

# ! 1. Eine Signatur schützt ein Skript (ps1, psm1, .format.ps1xml, .type.ps1xml, ...) vor Manipulation!

# ! 2. Der Signierer bestätigt mit seiner Signatur den endgültigen Status des Skriptes!

# ! 3. Dritte können erkennen wer dieses Skripte signiert / erstellt hat.

# ! 4. Die PowerShell kann über die Ausführungsrichtlinien so konfiguriert werden (AllSigned) dass nur Skripte ausgeführt werden, wenn folgende Voraussetzungen erfüllt sind:

# ! (A) Die Skriptdatei wurde mit einem X.509-Zertifikat signiert!

# ! (B) Der Zeitpunkt des Signier-Vorgangs in einem gültigen Bereich lag.

# ! (C) Die signierte Datei wurde anschließend nicht manipuliert!

# ! (D) Das Signierer-Zertifikat stammt von einer "Vertrauenswürdigen Stammzertifizierungsstelle" (Root) ab ODER es ist selbst in Root abgelegt worden!

# ! (E) Signierer-Zertifikat wurde zusätzlich im Zertifikatspeicher für "Vertrauenswürdige Herausgeber" (TrustPublisher) abgelegt!

#region Einfache Umsetzung über ein selbstsigniertes Zertifikat

# ! 1. Testumgebung vorbereiten:
Set-ExecutionPolicy -ExecutionPolicy 'AllSigned' -Scope 'Process'      -Force
Set-ExecutionPolicy -ExecutionPolicy 'AllSigned' -Scope 'CurrentUser'  -Force
Set-ExecutionPolicy -ExecutionPolicy 'AllSigned' -Scope 'LocalMachine' -Force
Get-ExecutionPolicy -List
New-Item -Path C:\ -Name Temp -ItemType Directory -Force | Select-Object -ExpandProperty FullName | Set-Location
CertLM.msc  # Zertifikate - Lokaler Computer
CertMgr.msc # Zertifikate - Aktueller Benutzer

# ! 2. Problem erkennen, s. Fehlermeldung: (LÖSUNG => Sginieren !)
"' > > > Hallo Welt! < < < ' | Write-Host -ForegroundColor Red -BackgroundColor Yellow" | Set-Content -Path '.\Test.ps1' -Force
. '.\Test.ps1'

# ! 3. Mögliche Zertifikate die zum Signieren geeignet wären ermitteln, um evtl. das Erstellen eines eigenen Signierer-Zertifikates zu überspringen:
Get-ChildItem -Path 'Cert:\CurrentUser\My' -CodeSigningCert

# ! 4. Mein Signierer-Zertifikate erstellen:
$params = @{
    Subject           = 'CN=_FirstName_LastName (PS Developer), E=v.nachname@abc.local'
    HashAlgorithm     = 'SHA512'
    KeyExportPolicy   = [Microsoft.CertificateServices.Commands.KeyExportPolicy]::ExportableEncrypted # ! GEFAHR !
    CertStoreLocation = 'Cert:\CurrentUser\My'
    Type              = [Microsoft.CertificateServices.Commands.CertificateType]::CodeSigningCert
    NotAfter          = (Get-Date).AddYears(5)
}
$myPfxCert = New-SelfSignedCertificate @params

# ! 5. Signierer-Zertifikate mit dem privaten Schlüssel exportieren
$myPfxCertPassword = Read-Host -Prompt 'Das Passwort zum schützen des privaten Schlüssels angeben' -AsSecureString
$myPfxCert | Export-PfxCertificate -Password $myPfxCertPassword -FilePath '.\MyCodeSigningCert.pfx'

# ! 6. Das eben erstellte Signierer-Zertifikate rückstandlos aus allen Zertifikatsspeichern wieder löschen:
Get-ChildItem -Path 'Cert:\' -Recurse | Where-Object -FilterScript { $_ -is [System.Security.Cryptography.X509Certificates.X509Certificate2] -and $_.Thumbprint -CEQ $myPfxCert.Thumbprint } | Remove-Item

# ! 7. Aus dem Signierer-Zertifikate ein öffentliches Zertifikat erstellen:
Get-PfxCertificate -FilePath '.\MyCodeSigningCert.pfx' | Export-Certificate -FilePath '.\PublicSignerCertificate.cer' -Type 'CERT' -Force

# ! 8. Vertrauensstellung einrichten:
# ! Das öffentliche Zertifikat ist z.Zt. nicht vertrauenswürdig, daher muss dies ...
# ! ... 1. im Zertifikatsspeicher Root abgelegt werden, um dem Zertifikat generell zu vertrauen und ...
# ! ... 2. im Zertifikatsspeicher TrustedPublisher abgelegt werden, um den signierten .PS1-Dateien im besonderen zu vertrauen.
# ! Beide Schritte können auch per GPO oder Remote erfolgen.
# ? 8.1. Zertifikat im Root-Store (Vertrauenswürdigen Stammzertifizierungsstelle) speichern
Import-Certificate -FilePath '.\PublicSignerCertificate.cer' -CertStoreLocation 'Cert:\LocalMachine\Root'
# ? 8.2. Zertifikat im TrustedPublisher-Store (Vertrauenswürdige Herausgeber) speichern
Import-Certificate -FilePath '.\PublicSignerCertificate.cer' -CertStoreLocation 'Cert:\LocalMachine\TrustedPublisher'

# ! 9. Aktueller Stand der Dinge:
Get-ChildItem -Path .\Test.ps1, ` # ? Die zu signierende .PS1-Datei
                    .\MyCodeSigningCert.pfx, ` # ? Das Signierer-Zertifikate mit dem geschützten privaten Schlüssel
                    .\PublicSignerCertificate.cer # ? Öffentliches Signierer-Zertifikate zum bestätigen von Signaturen

$PublicSignerCertificate = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new("$(Get-Location)\PublicSignerCertificate.cer")

# ? Das Öffentliches Signierer-Zertifikate liegt im Zertifikatsspeicher Root, um dem Zertifikat generell zu vertrauen.
Get-ChildItem -Path 'Cert:\*\Root' -Recurse | Where-Object Thumbprint -CEQ $PublicSignerCertificate.Thumbprint

# ? Das Öffentliches Signierer-Zertifikate liegt im Zertifikatsspeicher TrustedPublisher, um den signierten .PS1-Dateien im besonderen zu vertrauen.
Get-ChildItem -Path 'Cert:\*\TrustedPublisher' -Recurse | Where-Object Thumbprint -CEQ $PublicSignerCertificate.Thumbprint

# ! 10. Eichrichtung abgeschlossen, es folgt nun das Signieren:
$cert = Get-PfxCertificate -FilePath 'C:\temp\MyCodeSigningCert.pfx'

Set-AuthenticodeSignature -FilePath 'C:\temp\test.ps1' -Certificate $cert -HashAlgorithm SHA512 -TimestampServer http://timestamp.digicert.com # ! <= OPTIONAL um die Gültigkeitsdauer zu verifizieren

# ! 11. Signieren abgeschlossen, es folgt nun das Testen:

 # ? Wird .PS1-Datei nun problemlos ausgeführt?
. '.\Test.ps1'

# ? Für Testzwecke einmal den Dateiinhalt manipulieren:
Start-Process -FilePath '.\Test.ps1'

 # ? Wird .PS1-Datei nun NICHT mehr ausgeführt?
. '.\Test.ps1'

# ? Die digitale Signatur sollte UNGÜLTIG sein?
Get-AuthenticodeSignature -FilePath '.\Test.ps1' | Format-List -Property *

# ? Die Manipulation der der .PS1-Datei wieder rückgängig machen:
Start-Process -FilePath '.\Test.ps1'

 # ? Wird .PS1-Datei nun problemlos ausgeführt?
 . '.\Test.ps1'

 # ? Die digitale Signatur sollte GÜLTIG sein?
Get-AuthenticodeSignature -FilePath '.\Test.ps1' | Format-List -Property *

# ! 12. Die Tests sind abgeschlossen, es folgt nun das Aufräumen:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process      -Force
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser  -Force
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine -Force
Get-ExecutionPolicy -List

# TIPP:
Get-ChildItem ..\AKPT\ -Force -Recurse | Unblock-File

$PublicSignerCertificate = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new("$(Get-Location)\PublicSignerCertificate.cer")
Get-ChildItem -Path Cert:\LocalMachine\Root, Cert:\LocalMachine\TrustedPublisher | Where-Object Thumbprint -CEQ $PublicSignerCertificate.Thumbprint | Remove-Item
Get-ChildItem -Path .\Test.ps1, .\MyCodeSigningCert.pfx, .\PublicSignerCertificate.cer | Remove-Item

#endregion