feat(domains): Pruefung von DNS, Zuordnung und Zertifikat

Eine reine DNS-Pruefung sagt nichts darueber aus, ob der Webserver die
Domain auch dem richtigen Verzeichnis zuordnet - genau das ist der
haeufigste Fehler. Die Pruefung ruft die Domain deshalb von aussen auf
einen signierten Endpunkt auf. Kommt der Aufruf mit gueltigem Token an,
ist die ganze Kette bewiesen, denn nur diese Installation kann ein
solches Token erzeugen.

Erkannt und benannt werden: fehlender DNS-Eintrag, Domain-Weiterleitung
des Registrars, falsches Verzeichnis, ungueltiges Zertifikat.

Link-lokale Adressen werden nicht abgerufen - dort liegen bei Cloud-
Anbietern die Metadaten-Dienste.
This commit is contained in:
2026-08-03 18:11:40 +02:00
parent 8b6e05020b
commit 7238e7a937
3 changed files with 655 additions and 0 deletions
+50
View File
@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace App\Support;
/**
* Kurzlebiges, signiertes Token fuer die Domain-Pruefung.
*
* Die Pruefung ruft die zu testende Domain von aussen auf. Der dafuer
* noetige Endpunkt muss ohne Login erreichbar sein - deshalb ist er nur
* mit einem gueltigen Token ansprechbar und antwortet sonst mit 404.
*
* Das Token ist zustandslos (HMAC ueber den Zeitstempel), es muss also
* nichts zwischengespeichert werden. Kommt der Aufruf mit einem
* gueltigen Token an, ist damit zugleich bewiesen, dass er bei DIESER
* Installation gelandet ist - nur sie kennt das Geheimnis.
*/
final class CheckToken
{
/** Gueltigkeitsdauer in Sekunden. */
public const LIFETIME = 180;
public static function create(string $secret, ?int $now = null): string
{
$timestamp = (string) ($now ?? time());
return $timestamp . '.' . hash_hmac('sha256', $timestamp, $secret);
}
public static function isValid(string $token, string $secret, ?int $now = null): bool
{
$parts = explode('.', $token, 2);
if (count($parts) !== 2) {
return false;
}
[$timestamp, $signature] = $parts;
if ($timestamp === '' || !ctype_digit($timestamp)) {
return false;
}
if (abs(($now ?? time()) - (int) $timestamp) > self::LIFETIME) {
return false;
}
return hash_equals(hash_hmac('sha256', $timestamp, $secret), $signature);
}
}