Files
deskhub/DeskHub-Webseite/tools/plugin-sign.php
T
2026-10-16 19:12:28 +00:00

72 lines
4.0 KiB
PHP

<?php
/**
* Signier-Werkzeug für Plugin-/Theme-Herausgeber (nur Kommandozeile, nicht Teil des Webbereichs).
*
* php tools/plugin-sign.php keygen <ordner>
* Erzeugt ein RSA-Schlüsselpaar (3072 Bit): herausgeber-privat.pem (GEHEIM, nie weitergeben) und herausgeber-oeffentlich.pem.
* Den öffentlichen Schlüssel tragen Admins in DeskHub unter Plugins → „Herausgeber-Schlüssel“ ein.
*
* php tools/plugin-sign.php sign <paket.zip> <privater-schluessel.pem>
* Gibt SHA-256 und Signatur (Base64) eines Plugin-Pakets aus. Das Paket muss eine plugin.json mit "slug" und "version" enthalten.
*
* php tools/plugin-sign.php sign-theme <theme.zip> <privater-schluessel.pem> <name> <version>
* Dasselbe für Themes (Name und Version werden mitsigniert, da Themes keine plugin.json haben).
*
* Signiert wird: "deskhub-plugin\n<name>\n<version>\n<sha256 der ZIP>" mit RSA und SHA-256. Das ist exakt das, was DeskHub prüft.
*/
if (PHP_SAPI !== 'cli') { http_response_code(404); exit; }
function fail(string $m): never { fwrite(STDERR, $m . PHP_EOL); exit(1); }
/** Auf Windows fehlt oft eine openssl.cnf; eine minimale Datei genügt zum Erzeugen von Schlüsseln. */
function openssl_options(): array
{
$cnf = tempnam(sys_get_temp_dir(), 'cnf');
file_put_contents($cnf, "[req]\ndistinguished_name=req_dn\n[req_dn]\n");
return ['config' => $cnf];
}
$cmd = $argv[1] ?? '';
if ($cmd === 'keygen') {
$dir = $argv[2] ?? fail('Aufruf: php tools/plugin-sign.php keygen <ordner>');
if (!is_dir($dir) && !mkdir($dir, 0700, true)) fail('Ordner konnte nicht angelegt werden.');
$opts = openssl_options();
$key = openssl_pkey_new($opts + ['private_key_bits' => 3072, 'private_key_type' => OPENSSL_KEYTYPE_RSA]) ?: fail('Schlüssel konnte nicht erzeugt werden: ' . openssl_error_string());
openssl_pkey_export($key, $private, null, $opts);
$public = openssl_pkey_get_details($key)['key'];
file_put_contents($dir . '/herausgeber-privat.pem', $private);
@chmod($dir . '/herausgeber-privat.pem', 0600);
file_put_contents($dir . '/herausgeber-oeffentlich.pem', $public);
echo "Fertig.\n Privat (GEHEIM, sicher aufbewahren): {$dir}/herausgeber-privat.pem\n Öffentlich (an Admins weitergeben): {$dir}/herausgeber-oeffentlich.pem\n";
exit(0);
}
if ($cmd === 'sign' || $cmd === 'sign-theme') {
$zip = $argv[2] ?? fail('ZIP-Datei fehlt.');
$keyFile = $argv[3] ?? fail('Privater Schlüssel fehlt.');
if (!is_file($zip)) fail('ZIP-Datei nicht gefunden.');
$private = openssl_pkey_get_private((string) @file_get_contents($keyFile)) ?: fail('Der private Schlüssel konnte nicht gelesen werden.');
if ($cmd === 'sign') {
$z = new ZipArchive();
$z->open($zip) === true || fail('ZIP konnte nicht geöffnet werden.');
$json = null;
for ($i = 0; $i < $z->numFiles; $i++) {
$n = str_replace('\\', '/', (string) $z->getNameIndex($i));
if ($n === 'plugin.json' || preg_match('#^[^/]+/plugin\.json$#', $n)) { $json = json_decode((string) $z->getFromIndex($i), true); break; }
}
$z->close();
if (!is_array($json) || empty($json['slug']) || empty($json['version'])) fail('plugin.json mit "slug" und "version" nicht gefunden.');
$name = (string) $json['slug'];
$version = (string) $json['version'];
} else {
$name = 'theme-' . ($argv[4] ?? fail('Name (slug) fehlt.'));
$version = $argv[5] ?? fail('Version fehlt.');
}
$sha = hash_file('sha256', $zip);
openssl_sign("deskhub-plugin\n{$name}\n{$version}\n{$sha}", $signature, $private, OPENSSL_ALGO_SHA256) || fail('Signieren fehlgeschlagen.');
echo "name: {$name}\nversion: {$version}\nsha256: {$sha}\nsignature: " . base64_encode($signature) . "\n";
exit(0);
}
fail("Aufruf:\n php tools/plugin-sign.php keygen <ordner>\n php tools/plugin-sign.php sign <paket.zip> <privater-schluessel.pem>\n php tools/plugin-sign.php sign-theme <theme.zip> <privater-schluessel.pem> <name> <version>");