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 @@
+
\ 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() {
-
-
-