Demo-Code und Folien zu meinem Vortrag bei der PSUGB#7

Am 11.11. habe ich bei der PowerShell User Group Berlin ein Konzept zur sicheren zentralisierten Bereitstellung von Secrets mittels CMS gehalten. Hier sind die Folien und der DEMO-Code, da diese Schnipsel nicht in den Folien enthalten sind.

20201111_XPCM – Foliensatz zum Vortrag

cms-certificate – das in den Demos verwendete CMS-Zertifikat, das Kennwort für die PFX-Datei ist 12345

ANMERKUNG: Praktisch während ich den Vortrag hielt, releaste das PowerShell Team die Version 7.1.0, welche die CMS-Cmdlets erstmals nativ beinhaltet. Da jedoch einige noch in der Version 6 stecken und nicht so schnell upgraden können, macht das die mit .NET nachgebaute Funktionalität nicht weniger nützlich.

Demo 0: SecureString auf Linux

Führe den folgenden Code zum Vergleich auf Windows und Linux (oder Mac) aus:

Get-Credential | Select-Object -ExpandProperty Password | ConvertFrom-SecureString

Demo 1: CMS in Action (auf Windows und mit PowerShell 7.1)

Ein kleines Stück Text verschlüsseln (Public Key reicht):

$encryptionCertFile = Join-Path -Path $PSScriptRoot -ChildPath 'cms.cer'
$messageFile = Join-Path -Path $PSScriptRoot -ChildPath 'cms-message.txt'
$cleartext = 'You''ll n3v3r gue55 my pa$$word'
Protect-CmsMessage -To $encryptionCertFile -Content $cleartext | Set-Content -Path $messageFile

Das Resultat wieder entschlüsseln (Privat Key ist erforderlich):

$decryptionCertFile = Join-Path -Path $PSScriptRoot -ChildPath 'cms.pfx'
$decryptionCertFilePassword = '12345'
$messageFile = Join-Path -Path $PSScriptRoot -ChildPath 'cms-message.txt'$decryptionCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($decryptionCertFile,$decryptionCertFilePassword,[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet)
Unprotect-CmsMessage -To $decryptionCert -Path $messageFile

Um das Zertifikat aus der PFX-Datei einzulesen, hat PowerShell auch ein natives Cmdlet, Get-PFXCertificate. Allerdings gibt es zwischen PowerShell 5.1 und PowerShell 6+ einen entscheidenden Unterschied: die Windows PowerShell-Variante fragt immer interaktiv nach dem Kennwort und ist daher nicht automatisierbar. Andererseits, die Passwort-Übergabe im Klartext ist genau das, was wir versuchen abzuschaffen, insofern ist dieser „Nachteil“ freilich nicht sehr gravierend.

Demo 2: Cross-Platform CMS (PowerShell -le 7.0.3)

PowerShell 7.1 war noch nicht GA, daher musste hier für Cross-Platform-Funktionalität auf .NET-Klassen zurückgegriffen werden.

Verschlüsseln (per Default wird mit 3DES verschlüsselt, für AES256 muss der AlgorithmIdentifier also explizit angegeben werden):

Add-Type -AssemblyName 'System.Security'
$encryptionCertFile = Join-Path -Path $PSScriptRoot -ChildPath 'cms.cer'
$messageFile = Join-Path -Path $PSScriptRoot -ChildPath 'cms-message.txt'
$encryptionCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($encryptionCertFile)
$cleartext = 'You''ll n3v3r gue55 my pa$$word'

$cmsRecipient = [System.Security.Cryptography.Pkcs.CmsRecipient]::new($encryptionCert)
$oid = [System.Security.Cryptography.Oid]::new("2.16.840.1.101.3.4.1.42","AES256")
$algorithmIdentifier = [System.Security.Cryptography.Pkcs.AlgorithmIdentifier]::new($oid)
$bytes = [System.Text.Encoding]::UTF8.GetBytes($cleartext)
$cmsContentInfo = [System.Security.Cryptography.Pkcs.ContentInfo]::new($bytes)
$envelopedMessage =[System.Security.Cryptography.Pkcs.EnvelopedCms]::new($cmsContentInfo, $algorithmIdentifier)
$envelopedMessage.Encrypt($cmsRecipient)
$encodedMessage = $envelopedMessage.Encode()
$packagedMessage = [Convert]::ToBase64String($encodedMessage)
$packagedMessage | Set-Content -Path $messageFile -Force

Entschlüsseln:

Add-Type -AssemblyName 'System.Security'
$decryptionCertFile = Join-Path -Path $PSScriptRoot -ChildPath 'cms.pfx'
$decryptionCertFilePassword = '12345'
$messageFile = Join-Path -Path $PSScriptRoot -ChildPath 'cms-message.txt'

$decryptionCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($decryptionCertFile,$decryptionCertFilePassword,[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet)

$packagedMessage = Get-Content -Path $messageFile
$encodedMessage = [convert]::FromBase64String($packagedMessage)
$envelopedMessage =[System.Security.Cryptography.Pkcs.EnvelopedCms]::new()
$envelopedMessage.Decode($encodedMessage)
$envelopedMessage.Decrypt($decryptionCert)
$contentInfo = $envelopedMessage.ContentInfo
[System.Text.Encoding]::UTF8.GetString($contentInfo.Content)

ANMERKUNG: Das explizite Hinzufügen des Assembly scheint ein für Windows spezifisches Phänomen zu sein. Einige Unternamespaces laden ohne Probleme auch so, z.B. X509Certificate, aber für PKCS ist es notwendig, die Assembly vorher einzulesen!

Statt Demo 3: Zertifikat aus PFX in den lokalen Store importieren

Egal, ob man auf Get-PFXCertificate zurückgreift oder die oben dargestellte .NET-Arbeitsweise verwendet, möchte man natürlich nicht bei jedem Durchlauf das Zertifikat erneut aus der PFX-Datei laden, sondern in den lokalen Store importieren. Das gelingt mit:

$store = New-Object System.Security.Cryptography.X509Certificates.X509Store([System.Security.Cryptography.X509Certificates.StoreName]::My,[System.Security.Cryptography.X509Certificates.StoreLocation]::CurrentUser)
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::MaxAllowed)
$null = $store.Add($cert)
$store.Close()

wenn $cert das aus der PFX eingelesene Zertifikat samt Private Key beinhaltet.

Wann gibt es den produktiven Code?

Bald 🙂 Watch this space!

Ersten Kommentar schreiben

Antworten

Deine E-Mail-Adresse wird nicht veröffentlicht.


*


Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.