From 0378c650d400aa44cc521089a26746d63cece93c Mon Sep 17 00:00:00 2001 From: Ad Schellevis Date: Tue, 10 Sep 2024 21:15:11 +0200 Subject: [PATCH] Add System: Trust: Settings page (#7854) * System: Trust: Settings - add boilerplate and move existing store_intermediate_certs setting to new module including migration * System: Trust: Settings - add configuration constraints and glue to flush CRL's to local trust store when requested. This implements the following options: * Enable/Disable legacy providers (enabled by default, which is the current default) * Option to write specific configuration constraints, when enabled, CipherString, Ciphersuites and MinProtocol[DTS] can be configured One last piece of the puzzle is the "crl" event action, which should deploy to the local trust store as well ehen requested. * Update src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml Co-authored-by: Franco Fichtner * System: Trust: Settings - process review comments https://github.com/opnsense/core/pull/7854 * System: Trust: Settings - flush CRL's when "Store CRL's" is selected --------- Co-authored-by: Franco Fichtner --- src/etc/inc/plugins.inc.d/core.inc | 14 ++++ src/etc/inc/system.inc | 18 ++++- .../OPNsense/Trust/Api/SettingsController.php | 52 +++++++++++++ .../OPNsense/Trust/SettingsController.php | 38 ++++++++++ .../OPNsense/Trust/forms/settings.xml | 74 +++++++++++++++++++ .../app/models/OPNsense/Core/Menu/Menu.xml | 1 + .../mvc/app/models/OPNsense/Trust/General.php | 35 +++++++++ .../mvc/app/models/OPNsense/Trust/General.xml | 53 +++++++++++++ .../OPNsense/Trust/Migrations/M1_0_0.php | 49 ++++++++++++ .../app/views/OPNsense/Trust/settings.volt | 73 ++++++++++++++++++ src/opnsense/scripts/system/ssl_ciphers.py | 12 ++- src/opnsense/scripts/system/tls_groups.py | 53 +++++++++++++ .../conf/actions.d/actions_system.conf | 13 ++++ .../templates/OPNsense/Trust/openssl.cnf | 26 ++++++- src/www/system_general.php | 35 --------- 15 files changed, 504 insertions(+), 42 deletions(-) create mode 100644 src/opnsense/mvc/app/controllers/OPNsense/Trust/Api/SettingsController.php create mode 100644 src/opnsense/mvc/app/controllers/OPNsense/Trust/SettingsController.php create mode 100644 src/opnsense/mvc/app/controllers/OPNsense/Trust/forms/settings.xml create mode 100644 src/opnsense/mvc/app/models/OPNsense/Trust/General.php create mode 100644 src/opnsense/mvc/app/models/OPNsense/Trust/General.xml create mode 100644 src/opnsense/mvc/app/models/OPNsense/Trust/Migrations/M1_0_0.php create mode 100644 src/opnsense/mvc/app/views/OPNsense/Trust/settings.volt create mode 100755 src/opnsense/scripts/system/tls_groups.py diff --git a/src/etc/inc/plugins.inc.d/core.inc b/src/etc/inc/plugins.inc.d/core.inc index 350b6e9d5..f1e75df14 100644 --- a/src/etc/inc/plugins.inc.d/core.inc +++ b/src/etc/inc/plugins.inc.d/core.inc @@ -469,6 +469,7 @@ function core_configure() 'syslog_reset' => ['system_syslog_reset'], 'trust_reload' => ['system_trust_configure'], 'user_changed' => ['core_user_changed_groups:2'], + 'crl' => ['core_trust_crl'], ]; } @@ -516,3 +517,16 @@ function core_user_changed_groups($unused, $username) } } } + +/** + * When CRL's are deployed locally, we need to flush them to disk. + * If at some point in time it turns out this event is too slow, we should split system_trust_configure() and possibly + * certctl.py to only process CRL's on demand. + */ +function core_trust_crl() +{ + $trust = new \OPNsense\Trust\General(); + if (!empty((string)$trust->install_crls)) { + system_trust_configure(); + } +} \ No newline at end of file diff --git a/src/etc/inc/system.inc b/src/etc/inc/system.inc index 640166a92..0c86ff239 100644 --- a/src/etc/inc/system.inc +++ b/src/etc/inc/system.inc @@ -883,6 +883,7 @@ function system_trust_configure($verbose = false) global $config; service_log('Writing trust files...', $verbose); + $trust = new \OPNsense\Trust\General(); /* * Write separate files because certcl will blacklist the whole file @@ -917,7 +918,7 @@ function system_trust_configure($verbose = false) } $ca = "# OPNsense trust authority: {$entry['descr']}\n"; $ca_count = 0; - $include_intermediates = !empty($config['system']['store_intermediate_certs']); + $include_intermediates = !empty((string)$trust->store_intermediate_certs); foreach ($user_cas as $user_ca) { if (!empty(trim($user_ca))) { $certinfo = @openssl_x509_parse($user_ca); @@ -956,12 +957,23 @@ function system_trust_configure($verbose = false) if ($ca_count) { $ca_file = sprintf($ca_files, $i . '.crt'); - file_put_contents(sprintf($ca_file, $i), $ca); - chmod(sprintf($ca_file, $i), 0644); + \OPNsense\Core\File::file_put_contents(sprintf($ca_file, $i), $ca, 0644); } } } + if (!empty((string)$trust->install_crls)) { + # deploy all collected CRL's into the trust store, they will be hashed into /etc/ssl/certs/ by certctl eventually + foreach (config_read_array('crl') as $i => $entry) { + if (!empty($entry) && !empty($entry['text'])) { + $crl_file = sprintf($ca_files, $i . '.crl'); + $payload = base64_decode($entry['text']) ?? ''; + \OPNsense\Core\File::file_put_contents(sprintf($crl_file, $i), $payload, 0644); + } + } + } + + service_log("done.\n", $verbose); /* collects all trusted certificates into /etc/ssl/certs directory */ diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Trust/Api/SettingsController.php b/src/opnsense/mvc/app/controllers/OPNsense/Trust/Api/SettingsController.php new file mode 100644 index 000000000..8f915c44f --- /dev/null +++ b/src/opnsense/mvc/app/controllers/OPNsense/Trust/Api/SettingsController.php @@ -0,0 +1,52 @@ +request->isPost()) { + (new Backend())->configdRun('system trust configure', true); + return ['status' => 'ok']; + } + return ['status' => 'failed']; + + } +} diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Trust/SettingsController.php b/src/opnsense/mvc/app/controllers/OPNsense/Trust/SettingsController.php new file mode 100644 index 000000000..f8cee23d6 --- /dev/null +++ b/src/opnsense/mvc/app/controllers/OPNsense/Trust/SettingsController.php @@ -0,0 +1,38 @@ +view->formSettings = $this->getForm("settings"); + $this->view->pick('OPNsense/Trust/settings'); + } +} diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Trust/forms/settings.xml b/src/opnsense/mvc/app/controllers/OPNsense/Trust/forms/settings.xml new file mode 100644 index 000000000..beb66730b --- /dev/null +++ b/src/opnsense/mvc/app/controllers/OPNsense/Trust/forms/settings.xml @@ -0,0 +1,74 @@ +
+ + header + + + + trust.store_intermediate_certs + + checkbox + Allow local defined intermediate certificate authorities to be used in the local trust store. + We advise to only store root certificates to prevent cross signed ones causing breakage when included but expired later in the chain. + + + + trust.install_crls + + checkbox + Store all configured CRL's in the default trust store. + + + header + + + + + trust.enable_legacy_sect + + checkbox + Enable Legacy Providers. + + + trust.enable_config_constraints + + checkbox + Enable custom constraints. + + + trust.CipherString + + select_multiple + + Sets the ciphersuite list for TLSv1.2 and below. + + + trust.Ciphersuites + + select_multiple + + Sets the available ciphersuites for TLSv1.3. + + + trust.groups + + select_multiple + + Limit the default set of built-in curves to be used when using the standard openssl configuration. + + + trust.MinProtocol + + dropdown + + Sets the minimum supported SSL or TLS version. + + + trust.MinProtocol_DTLS + + dropdown + + Sets the minimum supported DTLS version, when configuring MinProtocol and leaving this empty, + DTLS will be disabled. + + +
\ No newline at end of file diff --git a/src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml b/src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml index 2929d928c..9223ec5b3 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Core/Menu/Menu.xml @@ -77,6 +77,7 @@ + diff --git a/src/opnsense/mvc/app/models/OPNsense/Trust/General.php b/src/opnsense/mvc/app/models/OPNsense/Trust/General.php new file mode 100644 index 000000000..3f6766151 --- /dev/null +++ b/src/opnsense/mvc/app/models/OPNsense/Trust/General.php @@ -0,0 +1,35 @@ + + //OPNsense/trust/general + Trust general settings + 1.0.0 + + + 0 + Y + + + 0 + Y + + + 1 + Y + + + 0 + Y + + + + Y + system ssl ciphers-keyval pre-TLSv1.3 + Y + + + Y + system ssl ciphers-keyval TLSv1.3 + Y + + + Y + system tls groups + Y + + + + TLSv1 + TLSv1.1 + TLSv1.2 + TLSv1.3 + + + + + DTLSv1 + DTLSv1.1 + + + + diff --git a/src/opnsense/mvc/app/models/OPNsense/Trust/Migrations/M1_0_0.php b/src/opnsense/mvc/app/models/OPNsense/Trust/Migrations/M1_0_0.php new file mode 100644 index 000000000..f3be32186 --- /dev/null +++ b/src/opnsense/mvc/app/models/OPNsense/Trust/Migrations/M1_0_0.php @@ -0,0 +1,49 @@ +object(); + if (!isset($cnf->system) || !isset($cnf->system->store_intermediate_certs)) { + return; + } + $model->store_intermediate_certs = !empty((string)$cnf->system->store_intermediate_certs) ? '1' : '0'; + unset($cnf->system->store_intermediate_certs); + } +} diff --git a/src/opnsense/mvc/app/views/OPNsense/Trust/settings.volt b/src/opnsense/mvc/app/views/OPNsense/Trust/settings.volt new file mode 100644 index 000000000..a65c52bce --- /dev/null +++ b/src/opnsense/mvc/app/views/OPNsense/Trust/settings.volt @@ -0,0 +1,73 @@ +{# + +OPNsense® is Copyright © 2024 by Deciso B.V. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, +this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, +OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +#} + + + +
+ {{ partial("layout_partials/base_form",['fields':formSettings,'id':'frm_settings'])}} +
+ +
+
+
+
+ +

+
+
+
\ No newline at end of file diff --git a/src/opnsense/scripts/system/ssl_ciphers.py b/src/opnsense/scripts/system/ssl_ciphers.py index b33505eb6..c1f7e7a75 100755 --- a/src/opnsense/scripts/system/ssl_ciphers.py +++ b/src/opnsense/scripts/system/ssl_ciphers.py @@ -30,7 +30,6 @@ """ import subprocess import os -import sys import ujson import csv import argparse @@ -38,7 +37,9 @@ import argparse if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--format', help='format',choices=['full', 'key_value'], default='full') + parser.add_argument('--filter', help='filter version', choices=['TLSv1.3', 'pre-TLSv1.3', '']) inputargs = parser.parse_args() + # source https://www.iana.org/assignments/tls-parameters/tls-parameters-4.csv rfc5246_file = '%s/rfc5246_cipher_suites.csv' % os.path.dirname(os.path.realpath(__file__)) rfc5246 = dict() @@ -48,10 +49,17 @@ if __name__ == '__main__': rfc5246[row[0]] = {'description': row[1]} result = {} - sp = subprocess.run(['/usr/local/bin/openssl', 'ciphers', '-V'], capture_output=True, text=True) + # use opnsense.cnf template to avoid generic config constraints limiting options + ossl_env = os.environ.copy() + ossl_env['OPENSSL_CONF'] = '/usr/local/etc/ssl/opnsense.cnf' + sp = subprocess.run(['/usr/local/bin/openssl', 'ciphers', '-V'], capture_output=True, text=True, env=ossl_env) for line in sp.stdout.split("\n"): parts = line.strip().split() if len(parts) > 1: + if parts[3] == 'TLSv1.3' and inputargs.filter not in [None, '', 'TLSv1.3']: + continue + elif parts[3] != 'TLSv1.3' and inputargs.filter not in [None, '', 'pre-TLSv1.3']: + continue cipher_id = parts[0] cipher_key = parts[2] item = {'version': parts[3], 'id': cipher_id, 'description': ''} diff --git a/src/opnsense/scripts/system/tls_groups.py b/src/opnsense/scripts/system/tls_groups.py new file mode 100755 index 000000000..83d69adc0 --- /dev/null +++ b/src/opnsense/scripts/system/tls_groups.py @@ -0,0 +1,53 @@ +#!/usr/local/bin/python3 + +""" + Copyright (c) 2024 Ad Schellevis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + -------------------------------------------------------------------------------------- + return all available dhgroups / curves +""" +import os +import subprocess +import ujson + +if __name__ == '__main__': + result = {} + # dhgroup params can't be queried from openssl. + for item in ['ffdhe2048', 'ffdhe3072', 'ffdhe4096', 'ffdhe6144', 'ffdhe8192', 'X25519', 'X448']: + result[item] = item + # use opnsense.cnf template to avoid generic config constraints limiting options + ossl_env = os.environ.copy() + ossl_env['OPENSSL_CONF'] = '/usr/local/etc/ssl/opnsense.cnf' + sp = subprocess.run( + ['/usr/local/bin/openssl', 'ecparam', '-list_curves'], + capture_output=True, + text=True, + env=ossl_env + ) + for line in sp.stdout.split("\n"): + if line.startswith(' '): + tmp = line.strip().split(':')[0].strip() + result[tmp] = tmp + print (ujson.dumps(result)) diff --git a/src/opnsense/service/conf/actions.d/actions_system.conf b/src/opnsense/service/conf/actions.d/actions_system.conf index 989bb0520..b26046937 100644 --- a/src/opnsense/service/conf/actions.d/actions_system.conf +++ b/src/opnsense/service/conf/actions.d/actions_system.conf @@ -52,6 +52,19 @@ parameters: type:script_output message:List SSL ciphers +[ssl.ciphers-keyval] +command:/usr/local/opnsense/scripts/system/ssl_ciphers.py +parameters: --filter %s --format key_value +type:script_output +message:List SSL ciphers + + +[tls.groups] +command:/usr/local/opnsense/scripts/system/tls_groups.py +parameters: +type:script_output +message:List TLS curves + [remote.backup] command:/usr/local/opnsense/scripts/system/remote_backup.php parameters: %s diff --git a/src/opnsense/service/templates/OPNsense/Trust/openssl.cnf b/src/opnsense/service/templates/OPNsense/Trust/openssl.cnf index a362b706e..da372ee53 100644 --- a/src/opnsense/service/templates/OPNsense/Trust/openssl.cnf +++ b/src/opnsense/service/templates/OPNsense/Trust/openssl.cnf @@ -52,11 +52,35 @@ tsa_policy3 = 1.2.3.4.5.7 [openssl_init] providers = provider_sect +{% if not helpers.empty('OPNsense.trust.general.enable_config_constraints') %} +ssl_conf = ssl_sect + +[ssl_sect] +system_default = system_default_sect + +[system_default_sect] +{% if not helpers.empty('OPNsense.trust.general.CipherString') %} +CipherString = {{OPNsense.trust.general.CipherString|replace(',',':')}} +{% endif %} +{% if not helpers.empty('OPNsense.trust.general.Ciphersuites') %} +Ciphersuites = {{OPNsense.trust.general.Ciphersuites|replace(',',':')}} +{% endif %} +{% if not helpers.empty('OPNsense.trust.general.groups') %} +Groups = {{OPNsense.trust.general.groups|replace(',',':')}} +{% endif %} +{% endif %} # List of providers to load [provider_sect] default = default_sect +{% if not helpers.empty('OPNsense.trust.general.enable_legacy_sect') %} legacy = legacy_sect + +[legacy_sect] +activate = 1 + +{% endif %} + # The fips section name should match the section name inside the # included fipsmodule.cnf. # fips = fips_sect @@ -72,8 +96,6 @@ legacy = legacy_sect [default_sect] activate = 1 -[legacy_sect] -activate = 1 #################################################################### diff --git a/src/www/system_general.php b/src/www/system_general.php index 51787f201..c98135ab2 100644 --- a/src/www/system_general.php +++ b/src/www/system_general.php @@ -54,7 +54,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { $pconfig['hostname'] = $config['system']['hostname']; $pconfig['language'] = $config['system']['language']; $pconfig['prefer_ipv4'] = isset($config['system']['prefer_ipv4']); - $pconfig['store_intermediate_certs'] = isset($config['system']['store_intermediate_certs']); $pconfig['theme'] = $config['theme'] ?? ''; $pconfig['timezone'] = empty($config['system']['timezone']) ? 'Etc/UTC' : $config['system']['timezone']; @@ -158,9 +157,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { unset($config['system']['prefer_ipv4']); } - $sync_trust = !empty($pconfig['store_intermediate_certs']) !== isset($config['system']['store_intermediate_certs']); - $config['system']['store_intermediate_certs'] = !empty($pconfig['store_intermediate_certs']); - if (!empty($pconfig['dnsallowoverride'])) { $config['system']['dnsallowoverride'] = true; $config['system']['dnsallowoverride_exclude'] = implode(',', $pconfig['dnsallowoverride_exclude']); @@ -227,15 +223,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'GET') { /* time zone change first */ system_timezone_configure(); - - if ($sync_trust) { - /* - * FreeBSD trust store integration is slow so we need - * to avoid processing when setting is unchanged. - */ - system_trust_configure(); - } - system_hostname_configure(); system_resolver_configure(); plugins_configure('dns'); @@ -370,28 +357,6 @@ $( document ).ready(function() { -
- - - - - - - - - -
- /> - -
-
- -