Deutsch | English | Français | zurück / back

🛠️ Anleitung zur Einrichtung des Zertifikat-Skripts

📋 Voraussetzungen

📁 Ordnerstruktur

OrdnernameTypBeschreibung
www.domain.comWeb-ZertifikatFür Domains und Server
email@domain.comE-Mail-ZertifikatFür S/MIME und Mailserver

📄 Inhalt pro Ordner

DateiPflichtBeschreibung
domain.keyPrivater Schlüssel
key.txtPasswort
dns.txtCommon Name
ip.txtNur bei Web-Zertifikaten

🔍 Beispiel: www.domain.com

dns.txt:
www.domain.com

ip.txt:
192.168.1.10

key.txt:
meinPasswort123
    

🚀 Einrichtungsschritte

  1. Skript cert_api.php speichern
  2. API-Key eintragen:
    $apiKey = 'DEIN_API_KEY_HIER';
  3. Ordner erstellen und Dateien einfügen
  4. Skript ausführen:
    php cert_api.php

🔐 Sicherheitshinweise

🛠️ Certificate Script Setup Guide

📋 Requirements

📁 Folder Structure

Folder NameTypeDescription
www.domain.comWeb CertificateFor domains and servers
email@domain.comEmail CertificateFor S/MIME and mail servers

📄 Folder Contents

FileRequiredDescription
domain.keyPrivate key
key.txtPassword
dns.txtCommon name
ip.txtOnly for web certificates

🔍 Example: www.domain.com

dns.txt:
www.domain.com

ip.txt:
192.168.1.10

key.txt:
myPassword123
    

🚀 Setup Steps

  1. Save the script cert_api.php
  2. Insert your API key:
    $apiKey = 'YOUR_API_KEY_HERE';
  3. Create folders and add files
  4. Run the script:
    php cert_api.php

🔐 Security Notes

🛠️ Guide d'installation du script de certificat

📋 Prérequis

📁 Structure des dossiers

Nom du dossierTypeDescription
www.domain.comCertificat WebPour les domaines et serveurs
email@domain.comCertificat EmailPour S/MIME et serveurs mail

📄 Contenu du dossier

FichierObligatoireDescription
domain.keyClé privée
key.txtMot de passe
dns.txtNom commun
ip.txtSeulement pour les certificats Web

🔍 Exemple : www.domain.com

dns.txt:
www.domain.com

ip.txt:
192.168.1.10

key.txt:
monMotDePasse123
  

🔍 Exemple : email@domain.com

dns.txt:
email@domain.com

key.txt:
motDePasseEmail
  

🚀 Étapes d'installation

  1. Enregistrer le script cert_api.php
  2. Insérer la clé API :
    $apiKey = 'VOTRE_CLÉ_API_ICI';
  3. Créer les dossiers et ajouter les fichiers
  4. Exécuter le script :
    php cert_api.php

🔐 Conseils de sécurité

📄 Example Script

🇩🇪 Deutsch
Dieses PHP-Skript durchsucht alle Unterordner, erkennt automatisch Web- oder E-Mail-Zertifikate und sendet die Daten an die CERT-API. Das Ergebnis wird als cert.pem im jeweiligen Ordner gespeichert.
Falls die Datei cert.pem gelöscht wird, wird beim nächsten Durchlauf automatisch ein neues Zertifikat erstellt.

🇬🇧 English
This PHP script scans all subfolders, automatically detects web or email certificates, and sends the data to the CERT API. The result is saved as cert.pem in the corresponding folder.
If the cert.pem file is deleted, a new certificate will be automatically created during the next run.

🇫🇷 Français
Ce script PHP analyse tous les sous-dossiers, détecte automatiquement les certificats Web ou Email, et envoie les données à l'API CERT. Le résultat est enregistré sous le nom cert.pem dans le dossier correspondant.
Si le fichier cert.pem est supprimé, un nouveau certificat sera automatiquement généré lors de la prochaine exécution.

<?php
// Basisverzeichnis – dort, wo das Skript liegt
$baseDir = __DIR__;
$apiUrl = 'https://www.erhardt-fbt.com/public/cert_api/';
$apiKey = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'; // zentraler API-Key

// Alle Unterordner im Basisverzeichnis finden
$entries = scandir($baseDir);

// Korrektur: Stellen Sie sicher, dass $entries kein 'false' (Fehler) ist
if ($entries === false) {
    echo "❌ FEHLER: Konnte das Verzeichnis '$baseDir' nicht lesen. Überprüfen Sie die Dateiberechtigungen.\n";
    exit(1);
}

// $folders ist nun definitiv ein Array, auch wenn es leer ist
$folders = array_filter($entries, function ($entry) use ($baseDir) {
    return $entry !== '.' && $entry !== '..' && is_dir("$baseDir/$entry");
});

foreach ($folders as $folderName) {
    $folderPath = "$baseDir/$folderName";

    // Schlüsseldateien definieren
    // ACHTUNG: Der Name der Schlüsseldatei (z.B. 'mein-schluessel.key') muss angepasst werden!
    $keyFile = "$folderPath/[PRIVAT_KEY_DATEINAME].key";      // privater Schlüssel
    $keyPassFile = "$folderPath/key.txt";                     // Passwort für den Schlüssel
    $dnsFile = "$folderPath/dns.txt";                         // optional für Web
    $ipFile = "$folderPath/ip.txt";                           // optional für Web

    // NEU: Dateinamen für das JSON-Bundle
    $certFile = "$folderPath/cert.pem";
    $chainFile = "$folderPath/chain.pem";
    $fullchainFile = "$folderPath/fullchain.pem";
    $newKeyFile = "$folderPath/private.key"; // Der entschlüsselte Schlüssel vom Server

    // Prüfen, ob Schlüssel und Passwort vorhanden sind
    if (!file_exists($keyFile) || !file_exists($keyPassFile)) {
        echo "❌ Überspringe '$folderName' – fehlende Schlüsseldateien\n";
        continue;
    }

    // Prüfen, ob Zertifikat bereits existiert und gültig aussieht
    // HINWEIS: Prüft nur die Cert-Datei, die Existenz der anderen wird akzeptiert.
    if (file_exists($certFile) && filesize($certFile) > 1000) {
        echo "✅ Zertifikat für '$folderName' bereits vorhanden – überspringe.\n";
        continue;
    }

    // Schlüssel und Passwort laden
    $privateKey = file_get_contents($keyFile);
    $keyPass = trim(file_get_contents($keyPassFile));

    // Typ erkennen: E-Mail oder Web
    $isEmail = strpos($folderName, '@') !== false;
    $type = $isEmail ? 'emailProtection' : 'serverAuth';

    // DNS/IP laden (nur für Web-Zertifikate)
    $dnsList = file_exists($dnsFile) ? file($dnsFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) : [];
    $ipList = file_exists($ipFile) ? file($ipFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) : [];

    // Daten für API vorbereiten
    $data = [
        'private_key' => $privateKey,
        'keypass' => $keyPass,
        'api_key' => $apiKey,
        'type' => $type,
        'dns' => [implode("\n", $dnsList)],
        'ip' => [implode("\n", $ipList)]
    ];


    // Anfrage an die API senden
    $ch = curl_init($apiUrl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);

    // Wichtig: Wir akzeptieren nun sowohl JSON als auch PEM
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'Content-Type: application/json',
        'Accept: application/json, application/x-pem-file'
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));

    // Header-Informationen erfassen
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $contentType = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
    curl_close($ch);


// Example for Postfix and Dovecot incl. Restart
// Ergebnis speichern oder Fehler melden
    if ($httpCode === 200) {
        // 1. Prüfen, ob JSON-Bundle zurückkam (bevorzugt)
        if (strpos($contentType, 'application/json') !== false) {
            $bundle = json_decode($response, true);

            if ($bundle && isset($bundle['success']) && $bundle['success'] === true) {

                // Standard-Dateien im jeweiligen Domain-Ordner speichern
                file_put_contents($certFile, $bundle['cert_only']);
                file_put_contents($newKeyFile, $bundle['private_key']);
                chmod($newKeyFile, 0600);

                if (!empty($bundle['sub_ca_only'])) {
                    file_put_contents($chainFile, $bundle['sub_ca_only']);
                    // ZUSÄTZLICH: Als explizite subca.pem für den Zertifikatsspeicher ablegen
                    file_put_contents("$folderPath/subca.pem", $bundle['sub_ca_only']);
                }

                // ZUSÄTZLICH: Das nackte Wurzelzertifikat (Root-CA) separat ablegen
                if (!empty($bundle['root_ca_only'])) {
                    file_put_contents("$folderPath/root.pem", $bundle['root_ca_only']);
                }

                // 1.) Die Fullchain für den Webserver (inkl. Root für FritzBox & Co.)
                if (!empty($bundle['chain_with_root'])) {
                    file_put_contents($fullchainFile, $bundle['chain_with_root']);
                }

                // 2.) Die reine Chain für Postfix (Cert + Sub) im gleichen Ordner ablegen
                if (!empty($bundle['chain_standard'])) {
                    file_put_contents("$folderPath/postfix-chain.pem", $bundle['chain_standard']);
                }

                // 3.) Das Combined-File für Dovecot (Key + Cert + Sub) im gleichen Ordner ablegen
                if (!empty($bundle['dovecot_combined'])) {
                    $dovecotFile = "$folderPath/dovecot-combined.pem";
                    file_put_contents($dovecotFile, $bundle['dovecot_combined']);
                    chmod($dovecotFile, 0600);
                }

                echo "✅ Alle Zertifikats-Formate für '$folderName' erfolgreich im Ordner abgelegt.\n";

                // Dienste neu laden, da die Dateien jetzt überall bereitliegen
                shell_exec("postmap -F /etc/postfix/vmail_ssl.map");
                shell_exec("systemctl reload postfix 2>&1");
                shell_exec("systemctl restart dovecot 2>&1");
                echo "🔄 Mailserver-Dienste (Postfix & Dovecot) wurden aktualisiert.\n";

                continue;

            } else {
                echo "⚠️ Fehler bei '$folderName': API meldete Erfolg = false oder JSON war ungültig.\n";
                if (isset($bundle['error'])) {
                    echo "Details: " . $bundle['error'] . "\n";
                }
            }

        } elseif (strpos($contentType, 'application/x-pem-file') !== false) {
            // 2. Fallback: Nur das einfache Zertifikat kam zurück
            file_put_contents($certFile, $response);
            echo "✅ Zertifikat (reine PEM-Datei) für '$folderName' gespeichert.\n";

        } else {
            echo "⚠️ Fehler bei '$folderName': Unerwarteter Content-Type '$contentType'.\n";
        }

    } else {
        // 3. Fehler melden (HTTP Code ist nicht 200)
        echo "⚠️ Fehler bei '$folderName': HTTP $httpCode\n";
        $error = json_decode($response, true);
        if ($error && isset($error['error'])) {
            echo "Fehlerdetail: " . $error['error'] . "\n";
        }
    }





}
?>