hat.gateway.devices.iec104.ssl
1from pathlib import Path 2import asyncio 3import logging 4import typing 5 6from hat import aio 7from hat import json 8from hat.drivers import iec104 9from hat.drivers import ssl 10 11 12mlog = logging.getLogger(__name__) 13 14 15SslProtocol: typing.TypeAlias = ssl.SslProtocol 16 17 18def create_ssl_ctx(conf: json.Data, 19 protocol: ssl.SslProtocol 20 ) -> ssl.SSLContext: 21 ctx = ssl.create_ssl_ctx( 22 protocol=protocol, 23 verify_cert=conf['verify_cert'], 24 cert_path=(Path(conf['cert_path']) if conf['cert_path'] else None), 25 key_path=(Path(conf['key_path']) if conf['key_path'] else None), 26 ca_path=(Path(conf['ca_path']) if conf['ca_path'] else None)) 27 28 ctx.minimum_version = ssl.TLSVersion.TLSv1_2 29 ctx.set_ciphers('AES128-SHA256:' 30 'DH-RSA-AES128-SHA256:' 31 'DH-RSA-AES128-GCM-SHA256:' 32 'DHE-RSA-AES128-GCM-SHA256:' 33 'DH-RSA-AES128-GCM-SHA256:' 34 'ECDHE-RSA-AES128-GCM-SHA256:' 35 'ECDHE-RSA-AES256-GCM-SHA384:' 36 'TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:' 37 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384') 38 39 if conf.get('strict_mode'): 40 ctx.verify_flags = ssl.VERIFY_CRL_CHECK_LEAF 41 42 return ctx 43 44 45def init_security(conf: json.Data, 46 conn: iec104.Connection): 47 if conf.get('strict_mode'): 48 cert = ssl.get_peer_cert(conn.conn.ssl_object) 49 if not cert: 50 raise Exception('peer cert not available') 51 52 _check_cert(cert) 53 54 mlog.info('TLS session successfully established') 55 56 renegotiate_delay = conf.get('renegotiate_delay') 57 if renegotiate_delay: 58 conn.async_group.spawn(_renegotiate_loop, conn.conn.ssl_object, 59 renegotiate_delay) 60 61 if conf.get('strict_mode') and renegotiate_delay and conf['ca_path']: 62 conn.async_group.spawn(_verify_loop, conn.conn.ssl_object, 63 renegotiate_delay * 2, Path(conf['ca_path'])) 64 65 66def _check_cert(cert): 67 cert_bytes = cert.get_bytes() 68 if len(cert_bytes) > 8192: 69 mlog.warning('TLS certificate size exceeded') 70 71 key = cert.get_pub_pkey() 72 73 if key.is_rsa(): 74 key_size = key.get_size() 75 76 if key_size < 2048: 77 raise Exception('insufficient RSA key length') 78 79 if key_size > 8192: 80 mlog.warning('RSA key length greater than 8192') 81 82 83async def _renegotiate_loop(ssl_object, renegotiate_delay): 84 executor = aio.Executor() 85 86 try: 87 while True: 88 await asyncio.sleep(renegotiate_delay) 89 90 try: 91 await executor.spawn(_ext_renegotiate, ssl_object) 92 93 except Exception as e: 94 mlog.error('renegotiate error: %s', e, exc_info=e) 95 96 except Exception as e: 97 mlog.error('renegotiate loop error: %s', e, exc_info=e) 98 99 finally: 100 mlog.debug('closing renegotiate loop') 101 await aio.uncancellable(executor.async_close()) 102 103 104async def _verify_loop(ssl_object, verify_delay, ca_path): 105 executor = aio.Executor() 106 107 try: 108 while True: 109 await asyncio.sleep(verify_delay) 110 111 try: 112 await executor.spawn(_ext_verify, ssl_object, ca_path) 113 114 except Exception as e: 115 mlog.error('verify error: %s', e, exc_info=e) 116 117 except Exception as e: 118 mlog.error('verify loop error: %s', e, exc_info=e) 119 120 finally: 121 mlog.debug('closing verify loop') 122 await aio.uncancellable(executor.async_close()) 123 124 125def _ext_renegotiate(ssl_object): 126 if ssl_object.version() == 'TLSv1.3': 127 ssl.key_update(ssl_object, ssl.KeyUpdateType.UPDATE_REQUESTED) 128 129 else: 130 ssl.renegotiate(ssl_object) 131 132 ssl_object.do_handshake() 133 134 135def _ext_verify(ssl_object, ca_path): 136 cert = ssl.get_peer_cert(ssl_object) 137 if not cert: 138 raise Exception('peer cert not available') 139 140 crl = ssl.load_crl(ca_path) 141 142 serial_number = cert.get_serial_number() 143 if crl.contains_cert(serial_number): 144 mlog.warning('current certificate in CRL')
mlog =
<Logger hat.gateway.devices.iec104.ssl (WARNING)>
class
SslProtocol(enum.Enum):
16class SslProtocol(enum.Enum): 17 TLS_CLIENT = ssl.PROTOCOL_TLS_CLIENT 18 TLS_SERVER = ssl.PROTOCOL_TLS_SERVER
An enumeration.
TLS_CLIENT =
<SslProtocol.TLS_CLIENT: <_SSLMethod.PROTOCOL_TLS_CLIENT: 16>>
TLS_SERVER =
<SslProtocol.TLS_SERVER: <_SSLMethod.PROTOCOL_TLS_SERVER: 17>>
def
create_ssl_ctx( conf: None | bool | int | float | str | list[ForwardRef('Data')] | dict[str, ForwardRef('Data')], protocol: hat.drivers.ssl.SslProtocol) -> ssl.SSLContext:
19def create_ssl_ctx(conf: json.Data, 20 protocol: ssl.SslProtocol 21 ) -> ssl.SSLContext: 22 ctx = ssl.create_ssl_ctx( 23 protocol=protocol, 24 verify_cert=conf['verify_cert'], 25 cert_path=(Path(conf['cert_path']) if conf['cert_path'] else None), 26 key_path=(Path(conf['key_path']) if conf['key_path'] else None), 27 ca_path=(Path(conf['ca_path']) if conf['ca_path'] else None)) 28 29 ctx.minimum_version = ssl.TLSVersion.TLSv1_2 30 ctx.set_ciphers('AES128-SHA256:' 31 'DH-RSA-AES128-SHA256:' 32 'DH-RSA-AES128-GCM-SHA256:' 33 'DHE-RSA-AES128-GCM-SHA256:' 34 'DH-RSA-AES128-GCM-SHA256:' 35 'ECDHE-RSA-AES128-GCM-SHA256:' 36 'ECDHE-RSA-AES256-GCM-SHA384:' 37 'TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:' 38 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384') 39 40 if conf.get('strict_mode'): 41 ctx.verify_flags = ssl.VERIFY_CRL_CHECK_LEAF 42 43 return ctx
def
init_security( conf: None | bool | int | float | str | list[ForwardRef('Data')] | dict[str, ForwardRef('Data')], conn: hat.drivers.iec104.common.Connection):
46def init_security(conf: json.Data, 47 conn: iec104.Connection): 48 if conf.get('strict_mode'): 49 cert = ssl.get_peer_cert(conn.conn.ssl_object) 50 if not cert: 51 raise Exception('peer cert not available') 52 53 _check_cert(cert) 54 55 mlog.info('TLS session successfully established') 56 57 renegotiate_delay = conf.get('renegotiate_delay') 58 if renegotiate_delay: 59 conn.async_group.spawn(_renegotiate_loop, conn.conn.ssl_object, 60 renegotiate_delay) 61 62 if conf.get('strict_mode') and renegotiate_delay and conf['ca_path']: 63 conn.async_group.spawn(_verify_loop, conn.conn.ssl_object, 64 renegotiate_delay * 2, Path(conf['ca_path']))