Inhaltsverzeichnis

Zertifikats-Inventur

Komplexität: Niedrig
Dauer: 1-4 Stunden (je nach Größe)
Voraussetzung: Zugang zu allen Systemen

Vollständige Bestandsaufnahme aller Zertifikate als Grundlage für die Migration.


Warum Inventur?

Grund Beschreibung
——-————–
Scope Wie viele Zertifikate müssen migriert werden?
Algorithmen Welche Algorithmen sind im Einsatz?
Ablaufzeiten Wann können Zertifikate bei Renewal migriert werden?
Abhängigkeiten Welche Systeme hängen zusammen?
Risiken Wo sind kritische Zertifikate?

Inventur-Quellen

flowchart TB subgraph LOCAL["💻 LOKAL"] L1[Dateisystem] L2[Zertifikats-Stores] L3[Konfigurationsdateien] end subgraph NETWORK["🌐 NETZWERK"] N1[TLS-Endpoints] N2[LDAP/AD] N3[HSM] end subgraph MGMT["📊 MANAGEMENT"] M1[CA-Datenbank] M2[CMDB] M3[Monitoring] end subgraph OUTPUT["📋 OUTPUT"] O[Inventur-CSV] end L1 & L2 & L3 --> O N1 & N2 & N3 --> O M1 & M2 & M3 --> O


Linux: Dateisystem-Scan

#!/bin/bash
# cert-inventory-linux.sh
 
OUTPUT="inventory-linux-$(hostname)-$(date +%Y%m%d).csv"
echo "Pfad,Subject,Issuer,Algorithmus,Schlüssellänge,Gültig_ab,Gültig_bis,Tage_verbleibend,Serial,SANs" > "$OUTPUT"
 
# Standard-Verzeichnisse
CERT_DIRS=(
    "/etc/ssl/certs"
    "/etc/pki/tls/certs"
    "/etc/nginx/ssl"
    "/etc/apache2/ssl"
    "/opt/*/ssl"
    "/var/lib/docker/volumes/*/ssl"
)
 
for dir in "${CERT_DIRS[@]}"; do
    for cert in $(find $dir -name "*.pem" -o -name "*.crt" -o -name "*.cer" 2>/dev/null); do
        # Nur Zertifikate (keine Keys/CSRs)
        openssl x509 -in "$cert" -noout 2>/dev/null || continue
 
        subject=$(openssl x509 -in "$cert" -subject -noout | sed 's/subject=//' | tr ',' ';')
        issuer=$(openssl x509 -in "$cert" -issuer -noout | sed 's/issuer=//' | tr ',' ';')
        algo=$(openssl x509 -in "$cert" -text -noout | grep "Signature Algorithm" | head -1 | awk '{print $3}')
        keysize=$(openssl x509 -in "$cert" -text -noout | grep "Public-Key:" | grep -oP '\d+')
        not_before=$(openssl x509 -in "$cert" -startdate -noout | cut -d= -f2)
        not_after=$(openssl x509 -in "$cert" -enddate -noout | cut -d= -f2)
        days_left=$(( ($(date -d "$not_after" +%s) - $(date +%s)) / 86400 ))
        serial=$(openssl x509 -in "$cert" -serial -noout | cut -d= -f2)
        sans=$(openssl x509 -in "$cert" -text -noout | grep -A1 "Subject Alternative Name" | tail -1 | tr ',' ';')
 
        echo "\"$cert\",\"$subject\",\"$issuer\",\"$algo\",\"$keysize\",\"$not_before\",\"$not_after\",\"$days_left\",\"$serial\",\"$sans\"" >> "$OUTPUT"
    done
done
 
echo "Inventur abgeschlossen: $OUTPUT"
echo "Zertifikate gefunden: $(tail -n +2 "$OUTPUT" | wc -l)"

Windows: Zertifikats-Store Scan

# Cert-Inventory-Windows.ps1
 
param(
    [string]$OutputPath = "inventory-windows-$env:COMPUTERNAME-$(Get-Date -Format 'yyyyMMdd').csv"
)
 
$results = @()
 
# Alle Certificate Stores durchsuchen
$stores = @(
    @{ Location = "LocalMachine"; Name = "My" },
    @{ Location = "LocalMachine"; Name = "Root" },
    @{ Location = "LocalMachine"; Name = "CA" },
    @{ Location = "LocalMachine"; Name = "WebHosting" },
    @{ Location = "CurrentUser"; Name = "My" }
)
 
foreach ($store in $stores) {
    $storePath = "Cert:\$($store.Location)\$($store.Name)"
 
    Get-ChildItem $storePath -ErrorAction SilentlyContinue | ForEach-Object {
        $cert = $_
        $daysLeft = ($cert.NotAfter - (Get-Date)).Days
 
        # SANs extrahieren
        $sanExt = $cert.Extensions | Where-Object { $_.Oid.Value -eq "2.5.29.17" }
        $sans = if ($sanExt) { $sanExt.Format($false) } else { "" }
 
        $results += [PSCustomObject]@{
            Store = "$($store.Location)\$($store.Name)"
            Subject = $cert.Subject
            Issuer = $cert.Issuer
            Algorithm = $cert.SignatureAlgorithm.FriendlyName
            KeySize = $cert.PublicKey.Key.KeySize
            NotBefore = $cert.NotBefore
            NotAfter = $cert.NotAfter
            DaysLeft = $daysLeft
            Serial = $cert.SerialNumber
            Thumbprint = $cert.Thumbprint
            SANs = $sans
            HasPrivateKey = $cert.HasPrivateKey
        }
    }
}
 
$results | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
Write-Host "Inventur abgeschlossen: $OutputPath"
Write-Host "Zertifikate gefunden: $($results.Count)"

Netzwerk: TLS-Endpoint Scan

#!/bin/bash
# cert-inventory-network.sh
 
OUTPUT="inventory-network-$(date +%Y%m%d).csv"
ENDPOINTS_FILE="endpoints.txt"
 
echo "Endpoint,Subject,Issuer,Algorithmus,Schlüssellänge,Gültig_bis,Tage,Serial" > "$OUTPUT"
 
# Endpoints aus Datei oder CMDB/DNS
# Format: hostname:port
cat "$ENDPOINTS_FILE" | while read endpoint; do
    host=${endpoint%:*}
    port=${endpoint#*:}
    [ -z "$port" ] && port=443
 
    echo "Scanning $host:$port..."
 
    cert_info=$(echo | openssl s_client -connect "$host:$port" -servername "$host" 2>/dev/null | openssl x509 -text -noout 2>/dev/null)
 
    if [ -n "$cert_info" ]; then
        subject=$(echo "$cert_info" | grep "Subject:" | head -1 | sed 's/.*Subject: //' | tr ',' ';')
        issuer=$(echo "$cert_info" | grep "Issuer:" | head -1 | sed 's/.*Issuer: //' | tr ',' ';')
        algo=$(echo "$cert_info" | grep "Signature Algorithm" | head -1 | awk '{print $3}')
        keysize=$(echo "$cert_info" | grep "Public-Key:" | grep -oP '\d+')
        not_after=$(echo | openssl s_client -connect "$host:$port" -servername "$host" 2>/dev/null | openssl x509 -enddate -noout | cut -d= -f2)
        days_left=$(( ($(date -d "$not_after" +%s) - $(date +%s)) / 86400 ))
        serial=$(echo | openssl s_client -connect "$host:$port" -servername "$host" 2>/dev/null | openssl x509 -serial -noout | cut -d= -f2)
 
        echo "\"$endpoint\",\"$subject\",\"$issuer\",\"$algo\",\"$keysize\",\"$not_after\",\"$days_left\",\"$serial\"" >> "$OUTPUT"
    else
        echo "\"$endpoint\",\"CONNECTION FAILED\",\"\",\"\",\"\",\"\",\"\",\"\"" >> "$OUTPUT"
    fi
done
 
echo "Netzwerk-Inventur abgeschlossen: $OUTPUT"

CA-Datenbank Inventur

#!/bin/bash
# cert-inventory-ca.sh - Aus OpenSSL CA-Index
 
CA_INDEX="/etc/pki/CA/index.txt"
OUTPUT="inventory-ca-$(date +%Y%m%d).csv"
 
echo "Status,Serial,Ablauf,Subject,Datei" > "$OUTPUT"
 
while IFS=$'\t' read -r status expiry revoke serial unknown subject; do
    # Status: V=Valid, R=Revoked, E=Expired
    status_text=""
    case "$status" in
        V) status_text="Valid" ;;
        R) status_text="Revoked" ;;
        E) status_text="Expired" ;;
    esac
 
    # Ablaufdatum konvertieren (YYMMDDHHMMSSZ Format)
    expiry_formatted=$(date -d "20${expiry:0:2}-${expiry:2:2}-${expiry:4:2}" +%Y-%m-%d 2>/dev/null)
 
    # Zertifikatsdatei
    cert_file="/etc/pki/CA/newcerts/${serial}.pem"
 
    echo "\"$status_text\",\"$serial\",\"$expiry_formatted\",\"$subject\",\"$cert_file\"" >> "$OUTPUT"
done < "$CA_INDEX"
 
echo "CA-Inventur abgeschlossen: $OUTPUT"
echo "Zertifikate:"
echo "  Valid:   $(grep -c "^\"Valid\"" "$OUTPUT")"
echo "  Revoked: $(grep -c "^\"Revoked\"" "$OUTPUT")"
echo "  Expired: $(grep -c "^\"Expired\"" "$OUTPUT")"

Analyse & Reporting

#!/usr/bin/env python3
# analyze-inventory.py
 
import pandas as pd
from datetime import datetime
 
# Alle Inventur-Dateien laden
df = pd.concat([
    pd.read_csv('inventory-linux*.csv'),
    pd.read_csv('inventory-windows*.csv'),
    pd.read_csv('inventory-network*.csv')
])
 
# Analyse
print("=== Inventur-Zusammenfassung ===\n")
 
print(f"Gesamtzahl Zertifikate: {len(df)}")
 
print("\n--- Nach Algorithmus ---")
print(df['Algorithmus'].value_counts())
 
print("\n--- Nach Schlüssellänge ---")
print(df['Schlüssellänge'].value_counts())
 
print("\n--- Ablauf-Analyse ---")
df['Tage_verbleibend'] = pd.to_numeric(df['Tage_verbleibend'], errors='coerce')
print(f"Abgelaufen:       {len(df[df['Tage_verbleibend'] < 0])}")
print(f"< 30 Tage:        {len(df[(df['Tage_verbleibend'] >= 0) & (df['Tage_verbleibend'] < 30)])}")
print(f"30-90 Tage:       {len(df[(df['Tage_verbleibend'] >= 30) & (df['Tage_verbleibend'] < 90)])}")
print(f"90-365 Tage:      {len(df[(df['Tage_verbleibend'] >= 90) & (df['Tage_verbleibend'] < 365)])}")
print(f"> 1 Jahr:         {len(df[df['Tage_verbleibend'] >= 365])}")
 
print("\n--- Migration-Potential ---")
rsa = len(df[df['Algorithmus'].str.contains('rsa', case=False, na=False)])
ecdsa = len(df[df['Algorithmus'].str.contains('ecdsa|ec', case=False, na=False)])
hybrid = len(df[df['Algorithmus'].str.contains('ml-dsa|hybrid', case=False, na=False)])
print(f"RSA (zu migrieren):     {rsa}")
print(f"ECDSA (zu migrieren):   {ecdsa}")
print(f"Hybrid (bereits):       {hybrid}")
 
# Export für Migration-Planung
df.to_excel('inventory-complete.xlsx', index=False)
print("\nVollständige Inventur exportiert: inventory-complete.xlsx")

Dashboard-Vorlage

Kategorie Anzahl Migration
———–——–———–
Algorithmus
RSA-2048 150 → Hybrid
RSA-4096 30 → Hybrid
ECDSA P-256 80 → Hybrid
ECDSA P-384 20 → Hybrid
ML-DSA/Hybrid 5 ✓ Fertig
Ablauf
< 30 Tage 12 Sofort migrieren
30-90 Tage 25 Phase 1
90-365 Tage 100 Phase 2
> 1 Jahr 148 Phase 3
Kritikalität
Extern-facing 40 Priorität 1
Intern-kritisch 80 Priorität 2
Development 165 Priorität 3

Checkliste

# Prüfpunkt
———–
1 Alle Linux-Server gescannt
2 Alle Windows-Server gescannt
3 Alle TLS-Endpoints gescannt
4 CA-Datenbank exportiert
5 Daten konsolidiert
6 Analyse durchgeführt
7 Migration-Plan erstellt

Verwandte Dokumentation


« ← Rollback-Strategie | → Operator-Szenarien »


Wolfgang van der Stille @ EMSR DATA d.o.o. - Post-Quantum Cryptography Professional