Files
deskhub/DeskHub-Webseite/includes/update_security.php
T
2026-10-19 18:05:57 +00:00

84 lines
3.7 KiB
PHP

<?php
declare(strict_types=1);
/**
* Sicherung des System-Updates.
*
* Ein Update ersetzt Programmcode. Würde das Konto beim Update-Server übernommen, könnten sonst Schadcode-Updates an
* alle Installationen gehen. Deshalb prüft DeskHub bei hinterlegtem Herausgeber-Schlüssel (includes/catalog_defaults.php)
* eine Signatur: Der Herausgeber signiert jedes Release-Paket mit seinem privaten Schlüssel (tools/release-sign.php),
* ohne passende Signatur wird nichts installiert. Der Schlüssel ist derselbe wie bei Plugins und Themes.
*
* Release-Dateien (Anhänge des Releases): deskhub-<Version>.zip und deskhub-<Version>.zip.sig
*/
/** Wird für Updates eine Herausgeber-Signatur verlangt? Ja, sobald ein Herausgeber-Schlüssel eingebaut ist. */
function update_signature_required(): bool
{
return function_exists('plugin_publisher_key') && plugin_publisher_key() !== '';
}
/** Zu signierende Nachricht: bindet Signatur an Version und Dateiinhalt. */
function update_signature_message(string $tag, string $sha256): string
{
return "deskhub-update\n" . ltrim(trim($tag), 'vV') . "\n" . strtolower($sha256);
}
/** Prüft die Signatur eines heruntergeladenen Pakets. Gibt null bei Erfolg zurück, sonst eine Fehlermeldung. */
function update_verify_package(string $tag, string $zipPath, string $signature): ?string
{
$signature = trim($signature);
if ($signature === '') return 'Dem Release fehlt die Signatur des Herausgebers.';
$key = plugin_publisher_key();
if ($key === '') return 'Es ist kein Herausgeber-Schlüssel hinterlegt, die Signatur kann nicht geprüft werden.';
$sha = hash_file('sha256', $zipPath);
if ($sha === false) return 'Das Paket konnte nicht gelesen werden.';
if (!plugin_verify_signature(update_signature_message($tag, $sha), $signature, $key)) {
return 'Die Signatur ist ungültig. Das Paket wurde verändert oder stammt nicht vom Herausgeber. Es wird nicht installiert.';
}
return null;
}
/**
* Sucht in den Anhängen eines Releases (Gitea-API) das Paket und die Signatur.
*
* @return array{zip: ?string, sig: ?string} Download-Adressen oder null
*/
function update_find_assets(array $release, string $tag): array
{
$version = ltrim(trim($tag), 'vV');
$names = ['zip' => "deskhub-{$version}.zip", 'sig' => "deskhub-{$version}.zip.sig"];
$out = ['zip' => null, 'sig' => null];
foreach ((array) ($release['assets'] ?? []) as $asset) {
$name = (string) ($asset['name'] ?? '');
$url = (string) ($asset['browser_download_url'] ?? '');
foreach ($names as $kind => $expected) {
if ($name === $expected && str_starts_with($url, 'https://')) $out[$kind] = $url;
}
}
return $out;
}
/**
* Zugangstoken für den Update-Server (nur nötig, wenn das Repository privat ist; Gitea antwortet sonst mit "404").
* Quelle: config/config.php ('update' => ['token' => '…']) hat Vorrang, sonst das in den Einstellungen der Update-Seite
* gespeicherte (verschlüsselt). Gibt [Token, Quelle] zurück, Quelle 'config', 'einstellungen' oder ''.
*
* @return array{0: string, 1: string}
*/
function update_token_info(): array
{
$fromConfig = trim((string) (config()['update']['token'] ?? ''));
if ($fromConfig !== '') return [$fromConfig, 'config'];
$stored = (string) get_setting('update_token', '');
$fromSettings = $stored !== '' ? trim(decrypt_secret($stored)) : '';
return $fromSettings !== '' ? [$fromSettings, 'einstellungen'] : ['', ''];
}
/** Kopfzeilen für Abrufe beim Update-Server; ein Zugangstoken ist nur nötig, wenn das Repository nicht öffentlich ist. */
function update_http_headers(): array
{
[$token] = update_token_info();
return $token !== '' ? ['Authorization: token ' . $token] : [];
}