summaryrefslogtreecommitdiffstats
path: root/modules-available/webinterface/inc/acme.inc.php
diff options
context:
space:
mode:
authorSimon Rettberg2024-10-08 16:22:17 +0200
committerSimon Rettberg2024-10-08 16:22:17 +0200
commit882b694e06acd389dd74f7a7d9b70ada0fd218d5 (patch)
tree11c95683e56645c498a08378dadf2422cd2d27fb /modules-available/webinterface/inc/acme.inc.php
parentUpdate phpdoc (diff)
downloadslx-admin-882b694e06acd389dd74f7a7d9b70ada0fd218d5.tar.gz
slx-admin-882b694e06acd389dd74f7a7d9b70ada0fd218d5.tar.xz
slx-admin-882b694e06acd389dd74f7a7d9b70ada0fd218d5.zip
[webinterface] Add support for ACME, add option to redirect to cert domain
Diffstat (limited to 'modules-available/webinterface/inc/acme.inc.php')
-rw-r--r--modules-available/webinterface/inc/acme.inc.php164
1 files changed, 164 insertions, 0 deletions
diff --git a/modules-available/webinterface/inc/acme.inc.php b/modules-available/webinterface/inc/acme.inc.php
new file mode 100644
index 00000000..c23578cc
--- /dev/null
+++ b/modules-available/webinterface/inc/acme.inc.php
@@ -0,0 +1,164 @@
+<?php
+
+class Acme
+{
+
+ const PROP_ERROR = 'acme.error-string';
+ const PROP_PROVIDER = 'acme.provider';
+ const PROP_KEY_ID = 'acme.key-id';
+ const PROP_HMAC_KEY = 'acme.hmac-key';
+ const PROP_DOMAINS = 'acme.domains';
+ const PROP_MAIL = 'acme.mail';
+ const VALID_PROVIDERS = [
+ 'letsencrypt' => "Let's Encrypt",
+ 'zerossl' => 'ZeroSSL.com',
+ 'buypass' => 'BuyPass.com',
+ 'geant/sectigo' => 'GEANT via Sectigo',
+ ];
+
+ const PROVIDER_ALIASES = [
+ 'geant/sectigo' => 'https://acme.sectigo.com/v2/GEANTOV',
+ ];
+
+ public static function getLastError(): ?string
+ {
+ return Property::get(self::PROP_ERROR, null);
+ }
+
+ public static function getProvider(): ?string
+ {
+ return Property::get(self::PROP_PROVIDER, null);
+ }
+
+ public static function getKeyId(): ?string
+ {
+ return Property::get(self::PROP_KEY_ID, null);
+ }
+
+ public static function getHmacKey(): ?string
+ {
+ return Property::get(self::PROP_HMAC_KEY, null);
+ }
+
+ /**
+ * @return string[] list of [id] => friendly name
+ */
+ public static function getProviders(): array
+ {
+ return self::VALID_PROVIDERS;
+ }
+
+ public static function getMail(): ?string
+ {
+ return Property::get(self::PROP_MAIL, null);
+ }
+
+ public static function getDomains(): array
+ {
+ return explode(' ', Property::get(self::PROP_DOMAINS));
+ }
+
+ public static function setConfig(string $provider, string $mail, ?string $keyId = null, ?string $hmacKey = null): bool
+ {
+ if (!isset(self::VALID_PROVIDERS[$provider])) {
+ Message::addError('webinterface.acme-invalid-provider', $provider);
+ return false;
+ }
+ Property::set(self::PROP_PROVIDER, $provider);
+ Property::set(self::PROP_MAIL, $mail);
+ Property::set(self::PROP_KEY_ID, $keyId);
+ Property::set(self::PROP_HMAC_KEY, $hmacKey);
+ return true;
+ }
+
+ public static function setDomains(array $list): void
+ {
+ Property::set(self::PROP_DOMAINS, implode(' ', $list));
+ }
+
+ private static function handleErrorAsync($task): void
+ {
+ if (!is_array($task) || !Taskmanager::isTask($task))
+ return;
+ $task = Taskmanager::waitComplete($task, 250);
+ if (Taskmanager::isFinished($task)) {
+ self::callbackErrorCheck($task);
+ } else {
+ Property::set(self::PROP_ERROR, false);
+ TaskmanagerCallback::addCallback($task, 'acmeErrors');
+ }
+ }
+
+ public static function callbackErrorCheck(array $task): void
+ {
+ if (!Taskmanager::isFinished($task))
+ return;
+ if (Taskmanager::isFailed($task)) {
+ Property::set(self::PROP_ERROR, $task['data']['error'] ?? 'Unknown error');
+ } else {
+ Property::set(self::PROP_ERROR, false);
+ }
+ }
+
+ public static function issueNewCertificate(bool $wipeAll = false): ?string
+ {
+ $provider = self::getProvider();
+ if ($provider === null) {
+ Message::addError('webinterface.acme-no-provider');
+ return null;
+ }
+ $mail = self::getMail();
+ if (empty($mail)) {
+ Message::addError('webinterface.acme-no-mail');
+ return null;
+ }
+ $domains = self::getDomains();
+ if (empty($domains)) {
+ Message::addError('webinterface.acme-no-domains');
+ return null;
+ }
+ $redirect = Property::get(WebInterface::PROP_REDIRECT);
+ $task = Taskmanager::submit('LighttpdHttps', [
+ 'redirect' => $redirect,
+ 'acmeMode' => 'issue',
+ 'acmeMail' => $mail,
+ 'acmeDomains' => $domains,
+ 'acmeProvider' => self::PROVIDER_ALIASES[$provider] ?? $provider,
+ 'acmeKeyId' => self::getKeyId(),
+ 'acmeHmacKey' => self::getHmacKey(),
+ 'acmeWipeAll' => $wipeAll,
+ ]);
+ self::handleErrorAsync($task);
+ return $task['id'] ?? null;
+ }
+
+ public static function renew(): ?string
+ {
+ error_log("Renew called");
+ $domains = self::getDomains();
+ if (empty($domains)) {
+ Message::addError('webinterface.acme-no-domains');
+ return null;
+ }
+ $redirect = Property::get(WebInterface::PROP_REDIRECT);
+ $task = Taskmanager::submit('LighttpdHttps', [
+ 'redirect' => $redirect,
+ 'acmeMode' => 'renew',
+ 'acmeDomains' => $domains,
+ ]);
+ self::handleErrorAsync($task);
+ return $task['id'] ?? null;
+ }
+
+ public static function tryEnable(): bool
+ {
+ $redirect = Property::get(WebInterface::PROP_REDIRECT);
+ $task = Taskmanager::submit('LighttpdHttps', [
+ 'redirect' => $redirect,
+ 'acmeMode' => 'try-enable',
+ ]);
+ $task = Taskmanager::waitComplete($task, 10000);
+ return Taskmanager::isFinished($task) && !Taskmanager::isFailed($task);
+ }
+
+} \ No newline at end of file