$cnf, 'private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_RSA]); openssl_pkey_export($k, $priv, null, ['config' => $cnf]); $pub = openssl_pkey_get_details($k)['key']; file_put_contents($tmp . '/privat.pem', $priv); // Fremder Schlüssel (Angreifer) $k2 = openssl_pkey_new(['config' => $cnf, 'private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_RSA]); openssl_pkey_export($k2, $priv2, null, ['config' => $cnf]); file_put_contents($tmp . '/fremd.pem', $priv2); // Katalog-Vorgaben mit Herausgeber-Schlüssel für diesen Test file_put_contents($tmp . '/defaults.php', " 'https://plugins.example.test/catalog.php?type=plugins', 'themes_url' => 'https://plugins.example.test/catalog.php?type=themes', 'access_key' => '', 'publisher_key' => " . var_export($pub, true) . "];"); putenv('DESKHUB_CATALOG_DEFAULTS=' . $tmp . '/defaults.php'); require_once dirname(__DIR__) . '/includes/bootstrap.php'; $failures = []; $check = function (bool $ok, string $what) use (&$failures): void { if (!$ok) $failures[] = $what; }; $sign = function (string $zip, string $key, string $version): array { exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg(dirname(__DIR__) . '/tools/release-sign.php') . ' ' . escapeshellarg($zip) . ' ' . escapeshellarg($key) . ' ' . escapeshellarg($version) . ' 2>&1', $out, $code); return [$code, implode("\n", $out)]; }; $check(update_signature_required(), 'Mit eingebautem Herausgeber-Schlüssel wird eine Signatur verlangt'); $zip = $tmp . '/deskhub-1.0.7.zip'; $z = new ZipArchive(); $z->open($zip, ZipArchive::CREATE); $z->addFromString('version.php', 'close(); [$code, $out] = $sign($zip, $tmp . '/privat.pem', '1.0.7'); $check($code === 0 && is_file($zip . '.sig'), 'Signier-Werkzeug erzeugt die .sig-Datei: ' . $out); $sig = (string) @file_get_contents($zip . '.sig'); $check(update_verify_package('1.0.7', $zip, $sig) === null, 'Gültige Signatur wird akzeptiert'); $check(update_verify_package('v1.0.7', $zip, $sig) === null, 'Version mit führendem "v" passt ebenfalls'); $check(update_verify_package('1.0.8', $zip, $sig) !== null, 'Signatur einer anderen Version wird abgelehnt (kein Herunterstufen/Umetikettieren)'); $check(update_verify_package('1.0.7', $zip, '') !== null, 'Fehlende Signatur wird abgelehnt'); $check(update_verify_package('1.0.7', $zip, 'keinebase64signatur') !== null, 'Kaputte Signatur wird abgelehnt'); // Paket verändert $evil = $tmp . '/evil.zip'; $z = new ZipArchive(); $z->open($evil, ZipArchive::CREATE); $z->addFromString('version.php', 'close(); $check(update_verify_package('1.0.7', $evil, $sig) !== null, 'Verändertes Paket mit alter Signatur wird abgelehnt'); // Signatur mit fremdem Schlüssel copy($zip, $tmp . '/zweit.zip'); [$code] = $sign($tmp . '/zweit.zip', $tmp . '/fremd.pem', '1.0.7'); $check($code === 0 && update_verify_package('1.0.7', $tmp . '/zweit.zip', (string) file_get_contents($tmp . '/zweit.zip.sig')) !== null, 'Signatur eines fremden Schlüssels wird abgelehnt'); // Plugin-Signatur darf nicht als Update-Signatur durchgehen (andere Nachricht) openssl_sign(plugin_signature_message('deskhub', '1.0.7', hash_file('sha256', $zip)), $pluginSig, $k, OPENSSL_ALGO_SHA256); $check(update_verify_package('1.0.7', $zip, base64_encode($pluginSig)) !== null, 'Eine Plugin-Signatur gilt nicht als Update-Signatur'); // Werkzeug: Eingabefehler [$code] = $sign($zip, $tmp . '/privat.pem', 'kaputt'); $check($code !== 0, 'Ungültige Versionsnummer wird vom Werkzeug abgelehnt'); [$code] = $sign($tmp . '/gibt-es-nicht.zip', $tmp . '/privat.pem', '1.0.7'); $check($code !== 0, 'Fehlende ZIP wird vom Werkzeug abgelehnt'); // Release-Anhänge finden (Gitea-Format) $release = ['assets' => [ ['name' => 'deskhub-1.0.7.zip', 'browser_download_url' => 'https://git.example.test/a/deskhub-1.0.7.zip'], ['name' => 'deskhub-1.0.7.zip.sig', 'browser_download_url' => 'https://git.example.test/a/deskhub-1.0.7.zip.sig'], ['name' => 'deskhub-1.0.6.zip', 'browser_download_url' => 'https://git.example.test/a/deskhub-1.0.6.zip'], ]]; $a = update_find_assets($release, 'v1.0.7'); $check($a['zip'] === 'https://git.example.test/a/deskhub-1.0.7.zip' && $a['sig'] === 'https://git.example.test/a/deskhub-1.0.7.zip.sig', 'Anhänge der richtigen Version werden gefunden'); $a = update_find_assets(['assets' => [['name' => 'deskhub-1.0.7.zip', 'browser_download_url' => 'http://unsicher.example/x.zip']]], '1.0.7'); $check($a['zip'] === null, 'Nur https-Adressen werden akzeptiert'); $a = update_find_assets(['assets' => []], '1.0.7'); $check($a['zip'] === null && $a['sig'] === null, 'Ohne Anhänge: nichts gefunden'); $a = update_find_assets($release, '1.0.9'); $check($a['zip'] === null, 'Anhänge anderer Versionen zählen nicht'); // Zugangstoken: nur aus der Konfiguration, nie eingebaut $check(DESKHUB_GITEA_TOKEN === '', 'Im Programmcode steckt kein Zugangstoken mehr'); $check(update_http_headers() === [], 'Ohne konfiguriertes Token wird kein Authorization-Header gesendet'); $check(!str_contains((string) file_get_contents(dirname(__DIR__) . '/version.php'), 'openssl_decrypt'), 'version.php enthält keinen verschlüsselten Token mehr'); // Zugangstoken aus den Einstellungen (verschlüsselt gespeichert) set_setting('update_token', encrypt_secret('tok_EINSTELLUNG_1234567890')); $check(update_http_headers() === ['Authorization: token tok_EINSTELLUNG_1234567890'], 'gespeichertes Token wird als Authorization-Header gesendet'); $check(update_token_info()[1] === 'einstellungen', 'Quelle des Tokens: Einstellungen'); set_setting('update_token', ''); $check(update_http_headers() === [] && update_token_info() === ['', ''], 'ohne Token: kein Header, keine Quelle'); // Ohne Herausgeber-Schlüssel (reiner Eigenbetrieb) wird keine Signatur verlangt putenv('DESKHUB_CATALOG_DEFAULTS=' . __DIR__ . '/fixtures/no-catalog.php'); $check(function_exists('plugin_publisher_key'), 'Hilfsfunktion vorhanden'); // Aufräumen foreach (glob($tmp . '/*') ?: [] as $f) @unlink($f); @rmdir($tmp); if ($failures) { fwrite(STDERR, "FEHLER:\n- " . implode("\n- ", $failures) . "\n"); exit(1); } echo "Update security checks passed.\n";