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 e7af982e8..fdd5b6a6d 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Trust/Api/CrlController.php +++ b/src/opnsense/mvc/app/controllers/OPNsense/Trust/Api/CrlController.php @@ -39,6 +39,16 @@ use OPNsense\Trust\Store as CertStore; */ class CrlController extends ApiControllerBase { + private static $status_codes = [ + '0' => 'unspecified', + '1' => 'keyCompromise', + '2' => 'cACompromise', + '3' => 'affiliationChanged', + '4' => 'superseded', + '5' => 'cessationOfOperation', + '6' => 'certificateHold' + ]; + public function searchAction() { $this->sessionClose(); @@ -55,6 +65,9 @@ class CrlController extends ApiControllerBase return $this->searchRecordsetBase(array_values($items)); } + /** + * fetch (a new) revocation list for a given autority. + */ public function getAction($caref) { if ($this->request->isGet() && !empty($caref)) { @@ -67,7 +80,7 @@ class CrlController extends ApiControllerBase } } if ($found) { - $result = ['caref' => $caref]; + $result = ['caref' => $caref, 'descr' => '']; foreach ($config->crl as $node) { if ((string)$node->caref == $caref) { $result['descr'] = (string)$node->descr; @@ -76,21 +89,153 @@ class CrlController extends ApiControllerBase $certs = []; foreach ($config->cert as $node) { if ((string)$node->caref == $caref) { - $certs[(string)$node->refid] = (string)255; + $certs[(string)$node->refid] = [ + 'code' => null, + 'descr' => (string)$node->descr + ]; } } + $crlmethod = 'internal'; foreach ($config->crl as $node) { if ((string)$node->caref == $caref) { foreach ($node->cert as $cert) { if (!empty((string)$cert->refid)) { - $certs[(string)$cert->refid] = (string)$cert->reason; + $certs[(string)$cert->refid] = [ + 'code' => (string)$cert->reason == '-1' ? '0' : (string)$cert->reason, + 'descr' => (string)$cert->descr + ]; } } + $crlmethod = (string)$node->crlmethod; + $result['text'] = !empty((string)$node->text) ? base64_decode((string)$node->text) : ''; + } + } + $result['crlmethod'] = [ + 'internal' => [ + 'value' => gettext('Internal'), + 'selected' => $crlmethod == 'internal' ? '1' : '0' + ], + 'existing' => [ + 'value' => gettext('Import existing'), + 'selected' => $crlmethod == 'existing' ? '1' : '0' + ], + ]; + for ($i=0 ; $i <= 6; $i++) { + $code = (string)$i; + $result['revoked_reason_' . $code] = []; + foreach ($certs as $ref => $data) { + $result['revoked_reason_' . $code][$ref] = [ + 'value' => $data['descr'], + 'selected' => $data['code'] === $code ? '1' : '0' + ]; } } - return ['crl' => $result, 'certs' => $certs]; + return ['crl' => $result]; } + return ['caref' => '', 'descr' => '']; } } + + /** + * set crl for a certificate authority, mimicking standard model operations + * (which we can not use due to the nested structure of the CRL's) + */ + public function setAction($caref) + { + if ($this->request->isPost() && !empty($caref)) { + Config::getInstance()->lock(); + $config = Config::getInstance()->object(); + $payload = $_POST['crl'] ?? []; + $validations = []; + if (!in_array($payload['crlmethod'], ['internal', 'existing'])) { + $validations['crl.crlmethod'] = [sprintf(gettext('Invalid method %s'), $payload['crlmethod'])]; + } + if (!preg_match('/^(.){1,255}$/', $payload['descr'] ?? '')) { + $validations['crl.descr'] = gettext('Description should be a string between 1 and 255 characters.'); + } + $found = false; + foreach ($config->ca as $node) { + if ((string)$node->refid == $caref) { + $found = true; + break; + } + } + if (!$found) { + $validations['crl.caref'] = gettext('Certificate does not seem to exist'); + } + + if (!empty($validations)) { + return ['status' => 'failed', 'validations' => $validations]; + } else { + $revoked_refs = []; + for ($i=0 ; $i <= 6; $i++) { + $fieldname = 'revoked_reason_' . (string)$i; + foreach (explode(',', $payload[$fieldname] ?? '') as $refid) { + if (!empty($refid)) { + $revoked_refs[$refid] = (string)$i; + } + } + } + $crl = null; + foreach ($config->crl as $node) { + if ((string)$node->caref == $caref) { + if ($crl !== null) { + /* When duplicate CRL's exist, remove all but the first */ + $dom = dom_import_simplexml($node); + $dom->parentNode->removeChild($dom); + } else { + $crl = $node; + } + } + } + $last_crl = null; + if ($crl === null) { + $last_crl = current($config->xpath('//opnsense/crl[last()]')); + if ($last_crl) { + $crl = simplexml_load_string(''); + } else { + $crl = $config->addChild('crl'); + } + } + $crl->caref = (string)$caref; + $crl->descr = (string)$payload['descr']; + foreach ($crl->cert as $cert) { + if (!isset($revoked_refs[(string)$cert->refid])) { + $dom = dom_import_simplexml($cert); + $dom->parentNode->removeChild($dom); + } else { + $cert->reason = $revoked_refs[(string)$cert->refid]; + unset($revoked_refs[(string)$cert->refid]); + } + } + foreach ($config->cert as $cert) { + if (isset($revoked_refs[(string)$cert->refid])) { + $tmp = $crl->addChild('cert'); + $tmp->refid = (string)$cert->refid; + $tmp->descr = (string)$cert->descr; + $tmp->caref = (string)$cert->caref; + $tmp->crt = (string)$cert->crt; + $tmp->prv = (string)$cert->prv; + $tmp->revoke_time = (string)time(); + $tmp->reason = $revoked_refs[(string)$cert->refid]; + } + } + + if ($last_crl) { + /* insert new item after last crl */ + $target = dom_import_simplexml($last_crl); + $insert = $target->ownerDocument->importNode(dom_import_simplexml($crl), true); + if ($target->nextSibling) { + $target->parentNode->insertBefore($insert, $target->nextSibling); + } else { + $target->parentNode->appendChild($insert); + } + } + Config::getInstance()->save(); + return ['status' => 'saved']; + } + } + return ['status' => 'failed']; + } } diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Trust/CrlController.php b/src/opnsense/mvc/app/controllers/OPNsense/Trust/CrlController.php index fc1d567f4..bc1efa98a 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Trust/CrlController.php +++ b/src/opnsense/mvc/app/controllers/OPNsense/Trust/CrlController.php @@ -32,6 +32,7 @@ class CrlController extends \OPNsense\Base\IndexController { public function indexAction() { + $this->view->formDialogCrl = $this->getForm("dialogCrl"); $this->view->pick('OPNsense/Trust/crl'); } } diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Trust/forms/dialogCrl.xml b/src/opnsense/mvc/app/controllers/OPNsense/Trust/forms/dialogCrl.xml new file mode 100644 index 000000000..42693c0a7 --- /dev/null +++ b/src/opnsense/mvc/app/controllers/OPNsense/Trust/forms/dialogCrl.xml @@ -0,0 +1,70 @@ +
+ + crl.crlmethod + + dropdown + + + crl.caref + + info + + + crl.descr + + text + + + crl.text + + textbox + + + + header + + + + + crl.revoked_reason_0 + + select_multiple + + + + crl.revoked_reason_1 + + select_multiple + + + + crl.revoked_reason_2 + + select_multiple + + + + crl.revoked_reason_3 + + select_multiple + + + + crl.revoked_reason_4 + + select_multiple + + + + crl.revoked_reason_5 + + select_multiple + + + + crl.revoked_reason_6 + + select_multiple + + +
diff --git a/src/opnsense/mvc/app/views/OPNsense/Trust/crl.volt b/src/opnsense/mvc/app/views/OPNsense/Trust/crl.volt index 3817357ef..3c64f2419 100644 --- a/src/opnsense/mvc/app/views/OPNsense/Trust/crl.volt +++ b/src/opnsense/mvc/app/views/OPNsense/Trust/crl.volt @@ -39,12 +39,51 @@ $(this).html($("#crl\\.descr").val() !== '' ? $("#crl\\.descr").val() : '-'); $(this).show(); }); + + $("#DialogCrl").change(function(){ + if ($("#edit_crl").is(':visible')) { + $("#tab_crls").click(); + $("#DialogCrl").hide(); + } + }); + + $("#crl\\.crlmethod").change(function(event){ + let this_action = $(this).val(); + $(".crlmethod").each(function(){ + let target = null; + if ($(this)[0].tagName == 'DIV') { + target = $(this) + } else { + target = $(this).closest("tr"); + } + target.hide(); + if ($(this).hasClass('crlmethod_' + this_action)) { + target.show(); + } + }); + }); + + /* when revoking a certificate, make sure we only add the same cert in one group */ + $(".revoked").change(function(event){ + event.preventDefault(); + let values = $(this).val(); + let that = $(this).attr('id'); + $("select.revoked").each(function(){ + if ($(this).attr('id') !== that) { + let target = $(this); + values.forEach(function(refid){ + target.find('option[value="'+refid+'"]').prop("selected", false); + }); + target.selectpicker('refresh'); + } + }); + }) });
@@ -73,8 +112,19 @@
- - + {{ partial("layout_partials/base_form",['fields':formDialogCrl,'id':'DialogCrl'])}}
+ + + + + + +
+ +