Private/Wissen/C08_ScripteSignieren.ps1
# ? TITEL Scriptdateien signieren # ? DESCRIPTION Aufbau einer PKI, erzeugen von Zertifikaten und Skripte signieren und testen # ? TAGS Zertifikat CodeSigningCert ExecutionPolicy # ? VERSION 2020.01.10 # ! WARUM? # ! a) Schutz gegen Manipulation von PS-Scriptdateien (ps1, psm1, ps1xml, ...) # ! b) Produktiv die Ausführungsrichtlinien auf "AllSigned" setzen, d.h. Skriptdateien werden nur noch ausgeführt, wenn: # ! 1. Skriptdatei X.509 signiert ist # ! 2. das X.509-Zertifikat von einer "Vertrauenswürdigen Stammzertifizierungsstelle" (Root) abstammt ODER # ! es ist selbst in Root abgelegt! # ! 3. das X.509-Zertifikat im Zertifikatspeicher für "Vertrauenswürdige Herausgeber" (TrustPublisher) enthalten ist #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 Set-Location -Path C:\Temp # ! 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 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. Import-Certificate -FilePath .\PublicSignerCertificate.cer -CertStoreLocation Cert:\LocalMachine\Root # ! 2. Zertifikat im Root-Store speichern Import-Certificate -FilePath .\PublicSignerCertificate.cer -CertStoreLocation Cert:\LocalMachine\TrustedPublisher # ! 3. Zertifikat im TrustedPublisher-Store speichern # ! 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-ChildItem -Path .\*.pfx | Out-GridView -Title "Zertifikat wählen" -OutputMode Single | Get-PfxCertificate Get-ChildItem -Path . | Where-Object Extension -In '.PS1', '.PSM1' | Out-GridView -Title "Zu signierende .PS1 oder PSM1-Datei(en) auswählen" -OutputMode Multiple | Set-AuthenticodeSignature -Certificate $cert ` -HashAlgorithm SHA512 ` -TimestampServer http://timestamp.globalsign.com/scripts/timstamp.dll # ! <= 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 $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 #region Komplexe Umsetzung über einen Mini-PKI # # 0. VORBEREITUNG # $CAName = "_PowerShell Root CA TEMP" # Name der Zertifizierungsstelle $PSDeveloperName = "_VORNAME NACHNAME (PS Developer)" # Name des PS-Signierer $PS1Name = "Get-EmptyDirectory.ps1" # Die zu signierende Datei Set-Location C:\Temp # Akt. Arbeitsverzeichnis festlegen Test-Path -Path ".\$Ps1Name" # Vorhanden? Set-ExecutionPolicy -ExecutionPolicy AllSigned -Scope Process -Force # Auch per GPO möglich. Set-ExecutionPolicy -ExecutionPolicy AllSigned -Scope CurrentUser -Force # Auch per GPO möglich. Set-ExecutionPolicy -ExecutionPolicy AllSigned -Scope LocalMachine -Force # Auch per GPO möglich. Get-ExecutionPolicy -List # Check? # Problem ... & "C:\temp\$PS1Name" #Lösung ... Lösung => Signieren! # 1. Vielleicht besitzen Sie schon so ein x.509 Zertifikat mit Verwendungszweck "Codesigning" inkl. des "Private Key"? Get-ChildItem -Path Cert:\CurrentUser\My -CodeSigningCert # ... Wenn vorhanden dan weiter mit Punkt 5. # 2. ROOT-CA erstellen $CACert = New-SelfSignedCertificate -Subject "CN=$CAName" ` -HashAlgorithm SHA512 ` -NotAfter (Get-Date).AddYears(10) ` -Type Custom ` -KeySpec Signature ` -KeyExportPolicy Exportable ` -KeyLength 4096 ` -KeyUsageProperty Sign ` -KeyUsage CertSign ` -CertStoreLocation Cert:\CurrentUser\My Get-ChildItem -Path Cert:\CurrentUser\CA | Where-Object Thumbprint -EQ $CACert.Thumbprint | Remove-Item # ! die Kopie in \CA ist Unnötig! $CACert | Export-Certificate -FilePath ".\$CAName.cer" # ? TESTING Get-ChildItem -Path Cert:\ -Recurse | Where-Object Thumbprint -EQ $CACert.Thumbprint . ".\$CAName.cer" # 3. Öffentlich Root-CA verteilen (z.B. über GPO) in den Speicher für vertrauenswürdige Stammzertifizierungsstellen (Root) Import-Certificate -FilePath ".\$CAName.cer" -CertStoreLocation Cert:\LocalMachine\Root # ? TESTING Get-ChildItem -Path Cert:\ -Recurse | Where-Object Thumbprint -EQ $CACert.Thumbprint . ".\$CAName.cer" # 4. Signierungszertifikate pro Benutzer in "Eigene Zertifikate (MY)" installieren $PSDeveloperCert = New-SelfSignedCertificate -Subject $PSDeveloperName ` -HashAlgorithm SHA512 ` -CertStoreLocation Cert:\CurrentUser\My ` -Type CodeSigningCert ` -Signer $CACert Export-Certificate -Cert "Cert:\CurrentUser\My\$($PSDeveloperCert.Thumbprint)" -FilePath "$PSDeveloperName.cer" -Force # ? TESTING . ".\$PSDeveloperName.cer" # 5. Jetzt Können *.ps1-Dateien signiert werden Set-AuthenticodeSignature -FilePath ".\$PS1Name" -Certificate $PSDeveloperCert -Force -HashAlgorithm SHA512 -IncludeChain all # ? TESTING . ".\$PS1Name" # 6. OPTIONAL Das öffentliche Benutzer-Zertifikat von 3. muss z.B. per GPO auf alle betroffenen Host's verteilt Import-Certificate -FilePath "$PSDeveloperName.cer" -CertStoreLocation Cert:\LocalMachine\TrustedPublisher # ? TESTING Get-ChildItem -Path Cert:\LocalMachine\TrustedPublisher . ".\$PS1Name" # 7. Kontrollieren (Mit / Ohne eine Änderung an der signierten Datei) Get-Content ".\$PS1Name" Get-AuthenticodeSignature -FilePath ".\$PS1Name" | Format-List -Property * Start-Process -FilePath ".\$PS1Name" # TEST: Einmal Code manipulieren Get-AuthenticodeSignature -FilePath ".\$PS1Name" | Format-List -Property * # TEST: Valid? . ".\$PS1Name" # TEST: Wird ausgeführt? # 8. Empfohlene Einstellung für den Entwicklungs-Rechner Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser -Force # 9. Signieren im Alltag $cert = Get-ChildItem -Path Cert:\CurrentUser\My -CodeSigningCert | Out-GridView -Title "Zertifikat wählen" -OutputMode Single Get-ChildItem -Path .\*.ps1 | Out-GridView -Title "Zu signierende .PS1 oder psm1-Datei(en) auswählen" -OutputMode Multiple | Set-AuthenticodeSignature -Certificate $cert -IncludeChain all -HashAlgorithm SHA512 # 10. A) AUFRÄUMEN: Öffentliches Zertifikat des vertrauenswürden Herausgeber (Der Signierer) löschen Get-ChildItem -Path Cert:\LocalMachine\TrustedPublisher | Where-Object Thumbprint -EQ $PSDeveloperCert.Thumbprint | Remove-Item Get-ChildItem -Path Cert:\CurrentUser\My | Where-Object Thumbprint -EQ $PSDeveloperCert.Thumbprint | Remove-Item # 10. B) AUFRÄUMEN: Öffentliches & privates Zertifikat der Zertifizierungsstelle löschen Get-ChildItem -Path Cert:\LocalMachine\Root | Where-Object Thumbprint -EQ $CACert.Thumbprint | Remove-Item Get-ChildItem -Path Cert:\CurrentUser\My | Where-Object Thumbprint -EQ $CACert.Thumbprint | Remove-Item # 10. C) AUFRÄUMEN: Öffentliche Zertifikat der Zertifizierungsstelle und des vertrauenswürden Herausgeber (Signierer) im Arbeitsverzeichnis löschen Get-ChildItem -Path . | Where-Object Extension -IN '.cer' | Remove-Item # 10. D) AUFRÄUMEN: Ausführungsrichtlinien auf RemoteSigned setzen. Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process -Force Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser -Force Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine -Force # 10. E) AUFRÄUMEN: TEST: Sind alle betroffenen Zertifikat im Laufwerk Cert:\ gelöscht? Get-ChildItem -Path Cert:\ -Recurse | Where-Object Thumbprint -IN $CACert.Thumbprint, $PSDeveloperCert.Thumbprint # 10. F) AUFRÄUMEN: TEST: Sind alle betroffenen Zertifikat im Arbeitsverzeichnis gelöscht? Get-ChildItem -Path . -Recurse | Where-Object -Property Extension -EQ ".cert" # 10. G) AUFRÄUMEN: TEST: Passen die Ausführungsrichtlinien? Get-ExecutionPolicy -List #endregion |