mirror of
https://github.com/lucaspalomodevelop/core.git
synced 2026-03-13 08:09:41 +00:00
firmware: add wrapper class for python to support using platform TLS settings (as far as possible).
Unfortunately python requests/urllib3/ssl ignores platform openssl defaults, but do choose defaults which do not always match expectations. Below per configuration item (system_default_sect) the current situation: * CipherString --> supported, using ciphers list * Ciphersuites --> not supported, but does seem to follow configuration defaults. When python adds support, we likely have to change something. * SignatureAlgorithms --> not supported, but seem as above * Groups (Curves) --> partly supported, only one may be offered, we select the first item in the list, knowing that is a bit flaky (see: set_ecdh_curve()) * MinProtocol --> supported
This commit is contained in:
parent
2616ace119
commit
7561f69e47
1
plist
1
plist
@ -1417,6 +1417,7 @@
|
||||
/usr/local/opnsense/site-python/log_helper.py
|
||||
/usr/local/opnsense/site-python/params.py
|
||||
/usr/local/opnsense/site-python/sqlite3_helper.py
|
||||
/usr/local/opnsense/site-python/tls_helper.py
|
||||
/usr/local/opnsense/site-python/watchers/__init__.py
|
||||
/usr/local/opnsense/site-python/watchers/dhcpd.py
|
||||
/usr/local/opnsense/version/core
|
||||
|
||||
@ -31,11 +31,13 @@ import ipaddress
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import requests
|
||||
sys.path.insert(0, "/usr/local/opnsense/site-python")
|
||||
import tls_helper
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.x509.extensions import CRLDistributionPoints
|
||||
|
||||
|
||||
def fetch_certs(domains):
|
||||
result = []
|
||||
for domain in domains:
|
||||
@ -50,7 +52,7 @@ def fetch_certs(domains):
|
||||
url = 'https://%s' % domain
|
||||
try:
|
||||
print('# [i] fetch certificate for %s' % url)
|
||||
with requests.get(url, timeout=30, stream=True) as response:
|
||||
with tls_helper.RequestsWrapper().get(url, timeout=30, stream=True) as response:
|
||||
# XXX: in python > 3.13, replace with sock.get_verified_chain()
|
||||
for cert in response.raw.connection.sock._sslobj.get_verified_chain():
|
||||
result.append({'domain': domain, 'depth': depth, 'pem': cert.public_bytes(1).encode()}) # _ssl.ENCODING_PEM
|
||||
@ -61,6 +63,7 @@ def fetch_certs(domains):
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def main(domains, target, lifetime):
|
||||
crl_index = target + 'index'
|
||||
crl_bundle = []
|
||||
@ -83,7 +86,7 @@ def main(domains, target, lifetime):
|
||||
dp_uri = Distributionpoint.full_name[0].value
|
||||
print("# [i] fetch CRL from %s" % dp_uri)
|
||||
# XXX: only support http for now
|
||||
response = requests.get(dp_uri)
|
||||
response = tls_helper.RequestsWrapper().get(dp_uri)
|
||||
if 200 <= response.status_code <= 299:
|
||||
crl = x509.load_der_x509_crl(response.content)
|
||||
crl_bundle.append({"domain": fetched['domain'], "depth": fetched['depth'], "name": str(cert.subject), "data": crl.public_bytes(serialization.Encoding.PEM).decode().strip()})
|
||||
|
||||
111
src/opnsense/site-python/tls_helper.py
Normal file
111
src/opnsense/site-python/tls_helper.py
Normal file
@ -0,0 +1,111 @@
|
||||
"""
|
||||
Copyright (c) 2024 Ad Schellevis <ad@opnsense.org>
|
||||
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.
|
||||
"""
|
||||
|
||||
import os
|
||||
import requests
|
||||
import ssl
|
||||
from requests.adapters import HTTPAdapter
|
||||
from requests.packages.urllib3.util.ssl_ import create_urllib3_context
|
||||
from collections import OrderedDict
|
||||
from configparser import ConfigParser
|
||||
|
||||
|
||||
class IniDict(OrderedDict):
|
||||
def __setitem__(self, key, value):
|
||||
if isinstance(value, list) and key in self:
|
||||
self[key].extend(value)
|
||||
else:
|
||||
super().__setitem__(key, value)
|
||||
|
||||
|
||||
class PlatformTLSAdaptor(HTTPAdapter):
|
||||
ssl_context = None
|
||||
@classmethod
|
||||
def get_sslcontext(cls):
|
||||
if cls.ssl_context is None:
|
||||
openssl_conf = {
|
||||
'cipherstring' : None,
|
||||
'minprotocol': None,
|
||||
# XXX: Curves (very) limited supported
|
||||
'groups' : None,
|
||||
# XXX: not supported, but openssl.cfg settings do seem to apply here.
|
||||
'signaturealgorithms' : None,
|
||||
# XXX: TLS1.3 modifications not supported, openssl.cnf defaults should apply
|
||||
# https://docs.python.org/3/library/ssl.html#ssl.SSLContext.set_ciphers
|
||||
'ciphersuites' : None,
|
||||
}
|
||||
conf_file = '/usr/local/openssl/openssl.cnf'
|
||||
conf_section = 'system_default_sect'
|
||||
if os.path.isfile(conf_file):
|
||||
cnf = ConfigParser(strict=False, dict_type=IniDict)
|
||||
data = "[openssl]\n%s" % open(conf_file, 'r').read()
|
||||
cnf.read_string(data)
|
||||
if cnf.has_section(conf_section):
|
||||
for option in cnf.options(conf_section):
|
||||
if option in openssl_conf:
|
||||
openssl_conf[option] = cnf.get(conf_section, option)
|
||||
|
||||
ctx_args = {}
|
||||
if openssl_conf['cipherstring']:
|
||||
ctx_args['ciphers'] = openssl_conf['cipherstring']
|
||||
|
||||
cls.ssl_context = create_urllib3_context(**ctx_args)
|
||||
cls.ssl_context.load_verify_locations('/etc/ssl/cert.pem')
|
||||
if openssl_conf['minprotocol']:
|
||||
for item in openssl_conf['minprotocol'].split("\n"):
|
||||
if item == 'TLSv1':
|
||||
cls.ssl_context.minimum_version = ssl.TLSVersion.TLSv1
|
||||
elif item == 'TLSv1.1':
|
||||
cls.ssl_context.minimum_version = ssl.TLSVersion.TLSv1_1
|
||||
elif item == 'TLSv1.2':
|
||||
cls.ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
|
||||
elif item == 'TLSv1.3':
|
||||
cls.ssl_context.minimum_version = ssl.TLSVersion.TLSv1_3
|
||||
if openssl_conf['groups']:
|
||||
# XXX: We can only select a single curve here instead of a group
|
||||
# although this is very flaky, we should choose one to prevent accepting ffdhe2048
|
||||
# which is enabled by default in openssl.
|
||||
cls.ssl_context.set_ecdh_curve(openssl_conf['groups'].split(':')[0])
|
||||
|
||||
return cls.ssl_context
|
||||
|
||||
def init_poolmanager(self, *args, **kwargs):
|
||||
kwargs['ssl_context'] = self.get_sslcontext()
|
||||
return super().init_poolmanager(*args, **kwargs)
|
||||
def proxy_manager_for(self, *args, **kwargs):
|
||||
kwargs['ssl_context'] = self.get_sslcontext()
|
||||
return super().proxy_manager_for(*args, **kwargs)
|
||||
|
||||
class RequestsWrapper:
|
||||
def get(self, *args, **kwargs):
|
||||
s = requests.Session()
|
||||
s.mount('https://', PlatformTLSAdaptor())
|
||||
return s.get(*args, **kwargs)
|
||||
|
||||
def post(self, *args, **kwargs):
|
||||
s = requests.Session()
|
||||
s.mount('https://', PlatformTLSAdaptor())
|
||||
return s.post(*args, **kwargs)
|
||||
Loading…
x
Reference in New Issue
Block a user