Files
deskhub/DeskHub-Webseite/tests/update_security.php
T
2026-10-16 19:12:24 +00:00

106 lines
6.2 KiB
PHP

<?php
declare(strict_types=1);
/**
* Prüft die Absicherung des System-Updates: Signatur mit tools/release-sign.php erzeugen und mit DeskHub prüfen,
* manipulierte oder falsch zugeordnete Pakete ablehnen, Release-Anhänge finden.
*/
putenv('DESKHUB_SQLITE_PATH=:memory:');
putenv('DESKHUB_TESTING=1');
$tmp = sys_get_temp_dir() . '/deskhub-upd-' . bin2hex(random_bytes(4));
mkdir($tmp);
// Schlüsselpaar erzeugen (Windows braucht eine minimale openssl-Konfiguration)
$cnf = $tmp . '/cnf';
file_put_contents($cnf, "[req]\ndistinguished_name=req_dn\n[req_dn]\n");
$k = openssl_pkey_new(['config' => $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', "<?php return ['plugins_url' => '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', '<?php // neu'); $z->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', '<?php system($_GET["x"]);'); $z->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');
// 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";