From 7e73d5ab875e924695300e4fa3160af264f0616d Mon Sep 17 00:00:00 2001 From: Frank Wall Date: Sat, 6 Jan 2024 23:13:59 +0100 Subject: [PATCH] security/acme-client: add support for duplicate common names, fixes #3127 --- security/acme-client/pkg-descr | 10 ++ .../library/OPNsense/AcmeClient/LeAccount.php | 33 ++++++ .../OPNsense/AcmeClient/LeAutomation/Base.php | 1 + .../OPNsense/AcmeClient/LeCertificate.php | 1 + .../library/OPNsense/AcmeClient/LeCommon.php | 3 +- .../OPNsense/AcmeClient/LeValidation/Base.php | 1 + .../models/OPNsense/AcmeClient/AcmeClient.xml | 2 +- .../OPNsense/AcmeClient/Migrations/M1_6_0.php | 2 +- .../OPNsense/AcmeClient/Migrations/M4_0_0.php | 107 ++++++++++++++++++ .../scripts/OPNsense/AcmeClient/setup.sh | 4 +- 10 files changed, 159 insertions(+), 5 deletions(-) create mode 100644 security/acme-client/src/opnsense/mvc/app/models/OPNsense/AcmeClient/Migrations/M4_0_0.php diff --git a/security/acme-client/pkg-descr b/security/acme-client/pkg-descr index bfec3433c..a7435246c 100644 --- a/security/acme-client/pkg-descr +++ b/security/acme-client/pkg-descr @@ -8,8 +8,18 @@ WWW: https://github.com/acmesh-official/acme.sh Plugin Changelog ================ +4.0 + +NOTE: This is a new major release with backwards-incompatible changes. +Downgrade to older releases is not supported. Be sure to create a +full backup and include /var/etc/acme-client. + +Changed: +* use a dedicated acme.sh runtime directory for every cert (#3127) + Fixed: * fix sporadic command failure with gcloud DNS API (#3745) +* fix errors when the same Common Name is used multiple times (#3127) 3.20 diff --git a/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeAccount.php b/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeAccount.php index 1186dbccb..7ff90ceda 100644 --- a/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeAccount.php +++ b/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeAccount.php @@ -224,6 +224,9 @@ class LeAccount extends LeCommon return false; } + // Fix account config + $this->fixConfig(); + // Update account status. LeUtils::log_error('account registration successful for ' . $this->config->name); $this->setStatus(200); @@ -233,4 +236,34 @@ class LeAccount extends LeCommon return true; } + + /** + * Remove CERT_HOME property from account config, + * otherwise --cert-home will be ignored by acme.sh. + */ + public function fixConfig() + { + $account_conf_dir = self::ACME_BASE_ACCOUNT_DIR . '/' . (string)$this->config->id . '_' . $this->ca_compat; + $account_conf_file = $account_conf_dir . '/account.conf'; + + if (is_dir($account_conf_dir)) { + if (is_file($account_conf_file)) { + // Parse config file and remove property + $account_conf = parse_ini_file($account_conf_file); + if (isset($account_conf['CERT_HOME'])) { + unset($account_conf['CERT_HOME']); + } + + // Convert array back to ini file format + $new_account_conf = array(); + foreach ($account_conf as $key => $value) { + $new_account_conf[] = "${key}='${value}'"; + } + + // Write changes back to file + file_put_contents($account_conf_file, implode("\n", $new_account_conf) . "\n"); + chmod($account_conf_file, 0600); + } + } + } } diff --git a/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeAutomation/Base.php b/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeAutomation/Base.php index fc3119580..f85a23d14 100644 --- a/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeAutomation/Base.php +++ b/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeAutomation/Base.php @@ -71,6 +71,7 @@ abstract class Base extends \OPNsense\AcmeClient\LeCommon // Store acme filenames $this->acme_args[] = LeUtils::execSafe('--home %s', self::ACME_HOME_DIR); + $this->acme_args[] = LeUtils::execSafe('--cert-home %s', sprintf(self::ACME_CERT_HOME_DIR, $this->cert_id)); $this->acme_args[] = LeUtils::execSafe('--certpath %s', sprintf(self::ACME_CERT_FILE, $this->cert_id)); $this->acme_args[] = LeUtils::execSafe('--keypath %s', sprintf(self::ACME_KEY_FILE, $this->cert_id)); $this->acme_args[] = LeUtils::execSafe('--capath %s', sprintf(self::ACME_CHAIN_FILE, $this->cert_id)); diff --git a/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeCertificate.php b/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeCertificate.php index 177651853..0b319d34b 100644 --- a/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeCertificate.php +++ b/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeCertificate.php @@ -95,6 +95,7 @@ class LeCertificate extends LeCommon // Store acme filenames $this->acme_args[] = LeUtils::execSafe('--home %s', self::ACME_HOME_DIR); + $this->acme_args[] = LeUtils::execSafe('--cert-home %s', sprintf(self::ACME_CERT_HOME_DIR, $this->config->id)); $this->acme_args[] = LeUtils::execSafe('--certpath %s', $this->cert_file); $this->acme_args[] = LeUtils::execSafe('--keypath %s', $this->cert_key_file); $this->acme_args[] = LeUtils::execSafe('--capath %s', $this->cert_chain_file); diff --git a/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeCommon.php b/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeCommon.php index bf278b7aa..f7237bfcc 100644 --- a/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeCommon.php +++ b/security/acme-client/src/opnsense/mvc/app/library/OPNsense/AcmeClient/LeCommon.php @@ -1,7 +1,7 @@ acme_args[] = LeUtils::execSafe('--home %s', self::ACME_HOME_DIR); + $this->acme_args[] = LeUtils::execSafe('--cert-home %s', sprintf(self::ACME_CERT_HOME_DIR, $this->cert_id)); $this->acme_args[] = LeUtils::execSafe('--certpath %s', sprintf(self::ACME_CERT_FILE, $this->cert_id)); $this->acme_args[] = LeUtils::execSafe('--keypath %s', sprintf(self::ACME_KEY_FILE, $this->cert_id)); $this->acme_args[] = LeUtils::execSafe('--capath %s', sprintf(self::ACME_CHAIN_FILE, $this->cert_id)); diff --git a/security/acme-client/src/opnsense/mvc/app/models/OPNsense/AcmeClient/AcmeClient.xml b/security/acme-client/src/opnsense/mvc/app/models/OPNsense/AcmeClient/AcmeClient.xml index a174304f8..b69d95fb3 100644 --- a/security/acme-client/src/opnsense/mvc/app/models/OPNsense/AcmeClient/AcmeClient.xml +++ b/security/acme-client/src/opnsense/mvc/app/models/OPNsense/AcmeClient/AcmeClient.xml @@ -1,6 +1,6 @@ //OPNsense/AcmeClient - 3.5.0 + 4.0.0 A secure ACME Client plugin diff --git a/security/acme-client/src/opnsense/mvc/app/models/OPNsense/AcmeClient/Migrations/M1_6_0.php b/security/acme-client/src/opnsense/mvc/app/models/OPNsense/AcmeClient/Migrations/M1_6_0.php index 22bb91001..f6982d236 100644 --- a/security/acme-client/src/opnsense/mvc/app/models/OPNsense/AcmeClient/Migrations/M1_6_0.php +++ b/security/acme-client/src/opnsense/mvc/app/models/OPNsense/AcmeClient/Migrations/M1_6_0.php @@ -75,7 +75,7 @@ class M1_6_0 extends BaseModelMigration } // Write changes back to file - file_put_contents($account_file, implode("\r\n", $new_account_conf) . "\n"); + file_put_contents($account_file, implode("\n", $new_account_conf) . "\n"); chmod($account_file, 0600); // Finally, rename account directory diff --git a/security/acme-client/src/opnsense/mvc/app/models/OPNsense/AcmeClient/Migrations/M4_0_0.php b/security/acme-client/src/opnsense/mvc/app/models/OPNsense/AcmeClient/Migrations/M4_0_0.php new file mode 100644 index 000000000..2a776cbe9 --- /dev/null +++ b/security/acme-client/src/opnsense/mvc/app/models/OPNsense/AcmeClient/Migrations/M4_0_0.php @@ -0,0 +1,107 @@ + $value) { + $new_account_conf[] = "${key}='${value}'"; + } + + // Write changes back to file + file_put_contents($account_file, implode("\n", $new_account_conf) . "\n"); + chmod($account_file, 0600); + } + } + } + + // Create new acme home directory + if (!is_dir($new_acme_home)) { + mkdir($new_acme_home, 0750); + } + + // Migrate all certificates to new directory + // OLD: /var/etc/acme-client/home/opnsense.example.com + // NEW: /var/etc/acme-client/cert-home/659971be677b69.19708532/opnsense.example.com + foreach ($model->getNodeByReference('certificates.certificate')->iterateItems() as $cert) { + $cert_id = (string)$cert->id; + $cert_name = (string)$cert->name; + + $old_cert_home = $old_acme_home . $cert_name; + $new_cert_home = $new_acme_home . $cert_id . '/' . $cert_name; + $old_cert_home_ecc = $old_acme_home . $cert_name . '_ecc'; + $new_cert_home_ecc = $new_acme_home . $cert_id . '/' . $cert_name . '_ecc'; + $_parent_dir = $new_acme_home . $cert_id; + + // Check if cert home directory exists + // Certs that haven't been issued yet don't need to be migrated. + if (is_dir($old_cert_home)) { + // Create parent directory + if (!is_dir($_parent_dir)) { + mkdir($_parent_dir, 0750); + } + // Rename cert home directory + rename($old_cert_home, $new_cert_home); + } + + // Migrate ECC certs + if (is_dir($old_cert_home_ecc)) { + // Create parent directory + if (!is_dir($_parent_dir)) { + mkdir($_parent_dir, 0750); + } + // Rename cert home directory + rename($old_cert_home_ecc, $new_cert_home_ecc); + } + } + } +} diff --git a/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient/setup.sh b/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient/setup.sh index fd9b73425..20a65224b 100755 --- a/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient/setup.sh +++ b/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient/setup.sh @@ -1,9 +1,9 @@ #!/bin/sh ACME_BASE="/var/etc/acme-client" -ACME_DIRS="/var/etc/acme-client/certs /var/etc/acme-client/keys /var/etc/acme-client/configs /var/etc/acme-client/challenges /var/etc/acme-client/home" +ACME_DIRS="/var/etc/acme-client/certs /var/etc/acme-client/keys /var/etc/acme-client/configs /var/etc/acme-client/challenges /var/etc/acme-client/home /var/etc/acme-client/cert-home" -# Generate required directories and set owner/mode recursively. +# Create required directories and set owner/mode recursively. for directory in ${ACME_DIRS}; do mkdir -p ${directory} chown -R root:wheel ${directory}