From bed2e66cf0ce22bc80ca566b4744e1cd82354be3 Mon Sep 17 00:00:00 2001 From: Ad Schellevis Date: Wed, 13 Mar 2024 21:01:33 +0100 Subject: [PATCH] System: Trust: Revocation - work in progress for https://github.com/opnsense/core/issues/7248 * add raw dump button * generate CRL and store text * remove add/del footer buttons --- .../OPNsense/Trust/Api/CrlController.php | 60 +++++++++++++++++++ .../mvc/app/library/OPNsense/Trust/Store.php | 24 ++++++++ .../mvc/app/views/OPNsense/Trust/crl.volt | 50 ++++++++++++---- 3 files changed, 124 insertions(+), 10 deletions(-) diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Trust/Api/CrlController.php b/src/opnsense/mvc/app/controllers/OPNsense/Trust/Api/CrlController.php index db840b6a8..14176a2f8 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Trust/Api/CrlController.php +++ b/src/opnsense/mvc/app/controllers/OPNsense/Trust/Api/CrlController.php @@ -231,6 +231,27 @@ class CrlController extends ApiControllerBase } } } + $x509_crl = new \phpseclib3\File\X509(); + if (empty($validations['crl.caref'])) { + /* + * 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->loadCA($ca_crt_str); + $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)) { + $validations['crl.caref'] = gettext('Cert revocation error: CRL signature invalid'); + } + } catch (Exception $e) { + $validations['crl.caref'] = gettext('Cert revocation error: CRL signature invalid') . " " . $e; + } + } if (!empty($validations)) { Config::getInstance()->unlock(); @@ -279,12 +300,16 @@ class CrlController extends ApiControllerBase } $crl->caref = (string)$caref; $crl->descr = (string)$payload['descr']; + $crl->serial = !empty($payload['serial']) ? $payload['serial'] : $crl->serial; + $crl->serial = ((int)((string)$crl->serial)) + 1; $to_delete = []; + $crl_certs = []; foreach ($crl->cert as $cert) { if (!isset($revoked_refs[(string)$cert->refid])) { $to_delete[] = $cert; } else { $cert->reason = $revoked_refs[(string)$cert->refid]; + $crl_certs[] = $cert; unset($revoked_refs[(string)$cert->refid]); } } @@ -302,8 +327,31 @@ class CrlController extends ApiControllerBase $tmp->prv = (string)$cert->prv; $tmp->revoke_time = (string)time(); $tmp->reason = $revoked_refs[(string)$cert->refid]; + $crl_certs[] = $tmp; } } + if ($payload['crlmethod'] == 'internal') { + /* add all cert serial numbers to crl */ + foreach ($crl_certs as $cert) { + $tmp = @openssl_x509_parse(base64_decode((string)$cert->crt)); + if ($tmp !== false && isset($tmp['serialNumber'])) { + $x509_crl->setRevokedCertificateExtension( + (string)$tmp['serialNumber'], + 'id-ce-cRLReasons', + self::$status_codes[(string)$cert->reason] + ); + } + } + $x509_crl->setSerialNumber((string)$crl->serial, 10); + /* consider dates after 2050 lifetime in GeneralizedTime format (rfc5280#section-4.1.2.5) */ + $date = new \DateTimeImmutable( + '+' . (string)$crl->lifetime . ' days', + new \DateTimeZone(@date_default_timezone_get()) + ); + $x509_crl->setEndDate((int)$date->format("Y") < 2050 ? $date : 'lifetime'); + $new_crl = $x509_crl->signCRL($ca_cert, $x509_crl); + $crl->text = base64_encode($x509_crl->saveCRL($new_crl) . PHP_EOL); + } if ($last_crl) { /* insert new item after last crl */ @@ -321,4 +369,16 @@ class CrlController extends ApiControllerBase } return ['status' => 'failed']; } + + + public function rawDumpAction($uuid) + { + $payload = $this->getAction($uuid); + if (!empty($payload['crl'])) { + if (!empty($payload['crl']['text'])) { + return CertStore::dumpCRL($payload['crl']['text']); + } + } + return []; + } } diff --git a/src/opnsense/mvc/app/library/OPNsense/Trust/Store.php b/src/opnsense/mvc/app/library/OPNsense/Trust/Store.php index 314f23606..2102a8ef7 100644 --- a/src/opnsense/mvc/app/library/OPNsense/Trust/Store.php +++ b/src/opnsense/mvc/app/library/OPNsense/Trust/Store.php @@ -555,6 +555,30 @@ class Store return $result; } + /** + * @param string $cert certificate + * @return array [stdout|stderr] + */ + public static function dumpCRL($cert) + { + $result = []; + $process = proc_open( + '/usr/local/bin/openssl crl -fingerprint -sha256 -text', + [["pipe", "r"], ["pipe", "w"], ["pipe", "w"]], + $pipes + ); + if (is_resource($process)) { + fwrite($pipes[0], $cert); + fclose($pipes[0]); + $result['stdout'] = stream_get_contents($pipes[1]); + fclose($pipes[1]); + $result['stderr'] = stream_get_contents($pipes[2]); + fclose($pipes[2]); + proc_close($process); + } + return $result; + } + /** diff --git a/src/opnsense/mvc/app/views/OPNsense/Trust/crl.volt b/src/opnsense/mvc/app/views/OPNsense/Trust/crl.volt index 3c64f2419..1fa72b7b3 100644 --- a/src/opnsense/mvc/app/views/OPNsense/Trust/crl.volt +++ b/src/opnsense/mvc/app/views/OPNsense/Trust/crl.volt @@ -32,7 +32,30 @@ get:'/api/trust/crl/get/', set:'/api/trust/crl/set/', del:'/api/trust/crl/del/', - datakey: 'refid' + datakey: 'refid', + commands: { + raw_dump: { + method: function(event){ + let refid = $(this).data("row-id") !== undefined ? $(this).data("row-id") : ''; + ajaxGet('/api/trust/crl/raw_dump/' + refid, {}, function(data, status){ + if (data.stdout) { + BootstrapDialog.show({ + title: "{{ lang._('CRL info') }}", + type:BootstrapDialog.TYPE_INFO, + message: $("
").text(data.stdout).html(), + cssClass: 'monospace-dialog', + }); + } + }); + }, + classname: 'fa fa-fw fa-info-circle', + title: "{{ lang._('show certificate info') }}", + sequence: 10 + }, + copy: { + classname: undefined + } + } }); $("#DialogCrl").click(function(){ @@ -82,6 +105,22 @@ + +