Letzten Freitag habe ich bei der PowerShell Usergroup Hannover kurz etwas zum Thema CodeSignieren für PowerShell Module zum Upload in die PowerShell Gallery erzählt. Nun, dass Interesse meine Kollegen war größer als gedacht und auch einige Frage ungeklärt. Hier hat sich auch einiges geändert. Fakt ist, die meisten Module auch in der Gallery sind unsigniert. Letztlich stellt sich auch immer die Frage, warum überhaupt signieren, wenn doch die PowerShell Executionpolicy so leicht ausgehebelt werden kann?
Beispielsweise ist es möglich, mit Powershell.exe -ExecutionPolicy Bypass -File <PathToPowershellSkript.ps1> beliebigen Code auszuführen.
Seit einigen Jahren nutze ich Code Signing Zertifikate für meine Community PowerShell Skripte. Das kommt insbesondere auch gut bei unseren Kunden an. Unsere Arbeit wird damit digital unterschrieben. Weiterhin sind auch die Hemmungen etwas Grösser, Skripte später zu verändern denn immer muss auch der Signaturblock geändert werden.
Einfache Codesignaturzertifikate gibt es für drei Jahre unter 200€ (wenn man etwas sucht). Mit diesem Zertifikat ist es einfach, seine Skripte zu signieren. Das Zertifikat ist nach einem korrekten Import in den Eigenen CertStore zu finden:
Mit der PowerShell können die eigenen Zertifikate über Get-ChildItem angezeigt werden.
Get-ChildItem Cert:\CurrentUser\My
Identifiziert wird das Zertifikat über das Propertie „Thrumbprint“. Das kann man sich auch in den Eigenschaften eines Objektes anschauen.
Über die PowerShell kann das Zertifikat für den Signierungsprozess ausgelesen werden. Der Thumbprint dient als Zugriffsschlüssel:
$cert = @(Get-ChildItem Cert:\CurrentUser\my\7FB82C295C4B3DC0C3BF8C76378461E9B48186DF -codesigning)[0]
Die Signatur mit Timestamp erfolgt nun Set-AuthenticodeSignature
Set-AuthenticodeSignature <PathToFile.ps1> $cert -TimestampServer http://timestamp.comodoca.com -HashAlgorithm "SHA256"
Wichtig ist die Angabe des Timespamp Servers damit Eure Signaturen auch nach dem Ablaufdatum gültig sind. Andere CA’s als Comodo haben hier auch andere Timestamp Server
Als Resultat bekommt das Skritp einen Signaturblock
# SIG # Begin signature block# MIIetQYJKoZIhvcNAQcCoIIepjCCHqICAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUFw86ss8Cl40CWe+LUa4iwVtD# Pxugghm/MIIEhDCCA2ygAwIBAgIQQhrylAmEGR9SCkvGJCanSzANBgkqhkiG9w0B … # SIG # End signature block
Über die Eigenschaften des Skripts ist die gültige Signatur zu erkennen
Nun kann die PowerShell Executionpolicy auf AllSigned gestellt werden oder nicht? Nun, das Problem ist, es gibt noch immer eine Meldung bei Ausführen des Skrips:
Set-ExecutionPolicy -ExecutionPolicy AlLSigned
Nun bekommen wir die folgende Meldung:
Do you want to run software from this untrusted publisher?
File C:\Users\Andreas\Desktop\test-codesigning.ps1 is published by CN=Nick Informationstechnik GmbH, O=Nick Informationstechnik GmbH… and is not trusted on your system. Only run scripts from trusted publishers. [V] Never run [D] Do not run [R] Run once [A] Always run [?] Help (default is "D"):
Nun, Kamil von der PSUGH kannte die Antwort. Die Skripte können noch immer nicht ausgeführt werden, bis das eigen Zertifikat auch in den „Trusted Publishes“ der eigenen Zertifikate auftaucht. Wenn die Frage oben mit [A] beantwortet wird kopiert die PowerShell das Zertifikat automatisch an die korrekte Stelle. Für ein Unternehmen empfiehlt es sich, die Zertifikate, die code signieren dürfen über eine Gruppenrichtlinie zu verteilen.
Das Signieren eines PowerShell Projektes kann man natürlich auch mit der PowerShell über ein Skript erledigen lassen.
$cert = @(Get-ChildItem Cert:\CurrentUser\my\7FB82C295C4B3DC0C3BF8C76378461E9B48186DF -codesigning)[0] Get-ChildItem D:\AppVForcelets | ForEach-Object ` { Set-AuthenticodeSignature $_.FullName $cert -TimestampServer http://timestamp.comodoca.com -HashAlgorithm "SHA256" }
Veröffentlichen in der PowerShell Gallerie
Der Download
Nun sollte das neue, vollständig signierte Modul in der PowerShell Gallerie veröffentlicht werden. Wir erinnern uns – Pakete in der PowerShell Gallerie können mit Install-Module heruntergeladen werden. Es empfiehlt sich der Schalter -Scope CurrentUser. Dann landen neue Module unter Documents\MicrosoftPowerShell\Modules und nicht im System.
Nun, das erste was man auch hier bekommt ist ein „You are installing the modules from an untrusted repository?“. Wie? Die PowerShellGallery ist nicht vertrauenswürdig? Aber doch – dort kann jeder etwas hochladen und im Grunde prüft keiner die Skripte. Vertrauenswürdig hat hier erst einmal nichts mit der Signatur zu tun.
Die Meldung bekommt ihr ganz einfach für immer weg mit:
Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted
Wie gesagt, hierbei beachten, dass darüber durchaus auch gefährlicher code kommen kann.
Der Upload
Der Upload in die Gallery erfolgt mit Publish-Modul anschließend -Name (Modulname in einem Modulverzeichnis) oder -Path für den direkten Pfad auf das Modul. Abschließend der NuGetApiKey mit -NuGetApiKey
Den Schlüssel findet ihr in Eurem PowerShell Gallery Account in Eurem Profil. Daneben sind noch weitere Angaben möglich. Beispielsweise eine URL für die Lizenz oder Tags.
Das funktioniert auch sehr gut, solange das Skript keine Signatur hat. Jetzt kommen wir also zum Kern. Hier hat sich etwas geändert. Mit digitaler Signatur wird eine jetzt Katalogdatei (.cat) gefordert. In dieser finden sich wiederum Hashes für alle Dateien eines Verzeichnisses. Diese Datei muss wiederum selber digital signiert sein. Erst dann klappt es auch mit dem Upload digital signierter Skripte in die PowerShell Gallery.
Für das Beispiel oben sind das die folgenden Anweisungen:
New-FileCatalog -Path D:\AppVForcelets -CatalogFilePath D:\ AppVForcelets\AppVForcelets.cat -CatalogVersion 2.0 $cert = @(Get-ChildItem Cert:\CurrentUser\my\7FB82C295C4B3DC0C3BF8C76378461E9B48186DF -codesigning)[0] Set-AuthenticodeSignature D:\ AppVForcelets\AppVForcelets.cat $cert -TimestampServer http://timestamp.comodoca.com -HashAlgorithm "SHA256"
Im Modul selber muss sich ein gültiges Manifest befinden. Nach jedem Upload ist insbesondere die Versionsnummer hochzusetzen.
# Version Number
ModuleVersion = '1.3'
Zusätzlich kann es einen PrivateData Blog geben in dem Angaben erfolgen können, die jedoch auch per Parameter in Publish-Module mit angegeben werden können. Beispielsweise Tags oder die Projektseite.
PrivateData = @{ PSData = @{ Tags = @('App-V','AppV','DeploymentConfig','AppXManifest') # A URL to the license for this module. LicenseUri = 'https://raw.githubusercontent.com/AndreasNick/AppVForcelets/master/LICENSE # A URL to the main website for this project. ProjectUri = 'http://www.andreasnick.com' # A URL to an icon representing this module. # IconUri = '' # ReleaseNotes of this module # ReleaseNotes = '' } # End of PSData hashtable } # End of PrivateData hashtable