From 875fb5d8b3be347265bc86a6115c2d653c2fa1a6 Mon Sep 17 00:00:00 2001 From: Ad Schellevis Date: Sun, 3 Mar 2024 15:40:49 +0100 Subject: [PATCH] System: Trust: Certificates - work in progress for https://github.com/opnsense/core/issues/7248 * add certificate purpose according to rfc3280 * add info button for raw (readable) certificate and csr output --- .../OPNsense/Trust/Api/CertController.php | 17 +++- .../mvc/app/library/OPNsense/Trust/Store.php | 86 +++++++++++++++++++ .../mvc/app/models/OPNsense/Trust/Cert.xml | 1 + .../mvc/app/views/OPNsense/Trust/cert.volt | 42 ++++++++- 4 files changed, 141 insertions(+), 5 deletions(-) diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Trust/Api/CertController.php b/src/opnsense/mvc/app/controllers/OPNsense/Trust/Api/CertController.php index 4588c3edd..e196e369f 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Trust/Api/CertController.php +++ b/src/opnsense/mvc/app/controllers/OPNsense/Trust/Api/CertController.php @@ -141,7 +141,9 @@ class CertController extends ApiMutableModelControllerBase $filter_funct = function ($record) use ($carefs) { return empty($carefs) || array_intersect(explode(',', $record->caref), $carefs); }; - return $this->searchBase('cert', ['descr', 'caref', 'name', 'valid_from', 'valid_to'], null, $filter_funct); + return $this->searchBase( + 'cert', ['descr', 'caref', 'rfc3280_purpose', 'name', 'valid_from', 'valid_to'], null, $filter_funct + ); } public function getAction($uuid = null) @@ -179,6 +181,19 @@ class CertController extends ApiMutableModelControllerBase return []; } + public function rawDumpAction($uuid) + { + $payload = $this->getBase('cert', 'cert', $uuid); + if (!empty($payload['cert'])) { + if (!empty($payload['cert']['crt_payload'])) { + return CertStore::dumpX509($payload['cert']['crt_payload']); + } elseif (!empty($payload['cert']['csr_payload'])) { + return CertStore::dumpCSR($payload['cert']['csr_payload']); + } + } + return []; + } + public function caListAction() { $result = []; diff --git a/src/opnsense/mvc/app/library/OPNsense/Trust/Store.php b/src/opnsense/mvc/app/library/OPNsense/Trust/Store.php index db610b48c..2a732e634 100644 --- a/src/opnsense/mvc/app/library/OPNsense/Trust/Store.php +++ b/src/opnsense/mvc/app/library/OPNsense/Trust/Store.php @@ -446,11 +446,97 @@ class Store $result[$target] = implode('\n', $values); } } + + /* Extract certificate purpose */ + $purpose = []; + foreach (['basicConstraints', 'extendedKeyUsage', 'keyUsage', 'authorityInfoAccess'] as $ext) { + $purpose[$ext] = []; + if (!empty($crt['extensions'][$ext])) { + foreach (explode(",", $crt['extensions'][$ext]) as $item) { + $purpose[$ext][] = trim($item); + } + } + } + + // rfc3280 purpose definitions + $result['rfc3280_purpose'] = ''; + if ( + in_array('TLS Web Server Authentication', $purpose['extendedKeyUsage']) && + in_array('Digital Signature', $purpose['keyUsage']) && ( + in_array('Key Encipherment', $purpose['keyUsage']) || + in_array('Key Agreement', $purpose['keyUsage']) + ) + ) { + $result['rfc3280_purpose'] = 'id-kp-serverAuth'; + } elseif ( + in_array('TLS Web Client Authentication', $purpose['extendedKeyUsage']) && + in_array('Digital Signature', $purpose['keyUsage']) + ) { + $result['rfc3280_purpose'] = 'id-kp-clientAuth'; + } elseif ( + in_array('OCSP Signing', $purpose['extendedKeyUsage']) && + in_array('Digital Signature', $purpose['keyUsage']) + ) { + $result['rfc3280_purpose'] = 'id-kp-OCSPSigning'; + } + // return $result; } return false; } + /** + * @param string $cert certificate + * @return array [stdout|stderr] + */ + public static function dumpX509($cert) + { + $result = []; + $process = proc_open( + '/usr/local/bin/openssl x509 -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; + } + + /** + * @param string $csr CSR + * @return array [stdout|stderr] + */ + public static function dumpCSR($csr) + { + $result = []; + $process = proc_open( + '/usr/local/bin/openssl req -text -noout', + [["pipe", "r"], ["pipe", "w"], ["pipe", "w"]], + $pipes + ); + if (is_resource($process)) { + fwrite($pipes[0], $csr); + 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; + } + + + /** * Extract certificate chain * @param string $caref reference number diff --git a/src/opnsense/mvc/app/models/OPNsense/Trust/Cert.xml b/src/opnsense/mvc/app/models/OPNsense/Trust/Cert.xml index 9afa01945..9a2ed1ae1 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Trust/Cert.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Trust/Cert.xml @@ -111,6 +111,7 @@ + diff --git a/src/opnsense/mvc/app/views/OPNsense/Trust/cert.volt b/src/opnsense/mvc/app/views/OPNsense/Trust/cert.volt index 783115d66..a07f62021 100644 --- a/src/opnsense/mvc/app/views/OPNsense/Trust/cert.volt +++ b/src/opnsense/mvc/app/views/OPNsense/Trust/cert.volt @@ -41,6 +41,26 @@ } return request; } + }, + commands: { + raw_dump: { + method: function(event){ + let uuid = $(this).data("row-id") !== undefined ? $(this).data("row-id") : ''; + ajaxGet('/api/trust/cert/raw_dump/' + uuid, {}, function(data, status){ + if (data.stdout) { + BootstrapDialog.show({ + title: "{{ lang._('Certificate 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 + } } }); grid_cert.on("loaded.rs.jquery.bootgrid", function (e){ @@ -126,13 +146,26 @@ } } }); - - - }); + + @@ -151,10 +184,11 @@ {{ lang._('ID') }} {{ lang._('Description') }} {{ lang._('Issuer') }} + {{ lang._('Purpose') }} {{ lang._('Name') }} {{ lang._('Valid from') }} {{ lang._('Valid to') }} - {{ lang._('Commands') }} + {{ lang._('Commands') }}