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.05.13 #> # ! WARUM SIGNIEREN ? # ! 1. Eine Signatur schützt ein Skript (ps1, psm1, 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 Set-Location -Path 'C:\Temp' 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 speichern Import-Certificate -FilePath '.\PublicSignerCertificate.cer' -CertStoreLocation 'Cert:\LocalMachine\Root' # ? 8.2. Zertifikat im TrustedPublisher-Store 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-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' -Wait # ? 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' -Wait # ? 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 #region 0. VORBEREITUNG # TODO Benötigte Namespaces importieren: using namespace 'Microsoft.CertificateServices.Commands' # TODO Die zu signierende Datei ins Arbeitsverzeichnis kopieren: Copy-Item -Path '.\Get-EuroExchange.ps1' -Destination 'C:\Temp\' -Force Test-Path -Path 'C:\Temp\Get-EuroExchange.ps1' | Write-Warning # TODO Die Ausführungsrichtlinien 'scharf' schalten: Set-ExecutionPolicy -ExecutionPolicy 'AllSigned' -Scope 'Process' -Force Get-ExecutionPolicy -List # ! Empfohlene Einstellung für den Entwicklungs-Rechner ist: Set-ExecutionPolicy -ExecutionPolicy 'RemoteSigned' -Scope 'CurrentUser' -Force # s.a. Unblock-File #endregion #region 1. Problem > Lösung # ! Problem ... & 'C:\Temp\Get-EuroExchange.ps1' # ! ... Lösung => Signieren! # ! Vielleicht besitzen Sie schon ein x.509 Zertifikat mit Verwendungszweck "Codesigning" inkl. des "Private Key"s? Get-ChildItem -Path 'Cert:\CurrentUser\My' -CodeSigningCert # ! ... wenn vorhanden dan weiter mit Punkt 6. #endregion #region 2. ROOT-CA erstellen # TODO Root CA erstellen: $parameters = @{ Subject = 'CN=_PowerShell Root CA TEMP' HashAlgorithm = 'SHA512' NotAfter = (Get-Date).AddYears(10) Type = [CertificateType]::Custom KeySpec = 'Signature' KeyLength = 4096 KeyUsageProperty = 'Sign' KeyUsage = 'CertSign' KeyExportPolicy = [KeyExportPolicy]::ExportableEncrypted CertStoreLocation = 'Cert:\CurrentUser\My' } $pfxCert = New-SelfSignedCertificate @parameters # TODO Root CA-Zertifikate mit dem privaten Schlüssel exportieren: $pfxPassword = Read-Host -Prompt 'ROOT CA - Das Passwort zum schützen des privaten Schlüssels angeben' -AsSecureString $pfxCert | Export-PfxCertificate -Password $pfxPassword -FilePath 'C:\Temp\PowerShell_RootCA.pfx' -CryptoAlgorithmOption 'AES256_SHA256' -ChainOption 'BuildChain' -Force # TODO Die Kopie in Cert:\CurrentUser\CA ist unnötig: Get-ChildItem -Path 'Cert:\CurrentUser\CA' | Where-Object -Property 'Subject' -EQ -Value 'CN=_PowerShell Root CA TEMP' | Remove-Item # TODO Ein öffentliches Zertifikat der Root CA exportieren: Get-PfxCertificate -FilePath 'C:\Temp\PowerShell_RootCA.pfx' | Export-Certificate -FilePath 'C:\Temp\PowerShell_RootCA.cer' # TODO TEST 1: Get-ChildItem -Path 'Cert:\' -Recurse | Where-Object 'Subject' -EQ -Value 'CN=_PowerShell Root CA TEMP' # TODO TEST 2: & 'C:\Temp\PowerShell_RootCA.cer' #endregion #region 3. Root CA verteilen # TODO Das öffentlich Root-CA-Zertifikat wird i.d.R. per GPO in den Speicher "Vertrauenswürdige Stammzertifizierungsstelle" (Root) verteilt: Import-Certificate -FilePath 'C:\Temp\PowerShell_RootCA.cer' -CertStoreLocation 'Cert:\LocalMachine\Root' -Verbose # TODO TEST 1: Get-ChildItem -Path 'Cert:\' -Recurse | Where-Object -Property 'Subject' -EQ -Value 'CN=_PowerShell Root CA TEMP' # TODO TEST 2: & 'C:\Temp\PowerShell_RootCA.cer' #endregion #region 4. Signierer-Zertifikate erstellen # TODO Ein Signierer-Zertifikat pro Benutzer von der Root CA in "Eigene Zertifikate" (MY) erstellen und installieren: $Parameters = @{ Subject = 'CN=_Montgomery Scott (PS Developer)' HashAlgorithm = 'SHA512' Type = [CertificateType]::CodeSigningCert KeyExportPolicy = [KeyExportPolicy]::ExportableEncrypted NotAfter = (Get-Date).AddYears(5) Signer = (Get-PfxCertificate -FilePath 'C:\Temp\PowerShell_RootCA.pfx') CertStoreLocation = "Cert:\CurrentUser\My" } $pfxCert = New-SelfSignedCertificate @Parameters # TODO Signierer-Zertifikat mit dem privaten Schlüssel exportieren: $pfxPassword = Read-Host -Prompt 'Signierer-Zertifikat - Das Passwort zum schützen des privaten Schlüssels angeben' -AsSecureString $pfxCert | Export-PfxCertificate -Password $pfxPassword -FilePath 'C:\Temp\MontgomeryScott_PSDeveloper.pfx' # TODO Signierer-Zertifikate mit privaten Schlüssel löschen, da der Cert-Speicher unsicher ist: Get-ChildItem -Path 'Cert:\CurrentUser\My' | Where-Object -Property 'Subject' -EQ -Value 'CN=_Montgomery Scott (PS Developer)' | Remove-Item -Force # TODO Öffentliches Zertifikat des Signierers exportieren: Get-PfxCertificate -FilePath 'C:\Temp\MontgomeryScott_PSDeveloper.pfx' | Export-Certificate -FilePath 'C:\Temp\MontgomeryScott_PSDeveloper.cer' -Force # TODO TEST: & 'C:\Temp\MontgomeryScott_PSDeveloper.cer' #endregion #region 5. Signierer-Zertifikat verteilen # TODO Das öffentliche Benutzer-Zertifikat von 4. muss z.B. per GPO auf alle betroffenen Host's verteilt werden: Import-Certificate -FilePath 'C:\Temp\MontgomeryScott_PSDeveloper.cer' -CertStoreLocation 'Cert:\LocalMachine\TrustedPublisher' # TODO TEST: Get-ChildItem -Path 'Cert:\LocalMachine\TrustedPublisher' #endregion #region 6. Datei signieren # TODO Signierer-Zertifikat auswählen: $pfxCert = Get-ChildItem -Path 'C:\Temp\*.pfx' | Out-GridView -OutputMode Single | Get-PfxCertificate # TODO .PS1-Datei signieren: Get-ChildItem -Path 'C:\Temp\*.ps1' | Out-GridView -OutputMode Single | Set-AuthenticodeSignature -Certificate $pfxCert -Force -HashAlgorithm 'SHA512' -IncludeChain 'All' -TimestampServer 'http://timestamp.globalsign.com/scripts/timstamp.dll' #endregion #region 7. Kontrolle # TODO Signierte Datei visualisieren: Start-Process -FilePath 'C:\Temp\Get-EuroExchange.ps1' # TODO Signatur der Datei prüfen: Get-AuthenticodeSignature -FilePath 'C:\Temp\Get-EuroExchange.ps1' | Format-List -Property * # TODO Signierte Datei ausführen: & 'C:\Temp\Get-EuroExchange.ps1' # TODO: Den Code der signierten Datei manipulieren und Prüfungen erneut ausführen: Start-Process -FilePath 'C:\Temp\Get-EuroExchange.ps1' Get-AuthenticodeSignature -FilePath 'C:\Temp\Get-EuroExchange.ps1' | Format-List -Property * & 'C:\Temp\Get-EuroExchange.ps1' #endregion #region 8. Aufräumen und keine Spuren hinterlassen # TODO Öffentliches (vertrauenswürden Herausgeber) und privates Zertifikat des Signierers löschen: Get-ChildItem -Path 'Cert:\LocalMachine\TrustedPublisher', 'Cert:\CurrentUser\My' | Where-Object 'Subject' -EQ 'CN=_Montgomery Scott (PS Developer)' | Remove-Item # TODO Öffentliches & privates Zertifikat der Zertifizierungsstelle löschen: Get-ChildItem -Path 'Cert:\LocalMachine\Root', 'Cert:\CurrentUser\My' | Where-Object 'Subject' -EQ 'CN=_PowerShell Root CA TEMP' | Remove-Item # TODO Öffentliche und private Zertifikat im Dateisystem löschen: Get-ChildItem -Path 'C:\Temp' -Recurse | Where-Object 'Extension' -IN '.cer', '.pfx' | Remove-Item # TODO Test-Dateien löschen: Get-ChildItem -Path 'C:\Temp' -Recurse | Where-Object 'Extension' -IN '.ps1' | Remove-Item # TODO Ausführungsrichtlinien auf RemoteSigned setzen: Set-ExecutionPolicy -ExecutionPolicy 'RemoteSigned' -Scope 'Process' -Force #endregion #endregion |