From b709232e44aede9be1a60c3a9da34ba4f9e88c6f Mon Sep 17 00:00:00 2001 From: Ad Schellevis Date: Tue, 24 Sep 2024 20:58:04 +0200 Subject: [PATCH] System: Trust: Authorities - finish crl fetch script for installed certificates and hook into form and cron updates. According to rfc5280 an CRL update should be issued before "next update", but does not seem to define a validity. Various sources seem to indicate updates should be fetched at least every couple of hours, in which case an hourly update sounds reasonable. To avoid excessive writes, we compare each CRL offered with the last one received before flushing it to disk and keep track of actual changes. When nothing changes, no rehash is neede, which the configd action takes care of. Distribution points could either be HTTP or LDAP, LDAPS and HTTPS may not be used according to the RFC (CAs SHOULD NOT include URIs that specify https, ldaps, or similar schemes in extensions.) --- src/etc/inc/plugins.inc.d/core.inc | 4 ++ .../OPNsense/Trust/Api/SettingsController.php | 2 + .../OPNsense/Trust/forms/settings.xml | 7 ++- .../mvc/app/models/OPNsense/Trust/General.xml | 6 ++- src/opnsense/scripts/system/crl_fetch.py | 47 ++++++++++++++----- .../conf/actions.d/actions_system.conf | 6 +++ 6 files changed, 59 insertions(+), 13 deletions(-) diff --git a/src/etc/inc/plugins.inc.d/core.inc b/src/etc/inc/plugins.inc.d/core.inc index b9c435c04..37758699f 100644 --- a/src/etc/inc/plugins.inc.d/core.inc +++ b/src/etc/inc/plugins.inc.d/core.inc @@ -344,6 +344,10 @@ function core_cron() } } + if (!empty((string)(new OPNsense\Trust\General())->fetch_crls)) { + $jobs[]['autocron'] = array('/usr/local/sbin/configctl -d system trust download_crls', '1'); + } + return $jobs; } diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Trust/Api/SettingsController.php b/src/opnsense/mvc/app/controllers/OPNsense/Trust/Api/SettingsController.php index ab472269c..59b0c7e97 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Trust/Api/SettingsController.php +++ b/src/opnsense/mvc/app/controllers/OPNsense/Trust/Api/SettingsController.php @@ -44,6 +44,8 @@ class SettingsController extends ApiMutableModelControllerBase { if ($this->request->isPost()) { (new Backend())->configdRun('system trust configure', true); + /* CRL fetches are scheduled */ + (new Backend())->configdRun('cron restart', true); return ['status' => 'ok']; } return ['status' => 'failed']; diff --git a/src/opnsense/mvc/app/controllers/OPNsense/Trust/forms/settings.xml b/src/opnsense/mvc/app/controllers/OPNsense/Trust/forms/settings.xml index 319e76283..c09a1281b 100644 --- a/src/opnsense/mvc/app/controllers/OPNsense/Trust/forms/settings.xml +++ b/src/opnsense/mvc/app/controllers/OPNsense/Trust/forms/settings.xml @@ -17,11 +17,16 @@ checkbox Store all configured CRL's in the default trust store. + + trust.fetch_crls + + checkbox + Schedule an hourly job to download CRLs using the defined Distributionpoints in the CAs deployed in our trust store. + header - trust.enable_legacy_sect diff --git a/src/opnsense/mvc/app/models/OPNsense/Trust/General.xml b/src/opnsense/mvc/app/models/OPNsense/Trust/General.xml index cde6711af..b8c7f44a7 100644 --- a/src/opnsense/mvc/app/models/OPNsense/Trust/General.xml +++ b/src/opnsense/mvc/app/models/OPNsense/Trust/General.xml @@ -1,7 +1,7 @@ //OPNsense/trust/general Trust general settings - 1.0.0 + 1.0.1 0 @@ -11,6 +11,10 @@ 0 Y + + 0 + Y + 1 Y diff --git a/src/opnsense/scripts/system/crl_fetch.py b/src/opnsense/scripts/system/crl_fetch.py index 23cf3f9ed..6f9a38f33 100755 --- a/src/opnsense/scripts/system/crl_fetch.py +++ b/src/opnsense/scripts/system/crl_fetch.py @@ -23,9 +23,14 @@ 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. + -------------------------------------------------------------------------------------------------------------- + Simple CRL Distributionpoint downloader using the CA's configured in the central trust store + Script returns exit status 0 when nothing has changed, 1 when changes have been made so a rehash can be scheduled """ import glob +import hashlib import urllib +import os import subprocess import ldap3 import requests @@ -39,7 +44,7 @@ TRUSTPATH = ['/usr/share/certs/trusted', '/usr/local/share/certs', '/usr/local/e def fetch_crl(uri): p = urllib.parse.urlparse(uri) payload = None - if p.scheme and p.scheme.lower() == 'ldap': + if p.scheme.lower() == 'ldap': server = ldap3.Server(p.netloc) conn = ldap3.Connection(server, auto_bind=True) conn.search( @@ -53,7 +58,7 @@ def fetch_crl(uri): if value and key.split(';')[0].lower() == 'certificaterevocationlist': payload = value[0] break - elif p.scheme and p.scheme.lower() == 'http': + elif p.scheme.lower() == 'http': r = requests.get(uri) if r.status_code >= 200 and r.status_code < 300: payload = r.content @@ -76,7 +81,12 @@ def fetch_crl(uri): 'pem': crl.public_bytes(serialization.Encoding.PEM).decode() } + def main(): + changes = 0 + output_pattern = '/usr/local/share/certs/ca-crl-upd-opn-%s.crl' + crl_files = [] + dp_uri = '' for path in TRUSTPATH: for filename in glob.glob('%s/*[.pem|.crt]' % path): try: @@ -84,7 +94,10 @@ def main(): for ext in cert.extensions: if type(ext.value) is CRLDistributionPoints: for Distributionpoint in ext.value: - this_crl = fetch_crl(Distributionpoint.full_name[0].value) + dp_uri = Distributionpoint.full_name[0].value + target_filename = output_pattern % hashlib.sha256(dp_uri.encode()).hexdigest() + this_crl = fetch_crl(dp_uri) + crl_files.append(target_filename) # use local trust store to validate if the received CRL is valid sp = subprocess.run( ['/usr/local/bin/openssl', 'crl', '-verify'], @@ -93,16 +106,28 @@ def main(): text=True ) if sp.stderr.strip() == 'verify OK': - print('ok') - print(filename) - - print(this_crl) - sys.exit(0) - #print(Distributionpoint.full_name[0].value) + if os.path.isfile(target_filename): + if open(target_filename).read() == this_crl['pem']: + print('[-] skip unchanged crl from %s' % dp_uri) + continue + with open(target_filename, 'w') as f_out: + print('[+] store crl from %s' % dp_uri) + f_out.write(this_crl['pem']) + changes += 1 + else: + print('[-] skip crl from %s (%s)' % (dp_uri, sp.stderr.strip())) except Exception as e: # error handling - print(e) + print('[-] error processing %s [%s]' % (dp_uri, e)) + + # cleanup unused CRLs within our responsible scope + for filename in glob.glob(output_pattern % '*'): + if filename not in crl_files: + os.unlink(filename) + changes += 1 + + return changes if __name__ == '__main__': - main() + sys.exit(0 if main() == 0 else 1) diff --git a/src/opnsense/service/conf/actions.d/actions_system.conf b/src/opnsense/service/conf/actions.d/actions_system.conf index f181e3478..28332ffa8 100644 --- a/src/opnsense/service/conf/actions.d/actions_system.conf +++ b/src/opnsense/service/conf/actions.d/actions_system.conf @@ -146,6 +146,12 @@ parameters: type:script message:configure trust +[trust.download_crls] +command:/usr/local/opnsense/scripts/system/crl_fetch.py || /usr/local/opnsense/scripts/system/certctl.py rehash +parameters: +type:script +message:download CRLs from Distributionpoints + [trust.crl] command:/usr/local/sbin/pluginctl -c crl type:script