From 7fec5111bdbd50e80944aa8f808fe6f26e9a9441 Mon Sep 17 00:00:00 2001 From: Franco Fichtner Date: Thu, 6 Oct 2022 11:39:06 +0200 Subject: [PATCH] system: contain abysmal CRL code in crlmanager page Also try to reconfigure after write_config() which is the sensible thing to do... --- src/etc/inc/certs.inc | 161 -------------------------------- src/www/system_certmanager.php | 14 ++- src/www/system_crlmanager.php | 162 ++++++++++++++++++++++++++++++++- 3 files changed, 169 insertions(+), 168 deletions(-) diff --git a/src/etc/inc/certs.inc b/src/etc/inc/certs.inc index 6870cae8e..827e03202 100644 --- a/src/etc/inc/certs.inc +++ b/src/etc/inc/certs.inc @@ -54,39 +54,6 @@ function phpseclib_autoload($namespace, $dir) ); } -/* Autoload phpseclib3 and it's dependencies */ -phpseclib_autoload('ParagonIE\ConstantTime', '/usr/local/share/phpseclib/paragonie'); -phpseclib_autoload('phpseclib3', '/usr/local/share/phpseclib'); - -use phpseclib3\File\X509; -use phpseclib3\Crypt\PublicKeyLoader; -use phpseclib3\Crypt\RSA; -use phpseclib3\Exception\NoKeyLoadedException; - -define("CERT_CRL_STATUS_NOSTATUS", -1); -define("CERT_CRL_STATUS_UNSPECIFIED", 0); -define("CERT_CRL_STATUS_KEYCOMPROMISE", 1); -define("CERT_CRL_STATUS_CACOMPROMISE", 2); -define("CERT_CRL_STATUS_AFFILIATIONCHANGED", 3); -define("CERT_CRL_STATUS_SUPERSEDED", 4); -define("CERT_CRL_STATUS_CESSATIONOFOPERATION", 5); -define("CERT_CRL_STATUS_CERTIFICATEHOLD", 6); - -function crl_status_code() -{ - /* Array index 0 is a description, index 1 is the key used by phpseclib */ - return array( - CERT_CRL_STATUS_NOSTATUS => ["No Status (default)", "unspecified"], - CERT_CRL_STATUS_UNSPECIFIED => ["Unspecified", "unspecified"], - CERT_CRL_STATUS_KEYCOMPROMISE => ["Key Compromise", "keyCompromise"], - CERT_CRL_STATUS_CACOMPROMISE => ["CA Compromise", "cACompromise"], - CERT_CRL_STATUS_AFFILIATIONCHANGED => ["Affiliation Changed", "affiliationChanged"], - CERT_CRL_STATUS_SUPERSEDED => ["Superseded", "superseded"], - CERT_CRL_STATUS_CESSATIONOFOPERATION => ["Cessation of Operation", "cessationOfOperation"], - CERT_CRL_STATUS_CERTIFICATEHOLD => ["Certificate Hold", "certificateHold"] - ); -} - function &lookup_ca($refid) { global $config; @@ -627,133 +594,6 @@ function cert_in_use($certref) is_ipsec_cert($certref)); } -function crl_update(&$crl) -{ - $ca =& lookup_ca($crl['caref']); - if (!$ca) { - return false; - } - // If we have text but no certs, it was imported and cannot be updated. - if (!is_crl_internal($crl)) { - return false; - } - - $crl['serial']++; - $ca_str_crt = base64_decode($ca['crt']); - $ca_str_key = base64_decode($ca['prv']); - if (!openssl_x509_check_private_key($ca_str_crt, $ca_str_key)) { - syslog(LOG_ERR, "Cert revocation error: CA keys mismatch"); - return false; - } - - if (!class_exists(X509::class)) { - syslog(LOG_ERR, 'Cert revocation error: phpseclib3 not loaded'); - return false; - } - - /* Load in the CA's cert */ - $ca_cert = new X509(); - $ca_cert->loadX509($ca_str_crt); - - if (!$ca_cert->validateDate()) { - syslog(LOG_ERR, 'Cert revocation error: CA certificate invalid: invalid date'); - return false; - } - - /* get the private key to sign the new (updated) CRL */ - try { - $ca_key = PublicKeyLoader::loadPrivateKey($ca_str_key); - if (method_exists($ca_key, 'withPadding')) { - $ca_key = $ca_key->withPadding(RSA::ENCRYPTION_PKCS1 | RSA::SIGNATURE_PKCS1); - } - $ca_cert->setPrivateKey($ca_key); - } catch (NoKeyLoadedException $e) { - syslog(LOG_ERR, 'Cert revocation error: Unable to load CA private key'); - return false; - } - - /* Load the CA for later signature validation */ - $x509_crl = new X509(); - $x509_crl->loadCA($ca_str_crt); - - /* - * create empty CRL. A quirk with phpseclib is that in order to correctly sign - * a new CRL, a CA must be loaded using a separate X509 container, which is passed - * to signCRL(). However, to validate the resulting signature, the original X509 - * CRL container must load the same CA using loadCA() with a direct reference - * to the CA's public cert. - */ - $x509_crl->loadCRL($x509_crl->saveCRL($x509_crl->signCRL($ca_cert, $x509_crl))); - - /* Now validate the CRL to see if everything went well */ - try { - if (!$x509_crl->validateSignature(false)) { - syslog(LOG_ERR, 'Cert revocation error: CRL signature invalid'); - return false; - } - } catch (Exception $e) { - syslog(LOG_ERR, 'Cert revocation error: CRL signature invalid ' . $e); - return false; - } - - if (is_array($crl['cert']) && (count($crl['cert']) > 0)) { - foreach ($crl['cert'] as $cert) { - /* load the certificate in an x509 container to get its serial number and validate its signature */ - $x509_cert = new X509(); - $x509_cert->loadCA($ca_str_crt); - $raw_cert = $x509_cert->loadX509(base64_decode($cert['crt'])); - try { - if (!$x509_cert->validateSignature(false)) { - syslog(LOG_ERR, "Cert revocation error: Revoked certificate validation failed."); - return false; - } - } catch (Exception $e) { - syslog(LOG_ERR, 'Cert revocation error: Revoked certificate validation failed ' . $e); - return false; - } - /* Get serial number of cert */ - $sn = $raw_cert['tbsCertificate']['serialNumber']->toString(); - $x509_crl->setRevokedCertificateExtension($sn, 'id-ce-cRLReasons', crl_status_code()[$cert["reason"]][1]); - } - } - $x509_crl->setSerialNumber($crl['serial'], 10); - /* consider dates after 2050 lifetime in GeneralizedTime format (rfc5280#section-4.1.2.5) */ - $date = new \DateTimeImmutable('+' . $crl['lifetime'] . ' days', new \DateTimeZone(@date_default_timezone_get())); - if ((int)$date->format("Y") < 2050) { - $x509_crl->setEndDate($date); - } else { - $x509_crl->setEndDate('lifetime'); - } - - $new_crl = $x509_crl->signCRL($ca_cert, $x509_crl); - $crl_text = $x509_crl->saveCRL($new_crl) . PHP_EOL; - - /* Update the CRL */ - $crl['text'] = base64_encode($crl_text); - return true; -} - -function cert_revoke($cert, &$crl, $reason = CERT_CRL_STATUS_UNSPECIFIED) -{ - if (is_cert_revoked($cert, $crl['refid'])) { - return true; - } - // If we have text but no certs, it was imported and cannot be updated. - if (!is_crl_internal($crl)) { - return false; - } - $crl_before = $crl; - $cert["reason"] = $reason; - $cert["revoke_time"] = time(); - $crl["cert"][] = $cert; - if (!crl_update($crl)) { - $crl = $crl_before; - return false; - } - return true; -} - - /* Compare two certificates to see if they match. */ function cert_compare($cert1, $cert2) { @@ -821,7 +661,6 @@ function is_openvpn_server_crl($crlref) return false; } - function is_crl_internal($crl) { return $crl["crlmethod"] ?? '' == "internal"; diff --git a/src/www/system_certmanager.php b/src/www/system_certmanager.php index edb30eac9..57350690d 100644 --- a/src/www/system_certmanager.php +++ b/src/www/system_certmanager.php @@ -30,17 +30,21 @@ require_once('guiconfig.inc'); require_once('system.inc'); +phpseclib_autoload('ParagonIE\ConstantTime', '/usr/local/share/phpseclib/paragonie'); +phpseclib_autoload('phpseclib3', '/usr/local/share/phpseclib'); + +use phpseclib3\File\X509; + function csr_generate(&$cert, $keylen_curve, $dn, $digest_alg, $extns) { $configFilename = create_temp_openssl_config($extns); - - - $args = array( + $args = [ 'config' => $configFilename, 'req_extensions' => 'v3_req', 'digest_alg' => $digest_alg, 'encrypt_key' => false - ); + ]; + if (is_numeric($keylen_curve)) { $args['private_key_type'] = OPENSSL_KEYTYPE_RSA; $args['private_key_bits'] = (int)$keylen_curve; @@ -99,7 +103,7 @@ function parse_csr($csr_str) return array('parse_success' => false); } - $x509_lib = new \phpseclib3\File\X509(); + $x509_lib = new X509(); $csr = $x509_lib->loadCSR($csr_str); if ($csr === false) { return array('parse_success' => false); diff --git a/src/www/system_crlmanager.php b/src/www/system_crlmanager.php index 60033895d..ed289632a 100644 --- a/src/www/system_crlmanager.php +++ b/src/www/system_crlmanager.php @@ -29,6 +29,58 @@ require_once('guiconfig.inc'); +phpseclib_autoload('ParagonIE\ConstantTime', '/usr/local/share/phpseclib/paragonie'); +phpseclib_autoload('phpseclib3', '/usr/local/share/phpseclib'); + +use phpseclib3\File\X509; +use phpseclib3\Crypt\PublicKeyLoader; +use phpseclib3\Crypt\RSA; +use phpseclib3\Exception\NoKeyLoadedException; + +define("CERT_CRL_STATUS_NOSTATUS", -1); +define("CERT_CRL_STATUS_UNSPECIFIED", 0); +define("CERT_CRL_STATUS_KEYCOMPROMISE", 1); +define("CERT_CRL_STATUS_CACOMPROMISE", 2); +define("CERT_CRL_STATUS_AFFILIATIONCHANGED", 3); +define("CERT_CRL_STATUS_SUPERSEDED", 4); +define("CERT_CRL_STATUS_CESSATIONOFOPERATION", 5); +define("CERT_CRL_STATUS_CERTIFICATEHOLD", 6); + +function crl_status_code() +{ + /* Array index 0 is a description, index 1 is the key used by phpseclib */ + return array( + CERT_CRL_STATUS_NOSTATUS => ["No Status (default)", "unspecified"], + CERT_CRL_STATUS_UNSPECIFIED => ["Unspecified", "unspecified"], + CERT_CRL_STATUS_KEYCOMPROMISE => ["Key Compromise", "keyCompromise"], + CERT_CRL_STATUS_CACOMPROMISE => ["CA Compromise", "cACompromise"], + CERT_CRL_STATUS_AFFILIATIONCHANGED => ["Affiliation Changed", "affiliationChanged"], + CERT_CRL_STATUS_SUPERSEDED => ["Superseded", "superseded"], + CERT_CRL_STATUS_CESSATIONOFOPERATION => ["Cessation of Operation", "cessationOfOperation"], + CERT_CRL_STATUS_CERTIFICATEHOLD => ["Certificate Hold", "certificateHold"] + ); +} + +function cert_revoke($cert, &$crl, $reason = CERT_CRL_STATUS_UNSPECIFIED) +{ + if (is_cert_revoked($cert, $crl['refid'])) { + return true; + } + // If we have text but no certs, it was imported and cannot be updated. + if (!is_crl_internal($crl)) { + return false; + } + $crl_before = $crl; + $cert["reason"] = $reason; + $cert["revoke_time"] = time(); + $crl["cert"][] = $cert; + if (!crl_update($crl)) { + $crl = $crl_before; + return false; + } + return true; +} + function cert_unrevoke($cert, &$crl) { if (!is_crl_internal($crl)) { @@ -52,6 +104,112 @@ function cert_unrevoke($cert, &$crl) return false; } +function crl_update(&$crl) +{ + $ca =& lookup_ca($crl['caref']); + if (!$ca) { + return false; + } + // If we have text but no certs, it was imported and cannot be updated. + if (!is_crl_internal($crl)) { + return false; + } + + $crl['serial']++; + $ca_str_crt = base64_decode($ca['crt']); + $ca_str_key = base64_decode($ca['prv']); + if (!openssl_x509_check_private_key($ca_str_crt, $ca_str_key)) { + syslog(LOG_ERR, "Cert revocation error: CA keys mismatch"); + return false; + } + + if (!class_exists(X509::class)) { + syslog(LOG_ERR, 'Cert revocation error: phpseclib3 not loaded'); + return false; + } + + /* Load in the CA's cert */ + $ca_cert = new X509(); + $ca_cert->loadX509($ca_str_crt); + + if (!$ca_cert->validateDate()) { + syslog(LOG_ERR, 'Cert revocation error: CA certificate invalid: invalid date'); + return false; + } + + /* get the private key to sign the new (updated) CRL */ + try { + $ca_key = PublicKeyLoader::loadPrivateKey($ca_str_key); + if (method_exists($ca_key, 'withPadding')) { + $ca_key = $ca_key->withPadding(RSA::ENCRYPTION_PKCS1 | RSA::SIGNATURE_PKCS1); + } + $ca_cert->setPrivateKey($ca_key); + } catch (NoKeyLoadedException $e) { + syslog(LOG_ERR, 'Cert revocation error: Unable to load CA private key'); + return false; + } + + /* Load the CA for later signature validation */ + $x509_crl = new X509(); + $x509_crl->loadCA($ca_str_crt); + + /* + * create empty CRL. A quirk with phpseclib is that in order to correctly sign + * a new CRL, a CA must be loaded using a separate X509 container, which is passed + * to signCRL(). However, to validate the resulting signature, the original X509 + * CRL container must load the same CA using loadCA() with a direct reference + * to the CA's public cert. + */ + $x509_crl->loadCRL($x509_crl->saveCRL($x509_crl->signCRL($ca_cert, $x509_crl))); + + /* Now validate the CRL to see if everything went well */ + try { + if (!$x509_crl->validateSignature(false)) { + syslog(LOG_ERR, 'Cert revocation error: CRL signature invalid'); + return false; + } + } catch (Exception $e) { + syslog(LOG_ERR, 'Cert revocation error: CRL signature invalid ' . $e); + return false; + } + + if (is_array($crl['cert']) && (count($crl['cert']) > 0)) { + foreach ($crl['cert'] as $cert) { + /* load the certificate in an x509 container to get its serial number and validate its signature */ + $x509_cert = new X509(); + $x509_cert->loadCA($ca_str_crt); + $raw_cert = $x509_cert->loadX509(base64_decode($cert['crt'])); + try { + if (!$x509_cert->validateSignature(false)) { + syslog(LOG_ERR, "Cert revocation error: Revoked certificate validation failed."); + return false; + } + } catch (Exception $e) { + syslog(LOG_ERR, 'Cert revocation error: Revoked certificate validation failed ' . $e); + return false; + } + /* Get serial number of cert */ + $sn = $raw_cert['tbsCertificate']['serialNumber']->toString(); + $x509_crl->setRevokedCertificateExtension($sn, 'id-ce-cRLReasons', crl_status_code()[$cert["reason"]][1]); + } + } + $x509_crl->setSerialNumber($crl['serial'], 10); + /* consider dates after 2050 lifetime in GeneralizedTime format (rfc5280#section-4.1.2.5) */ + $date = new \DateTimeImmutable('+' . $crl['lifetime'] . ' days', new \DateTimeZone(@date_default_timezone_get())); + if ((int)$date->format("Y") < 2050) { + $x509_crl->setEndDate($date); + } else { + $x509_crl->setEndDate('lifetime'); + } + + $new_crl = $x509_crl->signCRL($ca_cert, $x509_crl); + $crl_text = $x509_crl->saveCRL($new_crl) . PHP_EOL; + + /* Update the CRL */ + $crl['text'] = base64_encode($crl_text); + return true; +} + // prepare config types $a_crl = &config_read_array('crl'); $a_cert = &config_read_array('cert'); @@ -136,8 +294,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { } $name = $thiscert['descr']; if (cert_unrevoke($thiscert, $thiscrl)) { - plugins_configure('crl'); write_config(sprintf('Deleted certificate %s from CRL %s', $name, $thiscrl['descr'])); + plugins_configure('crl'); header(url_safe('Location: /system_crlmanager.php')); exit; } else { @@ -169,8 +327,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { if (!count($input_errors)) { $reason = (empty($pconfig['crlreason'])) ? CERT_CRL_STATUS_UNSPECIFIED : $pconfig['crlreason']; if (cert_revoke($cert, $crl, $reason)) { - plugins_configure('crl'); write_config(sprintf('Revoked certificate %s in CRL %s', $cert['descr'], $crl['descr'])); + plugins_configure('crl'); header(url_safe('Location: /system_crlmanager.php')); exit; } else {