colinardo
Goto Top

Powershell: StartSSL StartAPI für das automatisierte Validieren und Anfordern von SSL-Zertifikaten verwenden

back-to-top WICHTIGER HINWEIS:

back-to-topStartCom und WoSign Zertifikate werden seit einiger Zeit von den großen Betriebsystemen und Browsern nicht mehr als Vertrauenswürdig eingestuft. Bitte beachten sie dies bei der Verwendung dieser Anleitung!

Mozilla Security Blog - Distrusting New WoSign and StartCom Certificates
Microsoft to remove WoSign and StartCom certificates in Windows 10 (8. August 2017)

Neuerdings stellt auch StartCom (http://www.startssl.com) ein Entwickler-API bereit um kostenlose Zertifikate automatisiert abzurufen.
Bisher war es dort ja bei kostenlosen Zertifikaten nur mit regelmäßigem manuellem Aufwand zu bewerkstelligen.

Also habe ich mal ein Powershell-Skript geschrieben das das Validieren einer Domain und das anschließende Abrufen eines kostenlosen Zertifikates ermöglicht. Enthalten ist die Validierung der Domain über den Upload einer HTML Datei auf den Webserver der Domain welche den nötigen Validierungscode von StartSSL enthält.

Es folgt nun eine Beschreibung der Vorgehensweise für das Anfordern eines kostenlosen DV-Zertifikats für eine Domain (max. 5 SAN kein Wildcard).
WICHTIG: Bitte alles lesen. Voreingestellt ist das Test-API die Zertifikate sind also nur 1 Tag gültig. Zur Umstellung auf das Produktiv-API siehe die Variablen-Tabelle ($global:APIURL)

back-to-topAnfordern eines API-Authentifizierungszertifikats

Zuerst sollte man eine neue E-Mail Validierung wie üblich vornehmen (Hinweis: Es darf nicht die E-Mail sein die für den StartSSL-Account verwendet wird)

screenshot

Ist das erledigt, folgt das Anfordern der API-Authentifizierungsdaten:

screenshot

Neues API-Zertifikat generieren und herunterladen:

screenshot

Das *.p12 Zertifikat speichert man sich im Verzeichnis in dem später das PS-Skript liegt.

Ebenfalls kopiert man sich die API-Token ID und fügt sie im Skript im Variablenbereich ein

screenshot

back-to-topAnpassungen des Skripts

Im Skript-Header befinden sich ein Variablenbereich für die Anpassung des Skripts.
Es folgt nun eine Tabelle mit Erläuterungen zu den anpassbaren Variablen:

VariableBedeutung
$global:domains Hier werden die FQDNs mit Komma getrennt für das Zertifkat hinterlegt. Die erste Domain wird der CN (Common Name) des Zertifikates
$global:api_token Das API Token welches wir aus dem Account von StartSSL kopiert haben
$global:auth_cert_path Der Pfad und Name des API-Authentifizierungs-Zertifikats (Default ist hier im Pfad des PS-Skripts)
$global:auth_cert_password Das Passwort des API-Authentifizierungs-Zertifikats
$global:outpath Der Pfad in dem die heruntergeladenen Zertifikate abgelegt werden
$global:ftp_path Die komplette URL auf dem FTP-Server welches der ersten Domain in der Variablen $global:domains entpricht. Dies wird benötigt wenn das Skript die Validierung der Domain übernehmen soll. Ist das nicht gewollt muss die Validierung manuell im Account von StartSSL vorgenommen werden. ACHTUNG. Validierungen sind meist immer nur für 30 Tage gültig!
$global:ftp_username und $global:ftp_username Username und Passwort für den FTP-Account
$global:csr Der CSR (Certificate Signing Request) für das anzufordernde Zertifikat. Diesen Request erstellt man sich mit den gängigen Tools und fügt den CSR ins Skript ein.
$global:APIURL Die API-URL. Voreingestellt ist die Test-API URL damit Ihr das Skript bei euch erst einmal testen könnt. Läuft es dann so wie gewünscht stellt man die URL auf die Produktiv-URL 'https://api.startssl.com'

back-to-topPowershell-Skript

# =============================================================================
# StartSSL automated domain validation and certificate request / (c) @colinardo
# =============================================================================

# ==== Start Variable section =======================================================
# comma seperated list of domains to create certificate for (first gets common name CN and others SAN)
$global:domains = 'domainXY.de,sub1.domainXY.de,sub2.domainXY.de'  
# Personal API-Token from startssl.com
$global:api_token = 'tk_XXXXXXXXXXXXXXXXXXXXXXXXX'  
# Path to API-Authentification certificate
$global:auth_cert_path = "$(Split-Path $MyInvocation.MyCommand.Definition -Parent)\my_domain_auth_certificate.p12"  
# Password of API-Authentification certificate
$global:auth_cert_password = 'VeryStrongPassword'  
# Path to store downloaded certificates to (default same as script path)
$global:outpath = Split-Path $MyInvocation.MyCommand.Definition -Parent
# ---- FTP-Data -------
# FTP-Path which corresponds to CN-Domain
$global:ftp_path = 'ftp://ftp.domain.de/subpath'  
# FTP-Username
$global:ftp_username = 'FTP-USERNAME'  
# FTP-Password
$global:ftp_password =  'FTP-PASSWORD'  

# certificate request
$global:csr = '-----BEGIN CERTIFICATE REQUEST-----  
MIICpDCCAYwCAQAwFjEUMBIGA1UEAxMLcm9ldGdlbi5uZXQwggEiMA0GCSqGSIb3
....
....
MmNmcPSKaJIFF71jXT7fhmkwvwwJUlIqrGgVMvpCEnZ8dPfdPCI3+ymaGMzcsW2U
bXCudv/Uf5w=
-----END CERTIFICATE REQUEST-----'  

# API URL Test-API: 'https://apitest.startssl.com' / Production-API: 'https://api.startssl.com' 
$global:APIURL = 'https://apitest.startssl.com'  

# <<<< End Variable section =======================================================

if ($PSVersionTable.PSVersion.Major -lt 3){write-host "ERROR: Minimum Powershell Version 3.0 is required!" -F Yellow; return}  

# create x509 certificate object from authentification cert file
$global:auth_cert = new-object System.Security.Cryptography.X509Certificates.X509Certificate2($global:auth_cert_path,$global:auth_cert_password)

# >>> Start Helper functions section =============================================
function md5([string]$string){
    [System.BitConverter]::ToString([System.Security.Cryptography.MD5]::Create().ComputeHash([System.Text.Encoding]::UTF8.GetBytes($string))).replace('-','').toLower()  
}
# <<<< End Helper functions section =============================================

# ------------------------------------------------------
# Fetches certificate from StartSSL
# (Returns a X509 certificate-object and saves ther certificate to defined outputfolder)
# ------------------------------------------------------
function Get-StartSSLCertificate(){
    param(
        [parameter(mandatory=$true)][ValidateSet('DVSSL','IVSSL','OVSSL','EVSSL')][string]$certType = 'DVSSL',  
        [parameter(mandatory=$true)][string]$domain,
        [parameter(mandatory=$true)][string]$csr
    )
    $body = @{
        tokenID = $global:api_token
        actionType = 'ApplyCertificate'  
        certType = $certType
        domains = $domain
        CSR = $csr
    } | ConvertTo-Json
    $body = "RequestData=$([System.Net.WebUtility]::UrlEncode($body))"  
    try{
        $result = irm -Uri $global:APIURL -Body $body -Method Post -Certificate $global:auth_cert -ContentType 'application/x-www-form-urlencoded'  
        if ($result.status -eq 1 -and $result.data.orderStatus -eq 2){
            if ((md5 $result.data.certificate) -eq $result.data.certificateFieldMD5){
                $outcert = "$outpath\$($domain.split(',')).crt"  
                write-host "Request was successfull, saving certificate to '$outcert' ..." -F Green  
                [System.IO.File]::WriteAllBytes($outcert,[System.Convert]::FromBase64String($result.data.certificate))
                return [System.Security.Cryptography.X509Certificates.X509Certificate]::CreateFromCertFile($outcert)
            }else{
                throw "There was a problem in certificate transmission, the MD5 of the submitted certificate did not match!"  
            }
        }elseif($result.status -eq 0 -and $result.errorCode -eq -1025){
            # domain name is not validated, initiate validation
            write-host "Domain is not validated, initiating validation process ..." -F Yellow  
            $val_result = Validate-StartSSLDomain -domain ($domain.split(','))  
            if ($val_result.status -eq 1){
                Get-StartSSLCertificate -certType $certType -domain $domain -csr $csr
            }else{throw $val_result}
        }else{
            throw $result
        }

    }catch{
        throw $_
    }
}

# ---------------------------------------------------------------
# Validate Domain ownership via html file upload to ftp of domain
# ---------------------------------------------------------------
function Validate-StartSSLDomain(){
    param(
        [parameter(mandatory=$true)][string]$domain
    )
    function Upload-File([string]$path,[string]$url,$username,$password){
        try{
            $request = [System.Net.FtpWebRequest]::Create($url)
            $request.Method = [System.Net.WebRequestMethods+FTP]::UploadFile
            $request.Credentials = New-Object System.Net.NetworkCredential($username,$password)
            [bytes[]] $bytes = [System.IO.File]::ReadAllBytes($path)
            [System.IO.Stream]$stream = $request.GetRequestStream();
            $stream.Write($bytes,0,$bytes.Length)
            $stream.Close(); $stream.Dispose()
            $response = [System.Net.FtpWebResponse]$request.GetResponse()
            $result = $response.StatusDescription
            $response.Close()
            return $result
        }catch{
            throw $_.Exception.Message
            return $false
        }
    }
    function Delete-FtpFile([string]$url,$username,$password){
        try{
            $request = [System.Net.FtpWebRequest]::Create($url)
            $request.Method = [System.Net.WebRequestMethods+FTP]::DeleteFile
            $request.Credentials = New-Object System.Net.NetworkCredential($username,$password)
            $response = [System.Net.FtpWebResponse]$request.GetResponse()
            $result = $response.StatusDescription
            $response.Close()
            return $result
        }catch{
            throw $_.Exception.Message
            return $false
        }
    }
    $body = @{
        tokenID = $global:api_token
        actionType = 'ApplyWebControl'  
        hostname = $domain
    } | ConvertTo-Json
    $body = "RequestData=$([System.Net.WebUtility]::UrlEncode($body))"  
    try{
        # first request for domain validation
        $result = irm -Uri $global:APIURL -Body $body -Method Post -Certificate $global:auth_cert -ContentType 'application/x-www-form-urlencoded'  
        if($result.status -eq 1){
            # upload validation html file to ftp server
            $result.data | Set-Content "$env:TEMP\$domain.html"  
            write-host "Uploading validation file to ftp server..." -F Green  
            Upload-File -path "$env:TEMP\$domain.html" -url "$ftpfolder/$domain.html" -username $global:ftp_username -password $global:ftp_password  

            # second request to initiate domain ownership validation
            $body = @{
                tokenID = $global:api_token
                actionType = 'WebControlValidation'  
                hostname = $domain
            } | ConvertTo-Json
            $body = "RequestData=$([System.Net.WebUtility]::UrlEncode($body))"  
            write-host "Validating domain ownership..." -F Green  
            $result = irm -Uri $global:APIURL -Body $body -Method Post -Certificate $global:auth_cert -ContentType 'application/x-www-form-urlencoded'  
            if($result.status -eq 1){
                write-host "Validation successful, removing validation file from ftp..." -ForegroundColor Green  
                Delete-FtpFile "$ftpfolder/$domain.html" -username $global:ftp_username -password $global:ftp_password  
                return $result
            }else{throw $result}
        }else{
            throw $result
        }
    }catch{
        throw $_
    }
}

# Initiate certificate process
Get-StartSSLCertificate -certType DVSSL -domain $domains -csr $global:csr

back-to-topWichtige Hinweise

Voreingestellt ist das Test-API, für eure Tests. Wollt Ihr auf das Produktiv-API umstellen macht Ihr das indem Ihr die API-URL in der Variablen $global:APIURL auf
$global:APIURL = 'https://api.startssl.com'
umstellt.

Die automatisierte Domain-Validierung funktioniert hier folgendermaßen: Es wird vom API ein Validation-Code angefordert der dann in einem HTML-File auf den FTP-Server der primären Domain hochgeladen wird. Anschließend wird die Validierung per API angestoßen. Dabei ruft das API diese HTML-Datei ab und überprüft den Inhalt der Datei. Kann die Datei mit dem richtigen Code abgerufen werden ist die Validierung erfolgreich und das Zertifikat kann nun angefordert werden.

Wie immer alles ohne Gewähr auf Vollständigkeit und Fehlerfreiheit. Getestet wurde es hier mit einem kostenlosen Account bei StartSSL mit der einfachsten Klasse 1 Validierung.
Für die Einbindung des Zertfikates in eure Anwendungen seit Ihr ja frei das Skript um die entsprechenden Schritte z.B. für Exchange etc. zu erweitern.


Wünsche nun viel Spaß mit dem Skript.
Gruß @colinardo

Content-Key: 307762

Url: https://administrator.de/contentid/307762

Printed on: June 15, 2024 at 13:06 o'clock