* 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 * 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 * Dasselbe für Themes (Name und Version werden mitsigniert, da Themes keine plugin.json haben). * * Signiert wird: "deskhub-plugin\n\n\n" 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 '); 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 \n php tools/plugin-sign.php sign \n php tools/plugin-sign.php sign-theme ");