Allow to configure encryption level. (#523)

pull/525/head
Felix Fontein 2022-11-01 19:51:28 +01:00 committed by GitHub
parent 4533b3e934
commit e4e2b804bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 107 additions and 12 deletions

View File

@ -0,0 +1,4 @@
minor_changes:
- "openssl_pkcs12 - add option ``encryption_level`` which allows to chose ``compatibility2022`` when cryptography >= 38.0.0 is used
to enable a more backwards compatible encryption algorithm. If cryptography uses OpenSSL 3.0.0 or newer, the default algorithm
is not compatible with older software (https://github.com/ansible-collections/community.crypto/pull/523)."

View File

@ -65,15 +65,30 @@ options:
iter_size:
description:
- Number of times to repeat the encryption step.
- This is not considered during idempotency checks.
- This is only used by the C(pyopenssl) backend. When using it, the default is C(2048).
- This is B(not considered during idempotency checks).
- This is only used by the C(pyopenssl) backend, or when I(encryption_level=compatibility2022).
- When using it, the default is C(2048) for C(pyopenssl) and C(50000) for C(cryptography).
type: int
maciter_size:
description:
- Number of times to repeat the MAC step.
- This is not considered during idempotency checks.
- This is B(not considered during idempotency checks).
- This is only used by the C(pyopenssl) backend. When using it, the default is C(1).
type: int
encryption_level:
description:
- Determines the encryption level used.
- C(auto) uses the default of the selected backend. For C(cryptography), this is what the
cryptography library's specific version considers the best available encryption.
- C(compatibility2022) uses compatibility settings for older software in 2022.
This is only supported by the C(cryptography) backend if cryptography >= 38.0.0 is available.
- B(Note) that this option is B(not used for idempotency).
choices:
- auto
- compatibility2022
default: auto
type: str
version_added: 2.8.0
passphrase:
description:
- The PKCS#12 password.
@ -128,8 +143,8 @@ options:
description:
- Determines which crypto backend to use.
- The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl).
If one of I(iter_size) or I(maciter_size) is used, C(auto) will always result in C(pyopenssl) to be chosen
for backwards compatibility.
If I(iter_size) is used together with I(encryption_level != compatibility2022), or if I(maciter_size) is used,
C(auto) will always result in C(pyopenssl) to be chosen for backwards compatibility.
- If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library.
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
# - Please note that the C(pyopenssl) backend has been deprecated in community.crypto x.y.0, and will be
@ -302,6 +317,18 @@ except ImportError:
else:
CRYPTOGRAPHY_FOUND = True
CRYPTOGRAPHY_COMPATIBILITY2022_ERR = None
try:
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.serialization.pkcs12 import PBES
# Try to build encryption builder for compatibility2022
serialization.PrivateFormat.PKCS12.encryption_builder().key_cert_algorithm(PBES.PBESv1SHA1And3KeyTripleDESCBC).hmac_hash(hashes.SHA1())
except Exception:
CRYPTOGRAPHY_COMPATIBILITY2022_ERR = traceback.format_exc()
CRYPTOGRAPHY_HAS_COMPATIBILITY2022 = False
else:
CRYPTOGRAPHY_HAS_COMPATIBILITY2022 = True
def load_certificate_set(filename, backend):
'''
@ -317,7 +344,7 @@ class PkcsError(OpenSSLObjectError):
class Pkcs(OpenSSLObject):
def __init__(self, module, backend):
def __init__(self, module, backend, iter_size_default=2048):
super(Pkcs, self).__init__(
module.params['path'],
module.params['state'],
@ -330,8 +357,9 @@ class Pkcs(OpenSSLObject):
self.other_certificates_parse_all = module.params['other_certificates_parse_all']
self.certificate_path = module.params['certificate_path']
self.friendly_name = module.params['friendly_name']
self.iter_size = module.params['iter_size'] or 2048
self.iter_size = module.params['iter_size'] or iter_size_default
self.maciter_size = module.params['maciter_size'] or 1
self.encryption_level = module.params['encryption_level']
self.passphrase = module.params['passphrase']
self.pkcs12 = None
self.privatekey_passphrase = module.params['privatekey_passphrase']
@ -508,6 +536,8 @@ class Pkcs(OpenSSLObject):
class PkcsPyOpenSSL(Pkcs):
def __init__(self, module):
super(PkcsPyOpenSSL, self).__init__(module, 'pyopenssl')
if self.encryption_level != 'auto':
module.fail_json(msg='The PyOpenSSL backend only supports encryption_level = auto')
def generate_bytes(self, module):
"""Generate PKCS#12 file archive."""
@ -573,7 +603,12 @@ class PkcsPyOpenSSL(Pkcs):
class PkcsCryptography(Pkcs):
def __init__(self, module):
super(PkcsCryptography, self).__init__(module, 'cryptography')
super(PkcsCryptography, self).__init__(module, 'cryptography', iter_size_default=50000)
if self.encryption_level == 'compatibility2022' and not CRYPTOGRAPHY_HAS_COMPATIBILITY2022:
module.fail_json(
msg='The installed cryptography version does not support encryption_level = compatibility2022.'
' You need cryptography >= 38.0.0 and support for SHA1',
exception=CRYPTOGRAPHY_COMPATIBILITY2022_ERR)
def generate_bytes(self, module):
"""Generate PKCS#12 file archive."""
@ -593,13 +628,25 @@ class PkcsCryptography(Pkcs):
# Store fake object which can be used to retrieve the components back
self.pkcs12 = (pkey, cert, self.other_certificates, friendly_name)
if not self.passphrase:
encryption = serialization.NoEncryption()
elif self.encryption_level == 'compatibility2022':
encryption = (
serialization.PrivateFormat.PKCS12.encryption_builder().
kdf_rounds(self.iter_size).
key_cert_algorithm(PBES.PBESv1SHA1And3KeyTripleDESCBC).
hmac_hash(hashes.SHA1()).
build(to_bytes(self.passphrase))
)
else:
encryption = serialization.BestAvailableEncryption(to_bytes(self.passphrase))
return serialize_key_and_certificates(
friendly_name,
pkey,
cert,
self.other_certificates,
serialization.BestAvailableEncryption(to_bytes(self.passphrase))
if self.passphrase else serialization.NoEncryption(),
encryption,
)
def parse_bytes(self, pkcs12_content):
@ -658,8 +705,11 @@ def select_backend(module, backend):
can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION)
# If no restrictions are provided, first try cryptography, then pyOpenSSL
if module.params['iter_size'] is not None or module.params['maciter_size'] is not None:
# If iter_size or maciter_size is specified, use pyOpenSSL backend
if (
(module.params['iter_size'] is not None and module.params['encryption_level'] != 'compatibility2022')
or module.params['maciter_size'] is not None
):
# If iter_size (for encryption_level != compatibility2022) or maciter_size is specified, use pyOpenSSL backend
backend = 'pyopenssl'
elif can_use_cryptography:
backend = 'cryptography'
@ -697,6 +747,7 @@ def main():
certificate_path=dict(type='path'),
force=dict(type='bool', default=False),
friendly_name=dict(type='str', aliases=['name']),
encryption_level=dict(type='str', choices=['auto', 'compatibility2022'], default='auto'),
iter_size=dict(type='int'),
maciter_size=dict(type='int'),
passphrase=dict(type='str', no_log=True),

View File

@ -330,6 +330,25 @@
path: '{{ remote_tmp_dir }}/ansible_empty.pem'
action: parse
- name: "({{ select_crypto_backend }}) Generate PKCS#12 file passphrase and compatibility encryption"
openssl_pkcs12:
select_crypto_backend: '{{ select_crypto_backend }}'
path: '{{ remote_tmp_dir }}/ansible_compatibility2022.p12'
friendly_name: compat_fn
encryption_level: compatibility2022
iter_size: 3210
passphrase: magicpassword
privatekey_path: '{{ remote_tmp_dir }}/ansible_pkey1.pem'
certificate_path: '{{ remote_tmp_dir }}/ansible1.crt'
other_certificates:
- '{{ remote_tmp_dir }}/ansible2.crt'
- '{{ remote_tmp_dir }}/ansible3.crt'
state: present
register: p12_compatibility2022
when:
- select_crypto_backend == 'cryptography'
- cryptography_version.stdout is version('38.0.0', '>=')
- import_tasks: ../tests/validate.yml
always:
@ -345,3 +364,4 @@
- ansible_pw2
- ansible_pw3
- ansible_empty
- ansible_compatibility2022

View File

@ -90,3 +90,23 @@
- p12_empty_idem is not changed
- p12_empty_concat_idem is not changed
- (empty_contents == empty_expected_cryptography) or (empty_contents == empty_expected_pyopenssl and select_crypto_backend == 'pyopenssl')
- name: '({{ select_crypto_backend }}) PKCS#12 with compatibility2022 settings'
when:
- select_crypto_backend == 'cryptography'
- cryptography_version.stdout is version('38.0.0', '>=')
block:
- name: '({{ select_crypto_backend }}) Validate PKCS#12 with compatibility2022 settings'
shell: "{{ openssl_binary }} pkcs12 -info -in {{ remote_tmp_dir }}/ansible_compatibility2022.p12 -nodes -passin pass:'magicpassword'"
register: p12_validate_compatibility2022
- name: '({{ select_crypto_backend }}) Check PKCS#12 with compatibility2022 settings'
assert:
that:
- p12_compatibility2022 is changed
- >-
'PKCS7 Encrypted data: pbeWithSHA1And3-KeyTripleDES-CBC, Iteration 3210' in p12_validate_compatibility2022.stderr_lines
- >-
'Shrouded Keybag: pbeWithSHA1And3-KeyTripleDES-CBC, Iteration 3210' in p12_validate_compatibility2022.stderr_lines
- >-
'friendlyName: compat_fn' in p12_validate_compatibility2022.stdout