From de7729cec87467f8802c8b58c88618a78b7de4d4 Mon Sep 17 00:00:00 2001 From: Michael Kaufmann Date: Thu, 23 Mar 2023 12:40:01 +0100 Subject: [PATCH] add certificate metadata to db table Signed-off-by: Michael Kaufmann --- install/froxlor.sql.php | 4 +- install/updates/froxlor/update_2.x.inc.php | 22 ++++++++++ lib/Froxlor/Api/Commands/Certificates.php | 43 +++++++++++-------- lib/Froxlor/Cli/ValidateAcmeWebroot.php | 2 +- lib/Froxlor/Cron/Http/HttpConfigBase.php | 2 +- lib/Froxlor/Cron/Http/LetsEncrypt/AcmeSh.php | 34 ++++++++++----- lib/Froxlor/Domain/Domain.php | 2 +- .../tablelisting.sslcertificates.php | 16 +++---- 8 files changed, 81 insertions(+), 44 deletions(-) diff --git a/install/froxlor.sql.php b/install/froxlor.sql.php index 472a7cfb..e6eced43 100644 --- a/install/froxlor.sql.php +++ b/install/froxlor.sql.php @@ -983,7 +983,9 @@ CREATE TABLE IF NOT EXISTS `domain_ssl_settings` ( `ssl_cert_chainfile` mediumtext, `ssl_csr_file` mediumtext, `ssl_fullchain_file` mediumtext, - `expirationdate` datetime DEFAULT NULL, + `validfromdate` datetime DEFAULT NULL, + `validtodate` datetime DEFAULT NULL, + `issuer` varchar(255) NOT NULL default '', PRIMARY KEY (`id`), UNIQUE KEY (`domainid`) ) ENGINE=InnoDB CHARSET=utf8 COLLATE=utf8_general_ci; diff --git a/install/updates/froxlor/update_2.x.inc.php b/install/updates/froxlor/update_2.x.inc.php index 6a4ec7f8..4f2d6038 100644 --- a/install/updates/froxlor/update_2.x.inc.php +++ b/install/updates/froxlor/update_2.x.inc.php @@ -429,5 +429,27 @@ if (Froxlor::isDatabaseVersion('202302030')) { } Update::lastStepStatus(0); + Update::showUpdateStep("Enhancing ssl data table"); + Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` RENAME COLUMN `expirationdate` TO `validtodate`;"); + Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` ADD `validfromdate` datetime DEFAULT NULL AFTER `ssl_fullchain_file`;"); + Database::query("ALTER TABLE `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` ADD `issuer` varchar(255) NOT NULL default '' AFTER `validtodate`;"); + Update::lastStepStatus(0); + + Update::showUpdateStep("Filling new ssl data fields with existing certificate data"); + $crt_upd_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` SET `validfromdate` = :validfromdate, `issuer` = :issuer WHERE `id` = :id"); + $crt_stmt = Database::prepare("SELECT * FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "`"); + Database::pexecute($crt_stmt); + while ($cert = $crt_stmt->fetch(\PDO::FETCH_ASSOC)) { + $cert_content = openssl_x509_parse($cert['ssl_cert_file']); + if (is_array($cert_content)) { + $validfromdate = empty($cert_content['validFrom_time_t']) ? null : date("Y-m-d H:i:s", $cert_content['validFrom_time_t']); + $issuer = $cert_content['issuer']['O'] ?? ""; + Database::pexecute($crt_upd_stmt, ['validfromdate' => $validfromdate, 'issuer' => $issuer, 'id' => $cert['id']]); + } + } + // clear possible user customized columns + Database::query("DELETE FROM `" . TABLE_PANEL_USERCOLUMNS . "` WHERE `section` = 'sslcertificates_list'"); + Update::lastStepStatus(0); + Froxlor::updateToDbVersion('202303150'); } diff --git a/lib/Froxlor/Api/Commands/Certificates.php b/lib/Froxlor/Api/Commands/Certificates.php index 7784b0c3..3ad44633 100644 --- a/lib/Froxlor/Api/Commands/Certificates.php +++ b/lib/Froxlor/Api/Commands/Certificates.php @@ -127,7 +127,9 @@ class Certificates extends ApiCommand implements ResourceEntity } $do_verify = true; - $expirationdate = null; + $validtodate = null; + $validtodate = null; + $issuer = ""; // no cert-file given -> forget everything if ($ssl_cert_file == '') { $ssl_key_file = ''; @@ -168,7 +170,10 @@ class Certificates extends ApiCommand implements ResourceEntity } else { Response::standardError('sslcertificateinvalidcert', '', true); } - $expirationdate = empty($cert_content['validTo_time_t']) ? null : date("Y-m-d H:i:s", $cert_content['validTo_time_t']); + // get data from certificate to store in the table + $validfromdate = empty($cert_content['validFrom_time_t']) ? null : date("Y-m-d H:i:s", $cert_content['validFrom_time_t']); + $validtodate = empty($cert_content['validTo_time_t']) ? null : date("Y-m-d H:i:s", $cert_content['validTo_time_t']); + $issuer = $cert_content['issuer']['O'] ?? ""; } // Add/Update database entry @@ -183,7 +188,9 @@ class Certificates extends ApiCommand implements ResourceEntity `ssl_key_file` = :ssl_key_file, `ssl_ca_file` = :ssl_ca_file, `ssl_cert_chainfile` = :ssl_cert_chainfile, - `expirationdate` = :expirationdate + `validfromdate` = :validfromdate, + `validtodate` = :validtodate, + `issuer` = :issuer " . $qrywhere . " `domainid`= :domainid "); $params = [ @@ -191,7 +198,9 @@ class Certificates extends ApiCommand implements ResourceEntity "ssl_key_file" => $ssl_key_file, "ssl_ca_file" => $ssl_ca_file, "ssl_cert_chainfile" => $ssl_cert_chainfile, - "expirationdate" => $expirationdate, + "validfromdate" => $validfromdate, + "validtodate" => $validtodate, + "issuer" => $issuer, "domainid" => $domainid ]; Database::pexecute($stmt, $params, true, true); @@ -299,27 +308,23 @@ class Certificates extends ApiCommand implements ResourceEntity } // Set data from certificate + $cert['isvalid'] = false; + $cert['san'] = null; $cert_data = openssl_x509_parse($cert['ssl_cert_file']); if ($cert_data) { - $cert['validfromdate'] = date('Y-m-d H:i:s', $cert_data['validFrom_time_t']); - $cert['validtodate'] = date('Y-m-d H:i:s', $cert_data['validTo_time_t']); $cert['isvalid'] = (bool)$cert_data['validTo_time_t'] > time(); - $cert['issuer'] = $cert_data['issuer']['O'] ?? null; - } - - // Set subject alt names from certificate - $cert['san'] = null; - if (isset($cert_data['extensions']['subjectAltName']) && !empty($cert_data['extensions']['subjectAltName'])) { - $SANs = explode(",", $cert_data['extensions']['subjectAltName']); - $SANs = array_map('trim', $SANs); - foreach ($SANs as $san) { - $san = str_replace("DNS:", "", $san); - if ($san != $cert_data['subject']['CN'] && strpos($san, "othername:") === false) { - $cert['san'][] = $san; + // Set subject alt names from certificate + if (isset($cert_data['extensions']['subjectAltName']) && !empty($cert_data['extensions']['subjectAltName'])) { + $SANs = explode(",", $cert_data['extensions']['subjectAltName']); + $SANs = array_map('trim', $SANs); + foreach ($SANs as $san) { + $san = str_replace("DNS:", "", $san); + if ($san != $cert_data['subject']['CN'] && strpos($san, "othername:") === false) { + $cert['san'][] = $san; + } } } } - $result[] = $cert; } return $this->response([ diff --git a/lib/Froxlor/Cli/ValidateAcmeWebroot.php b/lib/Froxlor/Cli/ValidateAcmeWebroot.php index 1a634fae..4d906399 100644 --- a/lib/Froxlor/Cli/ValidateAcmeWebroot.php +++ b/lib/Froxlor/Cli/ValidateAcmeWebroot.php @@ -76,7 +76,7 @@ final class ValidateAcmeWebroot extends CliCommand 'domain' => Settings::Get('system.hostname') ]; } - $upd_stmt = Database::prepare("UPDATE domain_ssl_settings SET expirationdate=NULL WHERE `domainid` = :did"); + $upd_stmt = Database::prepare("UPDATE domain_ssl_settings SET `validtodate`=NULL WHERE `domainid` = :did"); $acmesh_dir = dirname(Settings::Get('system.acmeshpath')); $acmesh_challenge_dir = rtrim(FileDir::makeCorrectDir(Settings::Get('system.letsencryptchallengepath')), "/"); $recommended = rtrim(FileDir::makeCorrectDir(Froxlor::getInstallDir()), "/"); diff --git a/lib/Froxlor/Cron/Http/HttpConfigBase.php b/lib/Froxlor/Cron/Http/HttpConfigBase.php index d23be3ec..7b400726 100644 --- a/lib/Froxlor/Cron/Http/HttpConfigBase.php +++ b/lib/Froxlor/Cron/Http/HttpConfigBase.php @@ -179,7 +179,7 @@ class HttpConfigBase $froxlor_ssl_settings_stmt = Database::prepare(" SELECT * FROM `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` WHERE `domainid` = '0' AND - (`expirationdate` < DATE_ADD(NOW(), INTERVAL 30 DAY) OR `expirationdate` IS NULL) + (`validtodate` < DATE_ADD(NOW(), INTERVAL 30 DAY) OR `validtodate` IS NULL) "); $froxlor_ssl = Database::pexecute_first($froxlor_ssl_settings_stmt); if ($froxlor_ssl && !empty($froxlor_ssl['ssl_cert_file'])) { diff --git a/lib/Froxlor/Cron/Http/LetsEncrypt/AcmeSh.php b/lib/Froxlor/Cron/Http/LetsEncrypt/AcmeSh.php index 122d6443..6c4e9fd3 100644 --- a/lib/Froxlor/Cron/Http/LetsEncrypt/AcmeSh.php +++ b/lib/Froxlor/Cron/Http/LetsEncrypt/AcmeSh.php @@ -114,7 +114,9 @@ class AcmeSh extends FroxlorCron `ssl_cert_chainfile` = :chain, `ssl_csr_file` = :csr, `ssl_fullchain_file` = :fullchain, - `expirationdate` = :expirationdate + `validfromdate` = :validfromdate, + `validtodate` = :validtodate, + `issuer` = :issuer "); // prepare domain update sql @@ -136,7 +138,9 @@ class AcmeSh extends FroxlorCron 'lepublickey' => Settings::Get('system.lepublickey'), 'leregistered' => Settings::Get('system.leregistered'), 'ssl_redirect' => Settings::Get('system.le_froxlor_redirect'), - 'expirationdate' => null, + 'validfromdate' => null, + 'validtodate' => null, + 'issuer' => "", 'ssl_cert_file' => null, 'ssl_key_file' => null, 'ssl_ca_file' => null, @@ -171,7 +175,9 @@ class AcmeSh extends FroxlorCron 'lepublickey' => Settings::Get('system.lepublickey'), 'leregistered' => Settings::Get('system.leregistered'), 'ssl_redirect' => Settings::Get('system.le_froxlor_redirect'), - 'expirationdate' => is_array($renew_froxlor) ? $renew_froxlor['expirationdate'] : date('Y-m-d H:i:s', 0), + 'validfromdate' => is_array($renew_froxlor) ? $renew_froxlor['validfromdate'] : date('Y-m-d H:i:s', 0), + 'validtodate' => is_array($renew_froxlor) ? $renew_froxlor['validtodate'] : date('Y-m-d H:i:s', 0), + 'issuer' => is_array($renew_froxlor) ? $renew_froxlor['issuer'] : "", 'ssl_cert_file' => is_array($renew_froxlor) ? $renew_froxlor['ssl_cert_file'] : null, 'ssl_key_file' => is_array($renew_froxlor) ? $renew_froxlor['ssl_key_file'] : null, 'ssl_ca_file' => is_array($renew_froxlor) ? $renew_froxlor['ssl_ca_file'] : null, @@ -187,7 +193,7 @@ class AcmeSh extends FroxlorCron 'loginname' => $domain['loginname'], 'adminsession' => 0 ]); - if (defined('CRON_IS_FORCED') || self::checkFsFilesAreNewer($domain['domain'], $domain['expirationdate'])) { + if (defined('CRON_IS_FORCED') || self::checkFsFilesAreNewer($domain['domain'], $domain['validtodate'])) { self::certToDb($domain, $cronlog, []); $changedetected = 1; } @@ -279,7 +285,9 @@ EOC; SELECT domssl.`id`, domssl.`domainid`, - domssl.`expirationdate`, + domssl.`validfromdate`, + domssl.`validtodate`, + domssl.`issuer`, domssl.`ssl_cert_file`, domssl.`ssl_key_file`, domssl.`ssl_ca_file`, @@ -306,7 +314,7 @@ EOC; AND dom.`letsencrypt` = 1 AND dom.`aliasdomain` IS NULL AND dom.`iswildcarddomain` = 0 - AND domssl.`expirationdate` IS NULL + AND domssl.`validtodate` IS NULL "); $customer_ssl = $certificates_stmt->fetchAll(PDO::FETCH_ASSOC); if ($customer_ssl) { @@ -330,7 +338,7 @@ EOC; "); $froxlor_ssl = Database::pexecute_first($froxlor_ssl_settings_stmt); // also check for possible existing certificate - if ($froxlor_ssl && self::checkFsFilesAreNewer(Settings::Get('system.hostname'), $froxlor_ssl['expirationdate'])) { + if ($froxlor_ssl && self::checkFsFilesAreNewer(Settings::Get('system.hostname'), $froxlor_ssl['validtodate'])) { return $froxlor_ssl; } } @@ -346,7 +354,9 @@ EOC; SELECT domssl.`id`, domssl.`domainid`, - domssl.`expirationdate`, + domssl.`validfromdate`, + domssl.`validtodate`, + domssl.`issuer`, domssl.`ssl_cert_file`, domssl.`ssl_key_file`, dom.`domain`, @@ -370,7 +380,7 @@ EOC; if ($renew_certs) { if ($check) { foreach ($renew_certs as $cert) { - if (self::checkFsFilesAreNewer($cert['domain'], $cert['expirationdate'])) { + if (self::checkFsFilesAreNewer($cert['domain'], $cert['validtodate'])) { return true; } } @@ -453,7 +463,7 @@ EOC; // Only issue let's encrypt certificate if no broken ssl_redirect is enabled if ($certrow['ssl_redirect'] != 2) { $do_force = false; - if (!empty($certrow['ssl_cert_file']) && empty($certrow['expirationdate'])) { + if (!empty($certrow['ssl_cert_file']) && empty($certrow['validtodate'])) { // domain changed (SAN or similar) $do_force = true; $cronlog->logAction(FroxlorLogger::CRON_ACTION, LOG_INFO, "Re-creating certificate for " . $certrow['domain']); @@ -594,7 +604,9 @@ EOC; 'chain' => $return['chain'], 'csr' => $return['csr'], 'fullchain' => $return['fullchain'], - 'expirationdate' => date('Y-m-d H:i:s', $newcert['validTo_time_t']) + 'validfromdate' => date('Y-m-d H:i:s', $newcert['validFrom_time_t']), + 'validtodate' => date('Y-m-d H:i:s', $newcert['validTo_time_t']), + 'issuer' => $newcert['issuer']['O'] ?? "" ]); if ($certrow['ssl_redirect'] == 3) { diff --git a/lib/Froxlor/Domain/Domain.php b/lib/Froxlor/Domain/Domain.php index ebc16eb0..21aeead0 100644 --- a/lib/Froxlor/Domain/Domain.php +++ b/lib/Froxlor/Domain/Domain.php @@ -350,7 +350,7 @@ class Domain $upd_stmt = Database::prepare("UPDATE `" . TABLE_PANEL_DOMAIN_SSL_SETTINGS . "` SET - `expirationdate` = null + `validtodate` = null WHERE domainid = :domainid "); diff --git a/lib/tablelisting/tablelisting.sslcertificates.php b/lib/tablelisting/tablelisting.sslcertificates.php index 346b9295..d243f333 100644 --- a/lib/tablelisting/tablelisting.sslcertificates.php +++ b/lib/tablelisting/tablelisting.sslcertificates.php @@ -45,31 +45,27 @@ return [ 'callback' => [SSLCertificate::class, 'domainWithSan'], 'searchable' => false, ], - 'c.issuer' => [ + 's.issuer' => [ 'label' => lng('ssl_certificates.issuer'), 'field' => 'issuer', - 'searchable' => false, - 'sortable' => false, ], - 'c.validfromdate' => [ + 's.validfromdate' => [ 'label' => lng('ssl_certificates.valid_from'), 'field' => 'validfromdate', 'searchable' => false, - 'sortable' => false, ], - 'c.validtodate' => [ + 's.validtodate' => [ 'label' => lng('ssl_certificates.valid_until'), 'field' => 'validtodate', 'searchable' => false, - 'sortable' => false, ], ], 'visible_columns' => Listing::getVisibleColumnsForListing('sslcertificates_list', [ 'd.domain', 'c.domain', - 'c.issuer', - 'c.validfromdate', - 'c.validtodate', + 's.issuer', + 's.validfromdate', + 's.validtodate', ]), 'actions' => [ 'edit' => [