mirror of
https://github.com/ansible-collections/community.crypto.git
synced 2026-05-06 13:22:58 +00:00
Remove PyOpenSSL backends (except for openssl_pkcs12) (#273)
* Remove Ubuntu 16.04 (Xenial Xerus) from CI. * Removing PyOpenSSL backend from everywhere but openssl_pkcs12. * Remove PyOpenSSL support from module_utils that's not needed for openssl_pkcs12. * Add changelog fragment.
This commit is contained in:
@@ -33,37 +33,11 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptograp
|
||||
cryptography_serial_number_of_cert,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.pyopenssl_support import (
|
||||
pyopenssl_get_extensions_from_cert,
|
||||
pyopenssl_normalize_name,
|
||||
pyopenssl_normalize_name_attribute,
|
||||
)
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.publickey_info import (
|
||||
get_publickey_info,
|
||||
)
|
||||
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION = '1.6'
|
||||
MINIMAL_PYOPENSSL_VERSION = '0.15'
|
||||
|
||||
PYOPENSSL_IMP_ERR = None
|
||||
try:
|
||||
import OpenSSL
|
||||
from OpenSSL import crypto
|
||||
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
|
||||
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER >= 0x10100000:
|
||||
# OpenSSL 1.1.0 or newer
|
||||
OPENSSL_MUST_STAPLE_NAME = b"tlsfeature"
|
||||
OPENSSL_MUST_STAPLE_VALUE = b"status_request"
|
||||
else:
|
||||
# OpenSSL 1.0.x or older
|
||||
OPENSSL_MUST_STAPLE_NAME = b"1.3.6.1.5.5.7.1.24"
|
||||
OPENSSL_MUST_STAPLE_VALUE = b"DER:30:03:02:01:05"
|
||||
except ImportError:
|
||||
PYOPENSSL_IMP_ERR = traceback.format_exc()
|
||||
PYOPENSSL_FOUND = False
|
||||
else:
|
||||
PYOPENSSL_FOUND = True
|
||||
|
||||
CRYPTOGRAPHY_IMP_ERR = None
|
||||
try:
|
||||
@@ -209,20 +183,19 @@ class CertificateInfoRetrieval(object):
|
||||
result['fingerprints'] = get_fingerprint_of_bytes(
|
||||
self._get_der_bytes(), prefer_one=prefer_one_fingerprint)
|
||||
|
||||
if self.backend != 'pyopenssl':
|
||||
ski = self._get_subject_key_identifier()
|
||||
if ski is not None:
|
||||
ski = to_native(binascii.hexlify(ski))
|
||||
ski = ':'.join([ski[i:i + 2] for i in range(0, len(ski), 2)])
|
||||
result['subject_key_identifier'] = ski
|
||||
ski = self._get_subject_key_identifier()
|
||||
if ski is not None:
|
||||
ski = to_native(binascii.hexlify(ski))
|
||||
ski = ':'.join([ski[i:i + 2] for i in range(0, len(ski), 2)])
|
||||
result['subject_key_identifier'] = ski
|
||||
|
||||
aki, aci, acsn = self._get_authority_key_identifier()
|
||||
if aki is not None:
|
||||
aki = to_native(binascii.hexlify(aki))
|
||||
aki = ':'.join([aki[i:i + 2] for i in range(0, len(aki), 2)])
|
||||
result['authority_key_identifier'] = aki
|
||||
result['authority_cert_issuer'] = aci
|
||||
result['authority_cert_serial_number'] = acsn
|
||||
aki, aci, acsn = self._get_authority_key_identifier()
|
||||
if aki is not None:
|
||||
aki = to_native(binascii.hexlify(aki))
|
||||
aki = ':'.join([aki[i:i + 2] for i in range(0, len(aki), 2)])
|
||||
result['authority_key_identifier'] = aki
|
||||
result['authority_cert_issuer'] = aci
|
||||
result['authority_cert_serial_number'] = acsn
|
||||
|
||||
result['serial_number'] = self._get_serial_number()
|
||||
result['extensions_by_oid'] = self._get_all_extensions()
|
||||
@@ -392,136 +365,9 @@ class CertificateInfoRetrievalCryptography(CertificateInfoRetrieval):
|
||||
return None
|
||||
|
||||
|
||||
class CertificateInfoRetrievalPyOpenSSL(CertificateInfoRetrieval):
|
||||
"""validate the supplied certificate."""
|
||||
|
||||
def __init__(self, module, content):
|
||||
super(CertificateInfoRetrievalPyOpenSSL, self).__init__(module, 'pyopenssl', content)
|
||||
|
||||
def _get_der_bytes(self):
|
||||
return crypto.dump_certificate(crypto.FILETYPE_ASN1, self.cert)
|
||||
|
||||
def _get_signature_algorithm(self):
|
||||
return to_text(self.cert.get_signature_algorithm())
|
||||
|
||||
def __get_name(self, name):
|
||||
result = []
|
||||
for sub in name.get_components():
|
||||
result.append([pyopenssl_normalize_name(sub[0]), to_text(sub[1])])
|
||||
return result
|
||||
|
||||
def _get_subject_ordered(self):
|
||||
return self.__get_name(self.cert.get_subject())
|
||||
|
||||
def _get_issuer_ordered(self):
|
||||
return self.__get_name(self.cert.get_issuer())
|
||||
|
||||
def _get_version(self):
|
||||
# Version numbers in certs are off by one:
|
||||
# v1: 0, v2: 1, v3: 2 ...
|
||||
return self.cert.get_version() + 1
|
||||
|
||||
def _get_extension(self, short_name):
|
||||
for extension_idx in range(0, self.cert.get_extension_count()):
|
||||
extension = self.cert.get_extension(extension_idx)
|
||||
if extension.get_short_name() == short_name:
|
||||
result = [
|
||||
pyopenssl_normalize_name(usage.strip()) for usage in to_text(extension, errors='surrogate_or_strict').split(',')
|
||||
]
|
||||
return sorted(result), bool(extension.get_critical())
|
||||
return None, False
|
||||
|
||||
def _get_key_usage(self):
|
||||
return self._get_extension(b'keyUsage')
|
||||
|
||||
def _get_extended_key_usage(self):
|
||||
return self._get_extension(b'extendedKeyUsage')
|
||||
|
||||
def _get_basic_constraints(self):
|
||||
return self._get_extension(b'basicConstraints')
|
||||
|
||||
def _get_ocsp_must_staple(self):
|
||||
extensions = [self.cert.get_extension(i) for i in range(0, self.cert.get_extension_count())]
|
||||
oms_ext = [
|
||||
ext for ext in extensions
|
||||
if to_bytes(ext.get_short_name()) == OPENSSL_MUST_STAPLE_NAME and to_bytes(ext) == OPENSSL_MUST_STAPLE_VALUE
|
||||
]
|
||||
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER < 0x10100000:
|
||||
# Older versions of libssl don't know about OCSP Must Staple
|
||||
oms_ext.extend([ext for ext in extensions if ext.get_short_name() == b'UNDEF' and ext.get_data() == b'\x30\x03\x02\x01\x05'])
|
||||
if oms_ext:
|
||||
return True, bool(oms_ext[0].get_critical())
|
||||
else:
|
||||
return None, False
|
||||
|
||||
def _get_subject_alt_name(self):
|
||||
for extension_idx in range(0, self.cert.get_extension_count()):
|
||||
extension = self.cert.get_extension(extension_idx)
|
||||
if extension.get_short_name() == b'subjectAltName':
|
||||
result = [pyopenssl_normalize_name_attribute(altname.strip()) for altname in
|
||||
to_text(extension, errors='surrogate_or_strict').split(', ')]
|
||||
return result, bool(extension.get_critical())
|
||||
return None, False
|
||||
|
||||
def get_not_before(self):
|
||||
time_string = to_native(self.cert.get_notBefore())
|
||||
return datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ")
|
||||
|
||||
def get_not_after(self):
|
||||
time_string = to_native(self.cert.get_notAfter())
|
||||
return datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ")
|
||||
|
||||
def _get_public_key_pem(self):
|
||||
try:
|
||||
return crypto.dump_publickey(
|
||||
crypto.FILETYPE_PEM,
|
||||
self.cert.get_pubkey(),
|
||||
)
|
||||
except AttributeError:
|
||||
try:
|
||||
# pyOpenSSL < 16.0:
|
||||
bio = crypto._new_mem_buf()
|
||||
rc = crypto._lib.PEM_write_bio_PUBKEY(bio, self.cert.get_pubkey()._pkey)
|
||||
if rc != 1:
|
||||
crypto._raise_current_error()
|
||||
return crypto._bio_to_string(bio)
|
||||
except AttributeError:
|
||||
self.module.warn('Your pyOpenSSL version does not support dumping public keys. '
|
||||
'Please upgrade to version 16.0 or newer, or use the cryptography backend.')
|
||||
|
||||
def _get_public_key_object(self):
|
||||
return self.cert.get_pubkey()
|
||||
|
||||
def _get_subject_key_identifier(self):
|
||||
# Won't be implemented
|
||||
return None
|
||||
|
||||
def _get_authority_key_identifier(self):
|
||||
# Won't be implemented
|
||||
return None, None, None
|
||||
|
||||
def _get_serial_number(self):
|
||||
return self.cert.get_serial_number()
|
||||
|
||||
def _get_all_extensions(self):
|
||||
return pyopenssl_get_extensions_from_cert(self.cert)
|
||||
|
||||
def _get_ocsp_uri(self):
|
||||
for i in range(self.cert.get_extension_count()):
|
||||
ext = self.cert.get_extension(i)
|
||||
if ext.get_short_name() == b'authorityInfoAccess':
|
||||
v = str(ext)
|
||||
m = re.search('^OCSP - URI:(.*)$', v, flags=re.MULTILINE)
|
||||
if m:
|
||||
return m.group(1)
|
||||
return None
|
||||
|
||||
|
||||
def get_certificate_info(module, backend, content, prefer_one_fingerprint=False):
|
||||
if backend == 'cryptography':
|
||||
info = CertificateInfoRetrievalCryptography(module, content)
|
||||
elif backend == 'pyopenssl':
|
||||
info = CertificateInfoRetrievalPyOpenSSL(module, content)
|
||||
return info.get_info(prefer_one_fingerprint=prefer_one_fingerprint)
|
||||
|
||||
|
||||
@@ -529,34 +375,17 @@ def select_backend(module, backend, content):
|
||||
if backend == 'auto':
|
||||
# Detection what is possible
|
||||
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
|
||||
can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION)
|
||||
|
||||
# First try cryptography, then pyOpenSSL
|
||||
# Try cryptography
|
||||
if can_use_cryptography:
|
||||
backend = 'cryptography'
|
||||
elif can_use_pyopenssl:
|
||||
backend = 'pyopenssl'
|
||||
|
||||
# Success?
|
||||
if backend == 'auto':
|
||||
module.fail_json(msg=("Can't detect any of the required Python libraries "
|
||||
"cryptography (>= {0}) or PyOpenSSL (>= {1})").format(
|
||||
MINIMAL_CRYPTOGRAPHY_VERSION,
|
||||
MINIMAL_PYOPENSSL_VERSION))
|
||||
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_VERSION))
|
||||
|
||||
if backend == 'pyopenssl':
|
||||
if not PYOPENSSL_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
|
||||
exception=PYOPENSSL_IMP_ERR)
|
||||
try:
|
||||
getattr(crypto.X509Req, 'get_extensions')
|
||||
except AttributeError:
|
||||
module.fail_json(msg='You need to have PyOpenSSL>=0.15 to generate CSRs')
|
||||
|
||||
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
|
||||
version='2.0.0', collection_name='community.crypto')
|
||||
return backend, CertificateInfoRetrievalPyOpenSSL(module, content)
|
||||
elif backend == 'cryptography':
|
||||
if backend == 'cryptography':
|
||||
if not CRYPTOGRAPHY_FOUND:
|
||||
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
|
||||
exception=CRYPTOGRAPHY_IMP_ERR)
|
||||
|
||||
Reference in New Issue
Block a user