JFIF$        dd7 

Viewing File: /usr/share/crypto-policies/python/policygenerators/openssl.py

# SPDX-License-Identifier: LGPL-2.1-or-later

# Copyright (c) 2019 Red Hat, Inc.
# Copyright (c) 2019 Tomáš Mráz <tmraz@fedoraproject.org>

from subprocess import CalledProcessError, check_output

from .configgenerator import ConfigGenerator

RH_ALLOW_SHA1 = '''
[openssl_init]
alg_section = evp_properties

[evp_properties]
rh-allow-sha1-signatures = yes
'''

FIPS_MODULE_CONFIG = '''
[fips_sect]
tls1-prf-ems-check = {}
activate = 1
'''


class OpenSSLGenerator(ConfigGenerator):
    CONFIG_NAME = 'openssl'
    SCOPES = {'tls', 'ssl', 'openssl'}

    cipher_not_map = {
        'AES-256-CTR': '',
        'AES-128-CTR': '',
        'AES-256-GCM': '-AES256',
        'AES-128-GCM': '-AES128',
        'AES-256-CBC': '-SHA256',
        'AES-128-CBC': '',
        'CHACHA20-POLY1305': '-CHACHA20',
        'SEED-CBC': '-SEED',
        'IDEA-CBC': '!IDEA',
        'DES-CBC': '!DES',
        'RC4-40': '',
        'DES40-CBC': '',
        '3DES-CBC': '-3DES',
        'RC4-128': '!RC4',
        'RC2-CBC': '!RC2',
        'NULL': '!eNULL:!aNULL'
    }
    cipher_notany_multimap = {
        # CBC is documented as SSL_DES, SSL_3DES, SSL_RC2, SSL_IDEA,
        #                      SSL_AES128, SSL_AES256,
        #                      SSL_CAMELLIA128, SSL_CAMELLIA256, SSL_SEED
        '-CBC': {'DES-CBC', '3DES-CBC', 'RC2-CBC', 'IDEA-CBC',
                 'AES-128-CBC', 'AES-256-CBC',
                 'CAMELLIA-128-CBC', 'CAMELLIA-256-CBC', 'SEED-CBC'},
        '-AESCCM': {'AES-128-CCM', 'AES-256-CCM'},
        '-AESGCM': {'AES-128-GCM', 'AES-256-GCM'},
    }

    key_exchange_map = {
        'RSA': 'kRSA',
        'ECDHE': 'kEECDH',
        'PSK': 'kPSK',
        'DHE-PSK': 'kDHEPSK',
        'DHE-RSA': 'kEDH',
        'DHE-DSS': '',
        'ECDHE-PSK': 'kECDHEPSK',
        'RSA-PSK': 'kRSAPSK',
        'VKO-GOST-2012': 'kGOST'
    }

    key_exchange_not_map = {
        'ANON': '',
        'DH': '',
        'ECDH': '',
        'RSA': '-kRSA',
        'ECDHE': '-kEECDH',
        'DHE-RSA': '-aRSA',
        'DHE-DSS': '-aDSS',
        'PSK': '-kPSK',
        'DHE-PSK': '-kDHEPSK',
        'ECDHE-PSK': '-kECDHEPSK',
        'RSA-PSK': '-kRSAPSK'
    }

    mac_not_map = {
        'HMAC-MD5': '!MD5',
        'HMAC-SHA1': '-SHA1'
    }

    ciphersuite_map = {
        'TLS_AES_256_GCM_SHA384': {
            'cipher': {'AES-256-GCM'},
            'hash': {'SHA2-384'},
            'protocol': {'TLS1.3', 'DTLS1.3'},
        },
        'TLS_AES_128_GCM_SHA256': {
            'cipher': {'AES-128-GCM'},
            'hash': {'SHA2-256'},
            'protocol': {'TLS1.3', 'DTLS1.3'},
        },
        'TLS_CHACHA20_POLY1305_SHA256': {
            'cipher': {'CHACHA20-POLY1305'},
            'hash': {'SHA2-256'},
            'protocol': {'TLS1.3', 'DTLS1.3'},
        },
        'TLS_AES_128_CCM_SHA256': {
            'cipher': {'AES-128-CCM'},
            'hash': {'SHA2-256'},
            'protocol': {'TLS1.3', 'DTLS1.3'},
        },
        # This one is not enableable since c-p does not expose CCM8 ciphers:
        # 'TLS_AES_128_CCM_8_SHA256': {
        #     'cipher': {'AES-128-CCM8'},  # this is not a thing in c-p
        #     'hash': {'SHA2-256'},
        #     'protocol': {'TLS1.3', 'DTLS1.3'},
        # },
        'TLS_SHA256_SHA256': {
            'cipher': {'NULL'},
            'hash': {'SHA2-256'},
            'mac': {'HMAC-SHA2-256'},
            'protocol': {'TLS1.3', 'DTLS1.3'},
        },
        'TLS_SHA384_SHA384': {
            'cipher': {'NULL'},
            'hash': {'SHA2-384'},
            'mac': {'HMAC-SHA2-384'},
            'protocol': {'TLS1.3', 'DTLS1.3'},
        },
    }

    @classmethod
    def generate_ciphers(cls, policy):
        s = ''
        p = policy.enabled
        ip = policy.disabled
        # We cannot separate RSA strength from DH params.
        min_dh_size = policy.integers['min_dh_size']
        min_rsa_size = policy.integers['min_rsa_size']
        if min_dh_size < 1023 or min_rsa_size < 1023:
            s = cls.append(s, '@SECLEVEL=0')
        elif min_dh_size < 2048 or min_rsa_size < 2048:
            s = cls.append(s, '@SECLEVEL=1')
        elif min_dh_size < 3072 or min_rsa_size < 3072:
            s = cls.append(s, '@SECLEVEL=2')
        else:
            s = cls.append(s, '@SECLEVEL=3')

        for i in p['key_exchange']:
            try:
                s = cls.append(s, cls.key_exchange_map[i])
            except KeyError:
                pass

        for i in ip['key_exchange']:
            try:
                s = cls.append(s, cls.key_exchange_not_map[i])
            except KeyError:
                pass

        for i in ip['cipher']:
            try:
                s = cls.append(s, cls.cipher_not_map[i])
            except KeyError:
                pass
        for keyword, cipherset in cls.cipher_notany_multimap.items():
            if all(c in ip['cipher'] for c in cipherset):
                s = cls.append(s, keyword)

        for i in ip['mac']:
            try:
                s = cls.append(s, cls.mac_not_map[i])
            except KeyError:
                pass

        # These ciphers are not necessary for any
        # policy level, and only increase the attack surface.
        # FIXME! must be fixed for custom policies
        for c in ('-SHA384', '-CAMELLIA', '-ARIA', '-AESCCM8'):
            s = cls.append(s, c)

        return s

    @classmethod
    def generate_ciphersuites(cls, policy):
        s = ''
        p = policy.enabled
        # we need the output sorted by order of `cipher`
        # finer sorting nuances are currently ignored
        for c in p['cipher']:
            cipher_submap = {name: spec for name, spec
                             in cls.ciphersuite_map.items()
                             if spec['cipher'] == {c}}
            for ciphersuite_name, ciphersuite_spec in cipher_submap.items():
                if all(any(val in algvalues for val in p[algclass])
                       for algclass, algvalues in ciphersuite_spec.items()):
                    s = cls.append(s, ciphersuite_name)
        return s

    @classmethod
    def generate_config(cls, policy):
        return cls.generate_ciphers(policy) + '\n'

    @classmethod
    def test_config(cls, config):
        output = b''
        assert config.endswith('\n')  # noqa: S101
        try:
            output = check_output(['openssl',  # noqa: S607
                                   'ciphers', config[:-1]])
        except CalledProcessError:
            cls.eprint('There is an error in openssl generated policy')
            cls.eprint(f'Policy:\n{config}')
            return False
        except OSError:
            # Ignore missing openssl
            return True
        if b'NULL' in output or b'ADH' in output:
            cls.eprint('There is NULL or ADH in openssl generated policy')
            cls.eprint(f'Policy:\n{config}')
            return False
        return True


class OpenSSLConfigGenerator(OpenSSLGenerator):
    CONFIG_NAME = 'opensslcnf'

    # has to cover everything c-p has
    protocol_map = {
        'SSL3.0': 'SSLv3',
        'TLS1.0': 'TLSv1',
        'TLS1.1': 'TLSv1.1',
        'TLS1.2': 'TLSv1.2',
        'TLS1.3': 'TLSv1.3',
        'DTLS0.9': 'DTLSv0.9',
        'DTLS1.0': 'DTLSv1',
        'DTLS1.2': 'DTLSv1.2'
    }

    sign_map = {
        'RSA-SHA1': 'RSA+SHA1',
        'DSA-SHA1': 'DSA+SHA1',
        'ECDSA-SHA1': 'ECDSA+SHA1',
        'RSA-SHA2-224': 'RSA+SHA224',
        'DSA-SHA2-224': 'DSA+SHA224',
        'ECDSA-SHA2-224': 'ECDSA+SHA224',
        'RSA-SHA2-256': 'RSA+SHA256',
        'DSA-SHA2-256': 'DSA+SHA256',
        'ECDSA-SHA2-256': 'ECDSA+SHA256',
        'RSA-SHA2-384': 'RSA+SHA384',
        'DSA-SHA2-384': 'DSA+SHA384',
        'ECDSA-SHA2-384': 'ECDSA+SHA384',
        'RSA-SHA2-512': 'RSA+SHA512',
        'DSA-SHA2-512': 'DSA+SHA512',
        'ECDSA-SHA2-512': 'ECDSA+SHA512',
        'RSA-PSS-SHA2-256': 'rsa_pss_pss_sha256',
        'RSA-PSS-SHA2-384': 'rsa_pss_pss_sha384',
        'RSA-PSS-SHA2-512': 'rsa_pss_pss_sha512',
        'RSA-PSS-RSAE-SHA2-256': 'rsa_pss_rsae_sha256',
        'RSA-PSS-RSAE-SHA2-384': 'rsa_pss_rsae_sha384',
        'RSA-PSS-RSAE-SHA2-512': 'rsa_pss_rsae_sha512',
        'EDDSA-ED25519': 'ed25519',
        'EDDSA-ED448': 'ed448',
        'MLDSA44': '?mldsa44',
        'MLDSA65': '?mldsa65',
        'MLDSA87': '?mldsa87',
    }

    group_pq_map = {
        'MLKEM768-X25519': '?X25519MLKEM768:?x25519_mlkem768',  # new/old name
        'P256-MLKEM768': '?SecP256r1MLKEM768:?p256_mlkem768',  # new/old name
        'P384-MLKEM1024': '?SecP384r1MLKEM1024:?p384_mlkem1024',  # new/old
    }

    group_classic_map = {
        'SECP224R1': 'secp224r1',
        'SECP256R1': 'secp256r1',
        'SECP384R1': 'secp384r1',
        'SECP521R1': 'secp521r1',
        'X25519': 'X25519',
        'X448': 'X448',
        'FFDHE-2048': 'ffdhe2048',
        'FFDHE-3072': 'ffdhe3072',
        'FFDHE-4096': 'ffdhe4096',
        'FFDHE-6144': 'ffdhe6144',
        'FFDHE-8192': 'ffdhe8192',
        'BRAINPOOL-P256R1': 'brainpoolP256r1',
        'BRAINPOOL-P384R1': 'brainpoolP384r1',
        'BRAINPOOL-P512R1': 'brainpoolP512r1',
    }

    @classmethod
    def generate_config(cls, policy):
        p = policy.enabled
        # This includes the seclevel
        s = f'CipherString = {cls.generate_ciphers(policy)}\n'
        s += f'Ciphersuites = {cls.generate_ciphersuites(policy)}\n'

        if policy.min_tls_version:
            s += 'TLS.MinProtocol ='
            s += f' {cls.protocol_map[policy.min_tls_version]}\n'
        if policy.max_tls_version:
            s += 'TLS.MaxProtocol ='
            s += f' {cls.protocol_map[policy.max_tls_version]}\n'
        if policy.min_dtls_version:
            s += 'DTLS.MinProtocol ='
            s += f' {cls.protocol_map[policy.min_dtls_version]}\n'
        if policy.max_dtls_version:
            s += 'DTLS.MaxProtocol ='
            s += f' {cls.protocol_map[policy.max_dtls_version]}\n'

        sig_algs = [cls.sign_map[i] for i in p['sign'] if i in cls.sign_map]
        s += 'SignatureAlgorithms = ' + ':'.join(sig_algs) + '\n'

        # Separate groups into PQ and classic groups, generate them as follows:
        # `*first_pq:rest_pq/*first_classic:rest_classic`.
        # This way servers will prefer any PQ over any classic,
        # and clients will send key_shares for top priority PQ
        # and top priority classic groups
        groups_pq = [cls.group_pq_map[i] for i in p['group']
                     if i in cls.group_pq_map]
        groups_classic = [cls.group_classic_map[i] for i in p['group']
                          if i in cls.group_classic_map]
        group_classes = (
            (['*' + ':'.join(groups_pq)] if groups_pq else [])
            + (['*' + ':'.join(groups_classic)] if groups_classic else [])
        )
        s += 'Groups = ' + '/'.join(group_classes) + '\n'

        if policy.enums['__ems'] == 'RELAX':
            s += 'Options = RHNoEnforceEMSinFIPS\n'

        if 'SHA1' in p['hash']:
            s += RH_ALLOW_SHA1

        return s

    @classmethod
    def test_config(cls, config):  # pylint: disable=unused-argument
        return True


class OpenSSLFIPSGenerator(ConfigGenerator):
    CONFIG_NAME = 'openssl_fips'
    SCOPES = {'tls', 'ssl', 'openssl'}

    @classmethod
    def generate_config(cls, policy):
        # OpenSSL EMS relaxation is special
        # in that it uses a separate FIPS module config
        # and, just in case, EMS is enforcing by default.
        # It only puts `= 0` there if it's explicitly relaxed.
        # That's the reason why `__ems` is a tri-state enum.
        return FIPS_MODULE_CONFIG.format(int(policy.enums['__ems'] != 'RELAX'))

    @classmethod
    def test_config(cls, config):  # pylint: disable=unused-argument
        return True
Back to Directory  nL+D550H?Mx ,D"v]qv;6*Zqn)ZP0!1 A "#a$2Qr D8 a Ri[f\mIykIw0cuFcRı?lO7к_f˓[C$殷WF<_W ԣsKcëIzyQy/_LKℂ;C",pFA:/]=H  ~,ls/9ć:[=/#f;)x{ٛEQ )~ =𘙲r*2~ a _V=' kumFD}KYYC)({ *g&f`툪ry`=^cJ.I](*`wq1dđ#̩͑0;H]u搂@:~וKL Nsh}OIR*8:2 !lDJVo(3=M(zȰ+i*NAr6KnSl)!JJӁ* %݉?|D}d5:eP0R;{$X'xF@.ÊB {,WJuQɲRI;9QE琯62fT.DUJ;*cP A\ILNj!J۱+O\͔]ޒS߼Jȧc%ANolՎprULZԛerE2=XDXgVQeӓk yP7U*omQIs,K`)6\G3t?pgjrmۛجwluGtfh9uyP0D;Uڽ"OXlif$)&|ML0Zrm1[HXPlPR0'G=i2N+0e2]]9VTPO׮7h(F*癈'=QVZDF,d߬~TX G[`le69CR(!S2!P <0x<!1AQ "Raq02Br#SCTb ?Ζ"]mH5WR7k.ۛ!}Q~+yԏz|@T20S~Kek *zFf^2X*(@8r?CIuI|֓>^ExLgNUY+{.RѪ τV׸YTD I62'8Y27'\TP.6d&˦@Vqi|8-OΕ]ʔ U=TL8=;6c| !qfF3aů&~$l}'NWUs$Uk^SV:U# 6w++s&r+nڐ{@29 gL u"TÙM=6(^"7r}=6YݾlCuhquympǦ GjhsǜNlɻ}o7#S6aw4!OSrD57%|?x>L |/nD6?/8w#[)L7+6〼T ATg!%5MmZ/c-{1_Je"|^$'O&ޱմTrb$w)R$& N1EtdU3Uȉ1pM"N*(DNyd96.(jQ)X 5cQɎMyW?Q*!R>6=7)Xj5`J]e8%t!+'!1Q5 !1 AQaqё#2"0BRb?Gt^## .llQT $v,,m㵜5ubV =sY+@d{N! dnO<.-B;_wJt6;QJd.Qc%p{ 1,sNDdFHI0ГoXшe黅XۢF:)[FGXƹ/w_cMeD,ʡcc.WDtA$j@:) -# u c1<@ۗ9F)KJ-hpP]_x[qBlbpʖw q"LFGdƶ*s+ډ_Zc"?%t[IP 6J]#=ɺVvvCGsGh1 >)6|ey?Lӣm,4GWUi`]uJVoVDG< SB6ϏQ@ TiUlyOU0kfV~~}SZ@*WUUi##; s/[=!7}"WN]'(L! ~y5g9T̅JkbM' +s:S +B)v@Mj e Cf jE 0Y\QnzG1д~Wo{T9?`Rmyhsy3!HAD]mc1~2LSu7xT;j$`}4->L#vzŏILS ֭T{rjGKC;bpU=-`BsK.SFw4Mq]ZdHS0)tLg