Lehrmittelzuschuss – Antrag (Demo)

Hier ist eine minimale, lauffähige Demo: ein kleines HTML-Formular mit Canvas-Unterschrift + ein PHP-Handler, der alle Daten in ein ZIP packt, mit PGP verschlüsselt und per Mail verschickt.

1) public/index.html

<!doctype html><html lang="de"><head>
  <meta charset="utf-8" />
  <title>Lehrmittelzuschuss &#8211; Antrag (Demo)</title>
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <style>
    body { font-family: system-ui, sans-serif; margin: 2rem; max-width: 800px; }
    label { display:block; margin-top: .75rem; font-weight: 600; }
    input, textarea { width: 100%; padding: .5rem; }
    canvas { border: 1px solid #ccc; border-radius: 8px; width: 100%; height: 200px; touch-action: none; }
    .row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
    button { padding: .6rem 1rem; margin-right: .5rem; }
  </style></head><body>
  <h1>Lehrmittelzuschuss &#8211; Antrag (Demo)</h1>

  <form action="/submit.php" method="post" enctype="multipart/form-data">
    <div class="row">
      <div>
        <label>Ihr Name</label>
        <input name="name" required>
      </div>
      <div>
        <label>E-Mail</label>
        <input type="email" name="email" required>
      </div>
    </div>

    <div class="row">
      <div>
        <label>Kind (Name)</label>
        <input name="child_name" required>
      </div>
      <div>
        <label>Geburtsdatum Kind</label>
        <input type="date" name="child_dob" required>
      </div>
    </div>

    <label>Nachweise (mehrere möglich)</label>
    <input type="file" name="attachments[]" multiple accept=".pdf,.png,.jpg,.jpeg,.heic,.webp">

    <label>Unterschrift</label>
    <canvas id="sig" width="800" height="200"></canvas>
    <div style="margin:.5rem 0 1rem">
      <button type="button" id="clear">Unterschrift löschen</button>
    </div>
    <input type="hidden" name="signature_png" id="signature_png" required>

    <button type="submit">Sicher absenden</button>
  </form>

  <script>
    // Simple Signature Pad (ohne externe Lib)
    const canvas = document.getElementById('sig');
    const ctx = canvas.getContext('2d');
    let drawing = false, last = null;

    function pos(e){
      const r = canvas.getBoundingClientRect();
      const t = (touch = e.touches && e.touches[0]) ? touch : e;
      return { x: (t.clientX - r.left) * (canvas.width / r.width),
               y: (t.clientY - r.top)  * (canvas.height / r.height) };
    }
    function start(e){ drawing = true; last = pos(e); e.preventDefault(); }
    function end(){ drawing = false; last = null; }
    function move(e){
      if(!drawing) return;
      const p = pos(e);
      ctx.lineWidth = 2.0; ctx.lineCap = 'round';
      ctx.beginPath(); ctx.moveTo(last.x, last.y); ctx.lineTo(p.x, p.y); ctx.stroke();
      last = p; e.preventDefault();
    }
    canvas.addEventListener('mousedown', start);
    canvas.addEventListener('mousemove', move);
    window.addEventListener('mouseup', end);
    canvas.addEventListener('touchstart', start, {passive:false});
    canvas.addEventListener('touchmove', move, {passive:false});
    canvas.addEventListener('touchend', end);

    document.getElementById('clear').onclick = () => {
      ctx.clearRect(0,0,canvas.width,canvas.height);
      document.getElementById('signature_png').value = "";
    };

    // Beim Absenden Canvas als PNG übernehmen
    document.querySelector('form').addEventListener('submit', (e) => {
      const data = canvas.toDataURL('image/png');
      // Primitive Prüfung: ist die Signatur leer (alles transparent)?
      // Hier: wenn die DataURL sehr kurz ist, brechen wir ab.
      if (data.length < 2000) { // quick&dirty
        e.preventDefault();
        alert('Bitte unterschreiben.');
        return;
      }
      document.getElementById('signature_png').value = data;
    });
  </script></body></html>

2) submit.php (PGP-Verschlüsselung + Mail)

Benötigt: php-gnupg oder GnuPG-CLI (gpg) als Fallback.

<?php
// --- Minimal-Konfiguration ---
$RECIPIENT = 'it@example.gov';  // Empfänger (Mail + GPG-UID/Fingerprint)
$MAIL_TO   = 'it@example.gov';  // Zieladresse
$MAIL_SUBJ = 'Antrag Lehrmittelzuschuss (PGP-verschlüsselt)';
$FROM      = 'no-reply@deine-domain.tld';

// --- Sicherheitsgurt: nur HTTPS in Produktion ---
if (empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] === 'off') {
  // In Produktion: exit('HTTPS required');
}

// --- Pflichtfelder prüfen ---
foreach (['name','email','child_name','child_dob','signature_png'] as $f) {
  if (empty($_POST[$f])) { http_response_code(400); exit("Fehlendes Feld: $f"); }
}

// --- ZIP in Temp bauen ---
$tmpZip = tempnam(sys_get_temp_dir(), 'antrag_') . '.zip';
$zip = new ZipArchive();
if ($zip->open($tmpZip, ZipArchive::CREATE)!==TRUE) { http_response_code(500); exit('ZIP Fehler'); }

// 1) Formdaten als JSON
$data = [
  'name'       => $_POST['name'],
  'email'      => $_POST['email'],
  'child_name' => $_POST['child_name'],
  'child_dob'  => $_POST['child_dob'],
  'submitted'  => gmdate('c')
];
$zip->addFromString('antrag.json', json_encode($data, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE));

// 2) Unterschrift (Base64 &#8594; PNG)
if (preg_match('#^data:image/png;base64,#', $_POST['signature_png'])) {
  $png = base64_decode(preg_replace('#^data:image/\w+;base64,#', '', $_POST['signature_png']));
  $zip->addFromString('unterschrift.png', $png);
} else {
  $zip->close(); @unlink($tmpZip); http_response_code(400); exit('Ungültige Signatur');
}

// 3) Anhänge (optional)
if (!empty($_FILES['attachments']) && is_array($_FILES['attachments']['name'])) {
  for ($i=0; $i<count($_FILES['attachments']['name']); $i++) {
    if ($_FILES['attachments']['error'][$i] === UPLOAD_ERR_OK) {
      $name = basename($_FILES['attachments']['name'][$i]);
      $safe = preg_replace('/[^a-zA-Z0-9._-]/','_', $name);
      $zip->addFile($_FILES['attachments']['tmp_name'][$i], "anlagen/$safe");
    }
  }
}
$zip->close();

$zipData = file_get_contents($tmpZip);
@unlink($tmpZip);
if ($zipData === false) { http_response_code(500); exit('ZIP lesen fehlgeschlagen'); }

// --- PGP verschlüsseln (php-gnupg bevorzugt, sonst CLI) ---
$cipher = null;
if (extension_loaded('gnupg')) {
  $gpg = new gnupg();
  $gpg->seterrormode(GNUPG_ERROR_EXCEPTION);
  // Wichtig: Öffentlichen Schlüssel des Empfängers muss auf dem Server-Schlüsselbund vorhanden sein
  $gpg->addencryptkey($RECIPIENT);
  $cipher = $gpg->encrypt($zipData);
} else {
  // Fallback via gpg CLI (öffentlicher Schlüssel muss importiert sein)
  $in  = tempnam(sys_get_temp_dir(), 'in_');
  $out = tempnam(sys_get_temp_dir(), 'out_') . '.gpg';
  file_put_contents($in, $zipData);
  $cmd = sprintf('gpg --batch --yes --trust-model always -r %s -o %s -e %s 2>&1',
                 escapeshellarg($RECIPIENT), escapeshellarg($out), escapeshellarg($in));
  $ret = shell_exec($cmd);
  @unlink($in);
  if (!file_exists($out)) { @unlink($out); http_response_code(500); exit("GPG Fehler:\n".$ret); }
  $cipher = file_get_contents($out);
  @unlink($out);
}
if (!$cipher) { http_response_code(500); exit('Verschlüsselung fehlgeschlagen'); }

// --- Mail mit .gpg Anhang versenden (Minimal, für produktiv PHPMailer o.ä.) ---
$boundary = 'b'.bin2hex(random_bytes(8));
$headers  = [];
$headers[] = "From: $FROM";
$headers[] = "MIME-Version: 1.0";
$headers[] = "Content-Type: multipart/mixed; boundary=\"$boundary\"";

$body  = "--$boundary\r\n";
$body .= "Content-Type: text/plain; charset=UTF-8\r\n\r\n";
$body .= "Anbei der verschlüsselte Antrag als .gpg. Bitte mit dem IT-Schlüssel entschlüsseln.\r\n";
$body .= "\r\n";
$body .= "--$boundary\r\n";
$body .= "Content-Type: application/octet-stream; name=\"antrag.zip.gpg\"\r\n";
$body .= "Content-Transfer-Encoding: base64\r\n";
$body .= "Content-Disposition: attachment; filename=\"antrag.zip.gpg\"\r\n\r\n";
$body .= chunk_split(base64_encode($cipher));
$body .= "--$boundary--\r\n";

if (!mail($MAIL_TO, $MAIL_SUBJ, $body, implode("\r\n", $headers))) {
  http_response_code(500); exit('Mailversand fehlgeschlagen');
}

// --- Fertig ---
echo "Danke! Ihr Antrag wurde **sicher** übertragen.";

Setup in 5 Minuten

GnuPG + PHP-Modul installieren (Beispiele Ubuntu/Debian):

sudo apt-get install gnupg php-gnupg
sudo systemctl reload php*-fpm || sudo systemctl reload apache2

Öffentlichen Schlüssel der IT importieren (Server):

gpg --import /pfad/zum/publickey.asc
gpg --list-keys
# Fingerprint/UID merken und in $RECIPIENT eintragen (submit.php)

Mailversand sicherstellen:

Für Demo reicht mail() (Postfix/Exim vorhanden). Besser: PHPMailer mit SMTP.


Deployen


Warum das datenschutztechnisch sauber ist