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']))