shell bypass 403
# SPDX-License-Identifier: LGPL-2.1-or-later # Copyright (c) 2022 Red Hat, Inc. # Copyright (c) 2022 Alexander Sosedkin <[email protected]> import os import subprocess from tempfile import mkstemp from .configgenerator import ConfigGenerator # class SequoiaGenerator is intentionally omitted from RHEL-9 class RPMSequoiaGenerator(ConfigGenerator): # Limitation: controls only # * `hash_algorithms`, # * `symmetric_algorithms` and # * partially, `asymmetric_algorithms`, deduced from `sign` and `group` # RHEL-9 only has rpm-sequoia CONFIG_NAME = 'rpm-sequoia' SCOPES = {'rpm', 'rpm-sequoia'} # RHEL-112392 hack: some algorithms have to be force-enabled force_on_group = ['MLKEM768-X25519', 'MLKEM1024-X448'] force_on_sign = ['MLDSA65-ED25519', 'MLDSA87-ED448'] # sequoia display name to c-p name, taken from sequoia_openpgp/types/mod.rs hash_backwards_map = { 'md5': 'MD5', 'sha1': 'SHA1', 'ripemd160': None, 'sha224': 'SHA2-224', 'sha256': 'SHA2-256', 'sha384': 'SHA2-384', 'sha512': 'SHA2-512', 'sha3-256': 'SHA3-256', 'sha3-512': 'SHA3-512', } symmetric_backwards_map = { 'idea': 'IDEA-CFB', 'tripledes': '3DES-CFB', 'cast5': None, 'blowfish': None, 'aes128': 'AES-128-CFB', 'aes192': 'AES-192-CFB', 'aes256': 'AES-256-CFB', 'twofish': None, 'camellia128': 'CAMELLIA-128-CFB', 'camellia192': 'CAMELLIA-192-CFB', 'camellia256': 'CAMELLIA-256-CFB', # 'unencrypted': 'NULL', # can't be set } asymmetric_group_backwards_map = { 'nistp256': 'SECP256R1', 'nistp384': 'SECP384R1', 'nistp521': 'SECP521R1', 'cv25519': 'X25519', 'x25519': 'X25519', 'x448': 'X448', 'mlkem768-x25519': 'MLKEM768-X25519', 'mlkem1024-x448': 'MLKEM1024-X448', } asymmetric_sign_backwards_map = { 'ed25519': 'EDDSA-ED25519', 'ed448': 'EDDSA-ED448', 'mldsa65-ed25519': 'MLDSA65-ED25519', 'mldsa87-ed448': 'MLDSA87-ED448', } asymmetric_always_disabled = ( 'elgamal1024', 'elgamal2048', 'elgamal3072', 'elgamal4096', 'brainpoolp256', 'brainpoolp512', # 'unknown', # can't be set ) aead_backwards_map = { 'eax': {'AES-256-EAX', 'AES-128-EAX'}, 'ocb': {'AES-256-OCB', 'AES-128-OCB'}, 'gcm': {'AES-256-GCM', 'AES-128-GCM'}, } # listing new algorithms here would let old sequoia ignore unknown values ignore_invalid = { # c-p property name -> tuple[sequoia algorithm names] # sequoia-openpgp 2, rpm-sequoia 1.8 'hash': ('sha3-256', 'sha3-512'), 'group': ('x25519', 'x448', 'mlkem768-x25519', 'mlkem1024-x448'), 'sign': ('ed25519', 'ed448', 'mldsa65-ed25519', 'mldsa87-ed448'), 'aead': ('gcm',), } @classmethod def _generate_ignore_invalid(cls, *kinds): values = [v for k in kinds for v in cls.ignore_invalid.get(k, [])] if values: values = ', '.join(f'"{v}"' for v in values) return f'ignore_invalid = [ {values} ]\n' return '' @classmethod def generate_config(cls, policy): p = policy.enabled cfg = '[hash_algorithms]\n' cfg += cls._generate_ignore_invalid('hash') for seqoia_name, c_p_name in cls.hash_backwards_map.items(): v = 'always' if c_p_name in p['hash'] else 'never' cfg += f'{seqoia_name}.collision_resistance = "{v}"\n' cfg += f'{seqoia_name}.second_preimage_resistance = "{v}"\n' cfg += 'default_disposition = "never"\n\n' cfg += '[symmetric_algorithms]\n' cfg += cls._generate_ignore_invalid('cipher') for seqoia_name, c_p_name in cls.symmetric_backwards_map.items(): v = 'always' if c_p_name in p['cipher'] else 'never' cfg += f'{seqoia_name} = "{v}"\n' cfg += 'default_disposition = "never"\n\n' cfg += '[asymmetric_algorithms]\n' cfg += cls._generate_ignore_invalid('group', 'sign') # ugly inference from various lists: rsa/dsa is sign + min_size any_rsa = any(s.startswith('RSA-') for s in p['sign']) any_dsa = any(s.startswith('DSA-') for s in p['sign']) min_rsa = policy.integers['min_rsa_size'] for l in 1024, 2048, 3072, 4096: v = 'always' if l >= min_rsa and any_rsa else 'never' cfg += f'rsa{l} = "{v}"\n' min_dsa = policy.integers['min_dsa_size'] for l in 1024, 2048, 3072, 4096: v = 'always' if l >= min_dsa and any_dsa else 'never' cfg += f'dsa{l} = "{v}"\n' # groups for seq_name, group in cls.asymmetric_group_backwards_map.items(): v = 'always' if group in p['group'] else 'never' if group in cls.force_on_group: v = 'always' cfg += f'{seq_name} = "{v}"\n' # sign for seq_name, sign in cls.asymmetric_sign_backwards_map.items(): v = 'always' if sign in p['sign'] else 'never' if sign in cls.force_on_sign: v = 'always' cfg += f'{seq_name} = "{v}"\n' # always disabled for seq_name in cls.asymmetric_always_disabled: cfg += f'{seq_name} = "never"\n' cfg += 'default_disposition = "never"\n' # aead algorithms cfg += '\n[aead_algorithms]\n' cfg += 'default_disposition = "never"\n' cfg += cls._generate_ignore_invalid('aead') for seq_name, c_p_names in cls.aead_backwards_map.items(): v = 'always' if c_p_names.intersection(p['cipher']) else 'never' cfg += f'{seq_name} = "{v}"\n' return cfg @classmethod def test_config(cls, config): stricter_config = '\n'.join( l for l in config.split('\n') if not l.startswith('ignore_invalid = ') ) tightened = config != stricter_config config = stricter_config if os.getenv('OLD_SEQUOIA') == '1': return True fd, path = mkstemp() try: with os.fdopen(fd, 'w') as f: f.write(config) r = subprocess.run(['sequoia-policy-config-check', # noqa: S607 path], check=False, encoding='utf-8', stdout=subprocess.PIPE, stderr=subprocess.STDOUT) cls.eprint('sequoia-policy-config-check returns ' f'{r.returncode}' f'{" `" + r.stdout + "`" if r.stdout else ""}') if (r.returncode, r.stdout) == (0, ''): return True cls.eprint('There is an error in a ' + ('tightened' if tightened else 'generated') + ' sequoia policy') cls.eprint(f'Policy:\n{config}') except FileNotFoundError: cls.eprint('sequoia-policy-config not found, skipping...') return True finally: os.unlink(path) return False