Self-Signed PKI für IT-Infrastrukturen ohne AD Certificate Services

Bekanntermaßen benötigt es für eine Windows-basierte Enterprise PKI ein vollständiges Active Directory. In kleinen Arbeitsgruppen und in immer mehr verbreiteten Cloud-only Infrastrukturen stelle diese Anforderung ein Problem dar, da der Zweck solcher Infrastrukturen eben ist, keinen Aufwand mehr für eine Active Directory / On-Prem Infrastruktur betreiben zu müssen.

Wenn man auf Enterprise Features wie z.B. Templates, CRLs oder Auto-Enrollment verzichten kann, gibt es jedoch die Möglichkeit per PowerShell und einem Windows Endgerät eine PKI mit Self-signed Zertifikaten quasi „nachzubauen“.

Dabei gilt es jedoch zwingend vorher (!) zu bedenken, ob dieses Konstrukt wirklich ausreicht (besonders in Unternehmen) oder ob hier nicht doch ein größerer Aufwand betrieben werden muss, da sich verschiedene Dinge, die eventuell auch technisch oder rechtlich notwendig sein können, nicht umsetzen lassen, z.B.:

  • Certificate Revocation List
  • Certificate Policy / Certification Practice Statement

Was sich jedoch auch mit der PowerShell nutzen lässt, ist eine IANA.org Private Enterprise Number (IANA-PEN). Die lässt sich sogar für Privatpersonen beantragen. Dementsprechend habe ich die Nutzung der IANA-PEN in den folgenden Skripten berücksichtigt.

In meinem Fall tut es eine solche Self-Signed PKI und daher möchte ich für den „Eigenbedarf“ eine einfache, zweistufige PKI erstellen und Webserver-, User- und Smart Card-Zertifikate ausstellen. Die Basis der PKI bildet dabei eine Root CA und eine Intermediate CA, die gleichzeitig auch die Computer-/User-Zertifikate ausstellt und somit als Issuing CA fungiert. Wer’s etwas granularer möchte, der bekommt es mit den Skripten sicher auch hin, eine dreistufige PKI mit Root CA, Intermediate CA und separater Issuing CA zu erstellen.

Mein Deployment teile ich in verschiedene Skripte auf; natürlich kann das Eine oder Andere zusammengefasst und doppelt angegebene Parameter dann weggelassen werden.

Der Einfachheit halber verwende ich als Organisation die DNS Domain, bzw. bei Usern den UPN-Suffix, damit die Skript ein bisschen weniger Variablen haben und einfacher/übersichtlicher sind. Bei Bedarf kann das natürlich noch ausgebaut werden.

Am Ende des Beitrages pflege ich ein PS-Script (konvertiert in ein Executable), mit welchem man ein Self-Signed PKI Deployment bestehende aus 3 Tiers erstellen kann.

Die erstellten Zertifikate exportiere ich jeweils einmal mit Private Key, einmal mit Private Key inkl. Zertifikatskette (bis auf die Root CA) und einmal ohne Private Key.

So, genug Vorgeplänkel, los geht’s!

Root CA

Basis der PKI ist die Root CA. Diese erstelle ich wie folgt:

# Variablen abfragen und definieren
$OrgName = Read-Host "Organization Name (DNS-Domain)"
$OrgRootCA = "$OrgName-RootCA"
$PfxPwdSecureString = Read-Host "Password for certificate export as PFX" -AsSecureString
$PfxPwdEncrypted = ConvertFrom-SecureString -SecureString $PfxPwdSecureString
$CertFilePath = "$ENV:UserProfile\Desktop"

# Root CA Parameter definieren und CA Zertifikat erstellen
$params = @{  
   DnsName = $OrgRootCA  
   KeyLength = 4096  
   KeyAlgorithm = 'RSA'  
   KeyUsage = 'CertSign','CRLSign'
   HashAlgorithm = 'SHA512'  
   KeyExportPolicy = 'Exportable'  
   NotAfter = (Get-Date).AddYears(15)
   CertStoreLocation = 'Cert:\LocalMachine\My'  
   Type = 'Custom'
   Textextension = '2.5.29.37={text}1.3.6.1.5.5.7.3.2,1.3.6.1.5.5.7.3.1,1.3.6.1.5.5.7.3.4,1.3.6.1.5.5.7.3.3,1.3.6.1.5.5.7.3.8,1.3.6.1.5.5.7.3.1','2.5.29.19={critical}{text}ca=TRUE&pathLength=1'
}
$RootCA = New-SelfSignedCertificate @params

# Zertifikat exportieren; Zertifikat ohne Private Key in den Root CA Store importieren
Export-Certificate -Cert $RootCA -FilePath "$CertFilePath\$OrgRootCA.crt"
Export-PfxCertificate -Cert $RootCA -ChainOpton EndEntityCertOnly -FilePath "$CertFilePath\$OrgRootCA.pfx" -Password (ConvertTo-SecureString -String $PfxPwdEncrypted -Force)
Import-Certificate -CertStoreLocation "Cert:\LocalMachine\Root" -FilePath "$CertFilePath\$OrgRootCA.crt"

Hinweise zu den Parametern

  • TextExtension = …&pathLength=1
    • Die Angabe einer Pfadlänge erzwingt, dass die PKI in diesem Fall maximal zweistufig sein kann (Root CA + 1). Für mehrstufige PKIs muss der Parameter entsprechend erhöht oder auch ganz weggelassen werden.
Issuing CA

Im nächsten Schritt erstelle ich mit Hilfe des Root CA Zertifikates meine Issuing CA.

# Variablen abfragen und definieren
$OrgName = Read-Host "Organization Name (DNS-Domain)"
$OrgRootCA = "$OrgName-RootCA"
$OrgIssuingCA = "$OrgName-IssuingCA"
$PfxPwdSecureString = Read-Host "Password for certificate export as PFX" -AsSecureString
$PfxPwdEncrypted = ConvertFrom-SecureString -SecureString $PfxPwdSecureString
$CertFilePath = "$ENV:UserProfile\Desktop"
$RootCA = Get-ChildItem -Path cert:\LocalMachine\My | where {$_.Subject -like "*CN=$OrgRootCA*"}

# Issuing CA Parameter definieren und CA Zertifikat erstellen
$params = @{  
   DnsName = $OrgIssuingCA
   Signer = $RootCA
   KeyLength = 4096
   KeyAlgorithm = 'RSA'
   KeyUsage = 'CertSign','CRLSign'
   HashAlgorithm = 'SHA512'
   KeyExportPolicy = 'Exportable'
   NotAfter = (Get-Date).AddYears(10)
   CertStoreLocation = 'Cert:\LocalMachine\My'
   Type = 'Custom'
   Subject = "CN=$OrgIssuingCA, E=certificates@$OrgName,O=$OrgName,OU=1.3.6.1.4.1.<IANA-PEN>,C=DE"
   Textextension = '2.5.29.37={text}1.3.6.1.5.5.7.3.2,1.3.6.1.5.5.7.3.1,1.3.6.1.5.5.7.3.4,1.3.6.1.5.5.7.3.3,1.3.6.1.5.5.7.3.8,1.3.6.1.5.5.7.3.1','2.5.29.19={critical}{text}ca=TRUE'
}
$IssuingCA = New-SelfSignedCertificate @params

# Issuing CA Zertifikat exportieren; Zertifikat ohne Private Key in den Root CA und Intermediate CA Store importieren
Export-Certificate -Cert $IssuingCA -FilePath "$CertFilePath\$OrgIssuingCA.crt"
Export-PfxCertificate -Cert $IssuingCA -ChainOpton EndEntityCertOnly -FilePath "$CertFilePath\$OrgIssuingCA.pfx" -Password (ConvertTo-SecureString -String $PfxPwdEncrypted -Force)
Export-PfxCertificate -Cert $IssuingCA -ChainOption BuildChain -FilePath "$CertFilePath\$OrgIssuingCA-Chain.pfx" -Password (ConvertTo-SecureString -String $PfxPwdEncrypted -Force)
Import-Certificate -CertStoreLocation "Cert:\LocalMachine\CA" -FilePath "$CertFilePath\$OrgIssuingCA.crt"
Import-Certificate -CertStoreLocation "Cert:\LocalMachine\Root" -FilePath "$CertFilePath\$OrgIssuingCA.crt"

Hinweise zu den Parametern

  • Subject
    • Der Parameter ist optional! Prinzipiell kann auch der DnsName als Subject verwendet werden. Gerade bei der Issuing CA gibt man allerdings mehr Details zur CA an, als nur ihren Namen.
  • Subject OU=1.3.6.1.4.1.<IANA-PEN>
    • IANA.org Private Enterprise Number
Webserver-/SSL-Zertifikat mit SANs

Für unsere Webserver können nun über die Issuing CA Webzertifikate ausgestellt werden.

# Variablen abfragen und definieren
$OrgName = Read-Host "Organization Name (DNS-Domain)"
$OrgRootCA = "$OrgName-RootCA"
$OrgIssuingCA = "$OrgName-IssuingCA"
$Subject = Read-Host "Subject Name (FQDN)"
$SubAltName = Read-Host "Additional Subject Alternative Names (comma-separated)"
If ($SubAltName -eq $null) {$SANs = $Subject} else {$SANs = $Subject,$SubAltName}
$PfxPwdSecureString = Read-Host "Password for certificate export as PFX" -AsSecureString
$PfxPwdEncrypted = ConvertFrom-SecureString -SecureString $PfxPwdSecureString
$CertFilePath = "$ENV:UserProfile\Desktop"
$IssuingCA = Get-ChildItem -Path cert:\LocalMachine\My\ | where {$_.Subject -like "*CN=$OrgIssuingCA*"}

# Web Server Zertifikat Parameter definieren und Zertifikat erstellen
$params = @{ 
   Signer = $IssuingCA
   Subject = $Subject
   DnsName = $SANs
   KeyLength = 2048 
   KeyAlgorithm = 'RSA' 
   HashAlgorithm = 'SHA256' 
   KeyExportPolicy = 'Exportable' 
   NotAfter = (Get-date).AddYears(2) 
   CertStoreLocation = 'Cert:\LocalMachine\My'
   Type = 'SSLServerAuthentication'
}
$ComputerCert = New-SelfSignedCertificate @params

# Web Server Zertifikat exportieren
Export-Certificate -Cert $ComputerCert -FilePath "$CertFilePath\$Subject.cer"
Export-PfxCertificate -Cert $ComputerCert -ChainOpton EndEntityCertOnly -FilePath "$CertFilePath\$Subject.pfx" -Password (ConvertTo-SecureString -String $PfxPwdEncrypted -Force)
Export-PfxCertificate -Cert $ComputerCert -ChainOption BuildChain -FilePath "$CertFilePath\$Subject-Chain.pfx" -Password (ConvertTo-SecureString -String $PfxPwdEncrypted -Force)
User Zertifikat

User Zertifikate werden unter Umständen z.B. für S/MIME oder Client Authentication verwendet. Im folgenden Beispiel erstelle ich ein Zertifikat mit allen Schlüsselverwendungen (Purposes, Key Usages):

  • 1.3.6.1.5.5.7.3.3, Code Signing
  • 1.3.6.1.5.5.7.3.4, Secure Email
  • 1.3.6.1.5.5.7.3.2, Client Authentication
  • 1.3.6.1.4.1.311.10.3.4, Encrypting File System
# Variablen abfragen und definieren
$OrgName = Read-Host "Organization name"
$OrgRootCA = "$OrgName-RootCA"
$OrgIssuingCA = "$OrgName-IssuingCA"
$UserName = Read-Host "User Name as <Firstname>.<Lastname>"
$UPN = "$UserName@$OrgName"
$Email = "$UserName@$OrgName"
$PfxPwdSecureString = Read-Host "Password for certificate export as PFX" -AsSecureString
$PfxPwdEncrypted = ConvertFrom-SecureString -SecureString $PfxPwdSecureString
$CertFilePath = "$ENV:UserProfile\Desktop"

$Thumbprint=Get-ChildItem -Path cert:\LocalMachine\My | where {$_.Subject -like "*CN=$OrgIssuingCA*"} | Select-Object -ExpandProperty Thumbprint
$IssuingCA=Get-ChildItem -Path cert:\LocalMachine\My\$Thumbprint 

# User Zertifikat Parameter definieren und Zertifikat erstellen
$params = @{
   FriendlyName = $UPN
   Signer = $IssuingCA
   Subject = "CN=$UserName, E=$Email, O=$OrgName, OU=1.3.6.1.4.1.<IANA-PEN>, C=DE"
   CertStoreLocation = 'Cert:\CurrentUser\My'
   KeyLength = '4096'
   KeyAlgorithm = 'RSA'
   KeyUsage = 'DataEncipherment','KeyEncipherment','NonRepudiation','DigitalSignature'
   KeyExportPolicy = 'Exportable'
   HashAlgorithm = 'SHA512'
   NotAfter = (Get-Date).AddYears(2)
   Type = 'Custom'
   TextExtension = '2.5.29.37={text}1.3.6.1.5.5.7.3.3,1.3.6.1.5.5.7.3.4,1.3.6.1.5.5.7.3.2,1.3.6.1.4.1.311.10.3.4'
}
$UserCert = New-SelfSignedCertificate @params -type Custom -SmimeCapabilities

# User Zertifikat exportieren
Export-Certificate -Cert $UserCert -FilePath "$CertFilePath\User-$UPN.cer"
Export-PfxCertificate -Cert $UserCert -ChainOption EndEntityCertOnly -FilePath "$CertFilePath\User-$UPN.pfx" -Password (ConvertTo-SecureString -String $PfxPwdEncrypted -Force)
Export-PfxCertificate -Cert $UserCert -ChainOption BuildChain -FilePath "$CertFilePath\User-$UPN-Chain.pfx" -Password (ConvertTo-SecureString -String $PfxPwdEncrypted -Force)

Die Schlüsselverwendungen lassen sich natürlich auch noch einschränken mit Hilfe des Parameters TextExtension, in dem die Werte, die nicht benötigt werden, aus der Liste entfernt werden.

Smart Card Authentication User Zertifikat

Smart Card Zertifikate sind nochmal ein Sonderfall für User Zertifikate. Hierfür gibt es 2 Varianten, wie man die Zertifikate erstellt:

  • mit Microsoft Software Key Storage Provider als CSP
  • mit Microsoft Smart Card Key Storage Provider als CSP

Was ist der Unterschied?
Mit Microsoft Software Key Storage Provider als CSP erfolgt die Erstellung des Zertifikats wie gewohnt über den lokalen Zertifikatsspeicher. Das Zertifikat wird darin erstellt und ist exportierbar, um es manuell auf eine Smart Card zu verteilen.
Mit Microsoft Smart Card Key Storage Provider als CSP erfolgt die Erstellung des Zertifikats direkt in die Smart Card, d.h. es muss eine entsprechende Smart Card am Client vorhanden sein. Zudem ist der Private Key nicht exportierbar. Die Scripte unterscheiden sich daher etwas in ein paar Details.

Microsoft Software Key Storage Provider als CSP

# Variablen abfragen und definieren
$OrgName = Read-Host "Organization name (UPN-/Email-Suffix)"
$OrgRootCA = "$OrgName-RootCA"
$OrgIssuingCA = "$OrgName-IssuingCA"
$UserName = Read-Host "User Name as <Firstname>.<Lastname>"
$UPN = "$UserName@$OrgName"
$Email = "$UserName@$OrgName"
$PfxPwdSecureString = Read-Host "Password for certificate export as PFX" -AsSecureString
$PfxPwdEncrypted = ConvertFrom-SecureString -SecureString $PfxPwdSecureString
$CertFilePath = "$ENV:UserProfile\Desktop"
$Thumbprint = Get-ChildItem -Path Cert:\LocalMachine\My | where {$_.Subject -like "*$OrgIssuingCA*"} | Select-Object -ExpandProperty Thumbprint
$IssuingCA = Get-ChildItem -Path cert:\LocalMachine\My\$Thumbprint 

# Smart Card User Zertifikat Parameter definieren und Zertifikat erstellen
$params = @{
   Signer = $IssuingCA
   KeyLength = '4096'
   KeyAlgorithm = 'RSA'
   KeyFriendlyName = "$UPN"
   KeyExportPolicy = 'Exportable'
   HashAlgorithm = 'SHA512'
   NotBefore = Get-Date
   NotAfter = (Get-Date).AddYears(2)
   FriendlyName = $UPN
   Subject = "CN=$UPN"
   CertStoreLocation = 'Cert:\CurrentUser\My'
   KeyUsage = 'DigitalSignature','KeyEncipherment'
   Type = 'Custom'
   TextExtension = "2.5.29.37={text}1.3.6.1.5.5.7.3.2,1.3.6.1.4.1.311.20.2.2","2.5.29.17={text}upn=$UPN&email=$Email"
}
$SmartCardUserCert = New-SelfSignedCertificate @params

# Smart Card User Zertifikat exportieren
Export-Certificate -Cert $SmartCardUserCert -FilePath "$CertFilePath\SmartCard-$UPN.cer"
Export-PfxCertificate -Cert $SmartCardUserCert -ChainOption EndEntityCertOnly -FilePath "$CertFilePath\SmartCard-$UPN.pfx" -Password (ConvertTo-SecureString -String $PfxPwdEncrypted -Force)
Export-PfxCertificate -Cert $SmartCardUserCert -ChainOption BuildChain -FilePath "$CertFilePath\SmartCard-$UPN-Chain.pfx" -Password (ConvertTo-SecureString -String $PfxPwdEncrypted -Force)

Das Zertifikat wird hierdurch wie gewohnt im lokalen Zertifikatsspeicher erzeugt und dann exportiert.

Microsoft Smart Card Key Storage Provider als CSP

# Variablen abfragen und definieren
$OrgName = Read-Host "Organization name (UPN-/Email-Suffix)"
$OrgRootCA = "$OrgName-RootCA"
$OrgIssuingCA = "$OrgName-IssuingCA"
$UserName = Read-Host "User Name as <Firstname>.<Lastname>"
$UPN = "$UserName@$OrgName"
$Email = "$UserName@$OrgName"
$PIN = Read-Host "SmartCard PIV PIN"
$PfxPwdSecureString = Read-Host "Password for certificate export as PFX" -AsSecureString
$PfxPwdEncrypted = ConvertFrom-SecureString -SecureString $PfxPwdSecureString
$CertFilePath = "$ENV:UserProfile\Desktop"
$Thumbprint = Get-ChildItem -Path Cert:\LocalMachine\My | where {$_.Subject -like "*$OrgIssuingCA*"} | Select-Object -ExpandProperty Thumbprint
$IssuingCA = Get-ChildItem -Path cert:\LocalMachine\My\$Thumbprint 

# Smart Card User Zertifikat Parameter definieren und Zertifikat erstellen
$params = @{
   Signer = $IssuingCA
   KeyLength = '4096'
   KeyAlgorithm = 'RSA'
   KeyFriendlyName = "$UPN"
   HashAlgorithm = 'SHA512'
   NotBefore = Get-Date
   NotAfter = (Get-Date).AddYears(2)
   FriendlyName = $UPN
   Subject = "CN=$UPN"
   KeyUsage = 'DigitalSignature','KeyEncipherment'
   Provider = 'Microsoft Smart Card Key Storage Provider'
   Type = 'Custom'
   TextExtension = "2.5.29.37={text}1.3.6.1.5.5.7.3.2,1.3.6.1.4.1.311.20.2.2","2.5.29.17={text}upn=$UPN&email=$Email"
}
$SmartCardUserCert = New-SelfSignedCertificate @params -Pin $PIN

# Smart Card User Zertifikat exportieren
Export-Certificate -Cert $SmartCardUserCert -FilePath "$CertFilePath\SmartCard-$UPN.cer"
Export-PfxCertificate -Cert $SmartCardUserCert -ChainOption EndEntityCertOnly -FilePath "$CertFilePath\SmartCard-$UPN.pfx" -Password (ConvertTo-SecureString -String $PfxPwdEncrypted -Force)
Export-PfxCertificate -Cert $SmartCardUserCert -ChainOption BuildChain -FilePath "$CertFilePath\SmartCard-$UPN-Chain.pfx" -Password (ConvertTo-SecureString -String $PfxPwdEncrypted -Force)

Das Zertifikat wird hiermit direkt auf die Smart Card importiert und im Anschluss exportiert (hier einmalig inkl. Private Key!). Der Import funktioniert bei z.B. YubiKeys nur, wenn der entsprechende Smart Card Minidriver von Yubico installiert ist. Fehlt dieser, schlägt der Import fehl mit der Meldung The smart card is read-only.

Erklärung Parameter TextExtension

Die Werte in der TextExtension sind in diesem Fall von besonderem Interesse, da hierfür viele Zertifikatserweiterungen konfiguriert werden können/müssen, für die es keinen eigenen Parameter im New-SelfSignedCertificate Cmdlet gibt.

Es gibt hierbei eine Menge Optionen, die ihr im Detail in den u.g. Quellen nachlesen könnte. Für meinen Zweck war es wichtig, die Enhanced Key Usage und die (non-DNS) Subject Alternative Names konfigurieren zu können.

Dabei gelten folgende Syntaxen und ggf. IDs/Token/Werte:

  • Enhanced Key Usage Object Identifier: 2.5.29.37={text}oid,oid
    • 1.3.6.1.5.5.7.3.2 => Client Authentication
    • 1.3.6.1.4.1.311.20.2.2 => Smart Card Sign-In
  • Siubject Alternative Names: 2.5.29.17={text}token=value&token=value
    • upn=%UserPrincipalName%
    • email=%Email-Adresse%

Troubleshooting

Der folgende Fehler tritt auf, wenn bei der Nutzung des Microsoft Smart Card Key Storage Provider der Parameter KeyExportPolicy mit dem Wert Exportable konfiguriert ist:

New-SelfSignedCertificate : CertEnroll::CX509Enrollment::_CreateRequest: The parameter is incorrect. 0x80090027
(-2146893785 NTE_INVALID_PARAMETER)
At line:1 char:1
+ New-SelfSignedCertificate -Provider "Microsoft Smart Card Key Storage ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [New-SelfSignedCertificate], Exception
    + FullyQualifiedErrorId : System.Exception,Microsoft.CertificateServices.Commands.NewSelfSignedCertificateCommand

Wie oben bereits geschrieben ist dieser Wert bei direkter Erstellung des Zertifikats auf der Smart Card nicht zulässig und darf nicht konfiguriert werden. Entweder den Parameter auf NotExportable setzen oder einfach komplett weglassen.


Self-Signed 3-Tier-PKI Creator

Mit meinem PKI Creator Tool könnt ihr 3-stufige PKIs basierend auf Self-Signed Zertifikaten erstellen. Aktuell liegt das Tool in v0.0.0.4 vor.

Change Log:

v 0.0.0.4

  • Check $WorkingDir and create it if it does not exist

v 0.0.0.3

  • Changed user customizable variables from Command Line to Forms Box
  • Added $WorkingDir to variables

v 0.0.0.2

  • Changed some static variables to user customizable via Command Line
  • Added $IanaPen to variables

v 0.0.0.1

  • Initial Release
  • Create PKI with static variable config

Quellen:
https://www.pdq.com/

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

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