"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); $args = ['user' => User::getLogin()]; if (Taskmanager::isFinished($task)) { self::callbackErrorCheck($task, $args); } else { Property::set(self::PROP_ERROR, false); TaskmanagerCallback::addCallback($task, 'acmeErrors', $args); } } public static function callbackErrorCheck(array $task, $args): void { if (!Taskmanager::isFinished($task)) return; if (Taskmanager::isFailed($task)) { if (($args['user'] ?? null) === null) { EventLog::warning('Automatic ACME renewal of HTTPS certificate failed', json_encode($task, JSON_PRETTY_PRINT)); } Property::set(self::PROP_ERROR, $task['data']['error'] ?? 'Unknown error'); } else { EventLog::info('ACME issue/renewal of HTTPS certificate by ' . ($args['user'] ?? 'automatic cronjob')); 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); } }