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