system: contain abysmal CRL code in crlmanager page

Also try to reconfigure after write_config() which is the sensible
thing to do...
This commit is contained in:
Franco Fichtner 2022-10-06 11:39:06 +02:00
parent 9a77be0040
commit 7fec5111bd
3 changed files with 169 additions and 168 deletions

View File

@ -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";

View File

@ -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);

View File

@ -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 {