| DIR: /usr/share/crypto-policies/python/policygenerators /usr/share/crypto-policies/python/policygenerators/ |
| Current File : /usr/share/crypto-policies/python/policygenerators/sequoia.py |
# SPDX-License-Identifier: LGPL-2.1-or-later
# Copyright (c) 2022 Red Hat, Inc.
# Copyright (c) 2022 Alexander Sosedkin <asosedkin@redhat.com>
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
|