<?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);
}
}