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