From 36a790dcde44c2a66053522d38a6f9607c350f33 Mon Sep 17 00:00:00 2001 From: Andrea Tartaglia Date: Fri, 22 Mar 2019 13:21:23 +0000 Subject: [PATCH] New cryptography backend for openssl_certificate (#53924) * New cryptography backend for openssl_certificate load_* functions in module_utils/crypto.py now have a backend paramter which when set to 'cryptography' will return cryptography objects so they can be used for both pyopenssl and cryptography backends. Added a select_message_digest function too returning a cryptography digest hash from `cryptography.hazmat.primitives.hashes` Added new classes for Cryptography backend * Run test with various backends. * Prefixing tests. * Make sure we have the correct backend available. * Linting (flake8). * Moved cryptography import to separate try/except * Make sure certificate is actually valid at some time in the past. * Improve error handling. * Trying to fix validation for cryptography backend. * Fixed issue with keyUsage test in assertonly * Fixed CI/Lint issues * Fix private key problem for OwnCA. * Cryptography backend doesn't support v2 certs. * issue an expired cert with command when using cryptography backend * Added warning when backend is auto and v2 cert is requested * Bumped min cryptography version to 1.6 * Correctly check for failure when backend is cryptography and cert is v2 * Use self.backend where possible * Use secp521r1 EC when testing on CentOS6 * Fixed pylint issue * AcmeCertificate support for both backends * Review fixes * Fixed missing '(' when raising error * Fixed date_fmt loop * Updated docs and requirements with cryptography * Add openssl_certificate to changelog. --- changelogs/fragments/openssl-cryptography.yml | 1 + lib/ansible/module_utils/crypto.py | 116 ++- .../modules/crypto/openssl_certificate.py | 811 ++++++++++++++++-- .../openssl_certificate/tasks/assertonly.yml | 29 +- .../openssl_certificate/tasks/expired.yml | 21 +- .../openssl_certificate/tasks/impl.yml | 7 + .../openssl_certificate/tasks/main.yml | 30 +- .../openssl_certificate/tasks/ownca.yml | 48 +- .../openssl_certificate/tasks/selfsigned.yml | 61 +- .../tests/validate_ownca.yml | 52 +- .../tests/validate_selfsigned.yml | 58 +- 11 files changed, 1034 insertions(+), 200 deletions(-) create mode 100644 test/integration/targets/openssl_certificate/tasks/impl.yml diff --git a/changelogs/fragments/openssl-cryptography.yml b/changelogs/fragments/openssl-cryptography.yml index 394c6adfe9..25f40473ee 100644 --- a/changelogs/fragments/openssl-cryptography.yml +++ b/changelogs/fragments/openssl-cryptography.yml @@ -1,3 +1,4 @@ minor_changes: +- "openssl_certificate - now works with both PyOpenSSL and cryptography Python libraries. Autodetection can be overridden with ``select_crypto_backend`` option." - "openssl_csr - now works with both PyOpenSSL and cryptography Python libraries. Autodetection can be overridden with ``select_crypto_backend`` option." - "openssl_privatekey - now works with both PyOpenSSL and cryptography Python libraries. Autodetection can be overridden with ``select_crypto_backend`` option." diff --git a/lib/ansible/module_utils/crypto.py b/lib/ansible/module_utils/crypto.py index 6bee5d7c06..f698226ed2 100644 --- a/lib/ansible/module_utils/crypto.py +++ b/lib/ansible/module_utils/crypto.py @@ -23,6 +23,16 @@ except ImportError: # user know that OpenSSL couldn't be found. pass +try: + from cryptography import x509 + from cryptography.hazmat.backends import default_backend as cryptography_backend + from cryptography.hazmat.primitives.serialization import load_pem_private_key + from cryptography.hazmat.primitives import hashes +except ImportError: + # Error handled in the calling module. + pass + + import abc import datetime import errno @@ -82,70 +92,87 @@ def get_fingerprint(path, passphrase=None): return None -def load_privatekey(path, passphrase=None, check_passphrase=True): +def load_privatekey(path, passphrase=None, check_passphrase=True, backend='pyopenssl'): """Load the specified OpenSSL private key.""" try: with open(path, 'rb') as b_priv_key_fh: priv_key_detail = b_priv_key_fh.read() - # First try: try to load with real passphrase (resp. empty string) - # Will work if this is the correct passphrase, or the key is not - # password-protected. - try: - result = crypto.load_privatekey(crypto.FILETYPE_PEM, - priv_key_detail, - to_bytes(passphrase or '')) - except crypto.Error as e: - if len(e.args) > 0 and len(e.args[0]) > 0 and e.args[0][0][2] == 'bad decrypt': - # This happens in case we have the wrong passphrase. - if passphrase is not None: - raise OpenSSLBadPassphraseError('Wrong passphrase provided for private key!') - else: - raise OpenSSLBadPassphraseError('No passphrase provided, but private key is password-protected!') - raise - if check_passphrase: - # Next we want to make sure that the key is actually protected by - # a passphrase (in case we did try the empty string before, make - # sure that the key is not protected by the empty string) + if backend == 'pyopenssl': + + # First try: try to load with real passphrase (resp. empty string) + # Will work if this is the correct passphrase, or the key is not + # password-protected. try: - crypto.load_privatekey(crypto.FILETYPE_PEM, - priv_key_detail, - to_bytes('y' if passphrase == 'x' else 'x')) - if passphrase is not None: - # Since we can load the key without an exception, the - # key isn't password-protected - raise OpenSSLBadPassphraseError('Passphrase provided, but private key is not password-protected!') - except crypto.Error: - if passphrase is None and len(e.args) > 0 and len(e.args[0]) > 0 and e.args[0][0][2] == 'bad decrypt': - # The key is obviously protected by the empty string. - # Don't do this at home (if it's possible at all)... - raise OpenSSLBadPassphraseError('No passphrase provided, but private key is password-protected!') + result = crypto.load_privatekey(crypto.FILETYPE_PEM, + priv_key_detail, + to_bytes(passphrase or '')) + except crypto.Error as e: + if len(e.args) > 0 and len(e.args[0]) > 0 and e.args[0][0][2] == 'bad decrypt': + # This happens in case we have the wrong passphrase. + if passphrase is not None: + raise OpenSSLBadPassphraseError('Wrong passphrase provided for private key!') + else: + raise OpenSSLBadPassphraseError('No passphrase provided, but private key is password-protected!') + raise + if check_passphrase: + # Next we want to make sure that the key is actually protected by + # a passphrase (in case we did try the empty string before, make + # sure that the key is not protected by the empty string) + try: + crypto.load_privatekey(crypto.FILETYPE_PEM, + priv_key_detail, + to_bytes('y' if passphrase == 'x' else 'x')) + if passphrase is not None: + # Since we can load the key without an exception, the + # key isn't password-protected + raise OpenSSLBadPassphraseError('Passphrase provided, but private key is not password-protected!') + except crypto.Error: + if passphrase is None and len(e.args) > 0 and len(e.args[0]) > 0 and e.args[0][0][2] == 'bad decrypt': + # The key is obviously protected by the empty string. + # Don't do this at home (if it's possible at all)... + raise OpenSSLBadPassphraseError('No passphrase provided, but private key is password-protected!') + elif backend == 'cryptography': + try: + result = load_pem_private_key(priv_key_detail, + passphrase, + cryptography_backend()) + except TypeError as e: + raise OpenSSLBadPassphraseError('Wrong or empty passphrase provided for private key') + except ValueError as e: + raise OpenSSLBadPassphraseError('Wrong passphrase provided for private key') + return result except (IOError, OSError) as exc: raise OpenSSLObjectError(exc) -def load_certificate(path): +def load_certificate(path, backend='pyopenssl'): """Load the specified certificate.""" try: with open(path, 'rb') as cert_fh: cert_content = cert_fh.read() - return crypto.load_certificate(crypto.FILETYPE_PEM, cert_content) + if backend == 'pyopenssl': + return crypto.load_certificate(crypto.FILETYPE_PEM, cert_content) + elif backend == 'cryptography': + return x509.load_pem_x509_certificate(cert_content, cryptography_backend()) except (IOError, OSError) as exc: raise OpenSSLObjectError(exc) -def load_certificate_request(path): +def load_certificate_request(path, backend='pyopenssl'): """Load the specified certificate signing request.""" - try: with open(path, 'rb') as csr_fh: csr_content = csr_fh.read() - return crypto.load_certificate_request(crypto.FILETYPE_PEM, csr_content) except (IOError, OSError) as exc: raise OpenSSLObjectError(exc) + if backend == 'pyopenssl': + return crypto.load_certificate_request(crypto.FILETYPE_PEM, csr_content) + elif backend == 'cryptography': + return x509.load_pem_x509_csr(csr_content, cryptography_backend()) def parse_name_field(input_dict): @@ -192,6 +219,21 @@ def convert_relative_to_datetime(relative_time_string): return datetime.datetime.utcnow() - offset +def select_message_digest(digest_string): + digest = None + if digest_string == 'sha256': + digest = hashes.SHA256() + elif digest_string == 'sha384': + digest = hashes.SHA384() + elif digest_string == 'sha512': + digest = hashes.SHA512() + elif digest_string == 'sha1': + digest = hashes.SHA1() + elif digest_string == 'md5': + digest = hashes.MD5() + return digest + + @six.add_metaclass(abc.ABCMeta) class OpenSSLObject(object): diff --git a/lib/ansible/modules/crypto/openssl_certificate.py b/lib/ansible/modules/crypto/openssl_certificate.py index ba91773176..64890cb594 100644 --- a/lib/ansible/modules/crypto/openssl_certificate.py +++ b/lib/ansible/modules/crypto/openssl_certificate.py @@ -28,9 +28,11 @@ description: - Many properties that can be specified in this module are for validation of an existing or newly generated certificate. The proper place to specify them, if you want to receive a certificate with these properties is a CSR (Certificate Signing Request). - - It uses the pyOpenSSL python library to interact with OpenSSL. + - It uses the pyOpenSSL or cryptography python library to interact with OpenSSL. + - If both the cryptography and PyOpenSSL libraries are available (and meet the minimum version requirements) + cryptography will be preferred as a backend over PyOpenSSL (unless the backend is forced with C(select_crypto_backend)) requirements: - - python-pyOpenSSL >= 0.15 (if using C(selfsigned) or C(assertonly) provider) + - PyOpenSSL >= 0.15 or cryptography >= 1.6 (if using C(selfsigned) or C(assertonly) provider) - acme-tiny (if using the C(acme) provider) author: - Yanis Guenane (@Spredzy) @@ -346,6 +348,17 @@ options: default: no aliases: [ subjectAltName_strict ] + select_crypto_backend: + 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 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. + type: str + default: auto + choices: [ auto, cryptography, pyopenssl ] + version_added: "2.8" + extends_documentation_fragment: files notes: - All ASN.1 TIME values should be specified following the YYYYMMDDHHMMSSZ pattern. @@ -504,20 +517,39 @@ from random import randint import datetime import os import traceback +from distutils.version import LooseVersion from ansible.module_utils import crypto as crypto_utils from ansible.module_utils.basic import AnsibleModule, missing_required_lib -from ansible.module_utils._text import to_native, to_bytes +from ansible.module_utils._text import to_native, to_bytes, to_text + +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__) except ImportError: PYOPENSSL_IMP_ERR = traceback.format_exc() - pyopenssl_found = False + PYOPENSSL_FOUND = False else: - pyopenssl_found = True + PYOPENSSL_FOUND = True + +CRYPTOGRAPHY_IMP_ERR = None +try: + import cryptography + from cryptography import x509 + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives.serialization import Encoding + from cryptography.x509 import NameAttribute, Name + CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__) +except ImportError: + CRYPTOGRAPHY_IMP_ERR = traceback.format_exc() + CRYPTOGRAPHY_FOUND = False +else: + CRYPTOGRAPHY_FOUND = True class CertificateError(crypto_utils.OpenSSLObjectError): @@ -526,7 +558,7 @@ class CertificateError(crypto_utils.OpenSSLObjectError): class Certificate(crypto_utils.OpenSSLObject): - def __init__(self, module): + def __init__(self, module, backend): super(Certificate, self).__init__( module.params['path'], module.params['state'], @@ -541,6 +573,7 @@ class Certificate(crypto_utils.OpenSSLObject): self.cert = None self.privatekey = None self.csr = None + self.backend = backend self.module = module def get_relative_time_option(self, input_string, input_name): @@ -548,37 +581,55 @@ class Certificate(crypto_utils.OpenSSLObject): or an ASN1 formatted string is provided.""" result = input_string if result.startswith("+") or result.startswith("-"): - result = crypto_utils.convert_relative_to_datetime( - result).strftime("%Y%m%d%H%M%SZ") + result_datetime = crypto_utils.convert_relative_to_datetime( + result) + if self.backend == 'pyopenssl': + return result_datetime.strftime("%Y%m%d%H%M%SZ") + elif self.backend == 'cryptography': + return result_datetime if result is None: raise CertificateError( 'The timespec "%s" for %s is not valid' % input_string, input_name) + if self.backend == 'cryptography': + for date_fmt in ['%Y%m%d%H%M%SZ', '%Y%m%d%H%MZ', '%Y%m%d%H%M%S%z', '%Y%m%d%H%M%z']: + try: + result = datetime.datetime.strptime(input_string, date_fmt) + break + except ValueError: + pass + + if not isinstance(result, datetime.datetime): + raise CertificateError( + 'The time spec "%s" for %s is invalid' % + (input_string, input_name) + ) return result - def check(self, module, perms_required=True): - """Ensure the resource is in its desired state.""" + def _validate_privatekey(self): + if self.backend == 'pyopenssl': + ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_2_METHOD) + ctx.use_privatekey(self.privatekey) + ctx.use_certificate(self.cert) + try: + ctx.check_privatekey() + return True + except OpenSSL.SSL.Error: + return False + elif self.backend == 'cryptography': + return self.cert.public_key().public_numbers() == self.privatekey.public_key().public_numbers() - state_and_perms = super(Certificate, self).check(module, perms_required) - - def _validate_privatekey(): - if self.privatekey_path: - ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_2_METHOD) - ctx.use_privatekey(self.privatekey) - ctx.use_certificate(self.cert) - try: - ctx.check_privatekey() - return True - except OpenSSL.SSL.Error: - return False - - def _validate_csr(): + def _validate_csr(self): + if self.backend == 'pyopenssl': + # Verify that CSR is signed by certificate's private key try: self.csr.verify(self.cert.get_pubkey()) except OpenSSL.crypto.Error: return False + # Check subject if self.csr.get_subject() != self.cert.get_subject(): return False + # Check extensions csr_extensions = self.csr.get_extensions() cert_extension_count = self.cert.get_extension_count() if len(csr_extensions) != cert_extension_count: @@ -589,35 +640,156 @@ class Certificate(crypto_utils.OpenSSLObject): if cert_extension.get_data() != list(csr_extension)[0].get_data(): return False return True + elif self.backend == 'cryptography': + # Verify that CSR is signed by certificate's private key + if not self.csr.is_signature_valid: + return False + if self.csr.public_key().public_numbers() != self.cert.public_key().public_numbers(): + return False + # Check subject + if self.csr.subject != self.cert.subject: + return False + # Check extensions + cert_exts = self.cert.extensions + csr_exts = self.csr.extensions + if len(cert_exts) != len(csr_exts): + return False + for cert_ext in cert_exts: + try: + csr_ext = csr_exts.get_extension_for_oid(cert_ext.oid) + if cert_ext != csr_ext: + return False + except cryptography.x509.ExtensionNotFound as e: + return False + return True + + def check(self, module, perms_required=True): + """Ensure the resource is in its desired state.""" + + state_and_perms = super(Certificate, self).check(module, perms_required) if not state_and_perms: return False - self.cert = crypto_utils.load_certificate(self.path) + self.cert = crypto_utils.load_certificate(self.path, backend=self.backend) if self.privatekey_path: try: self.privatekey = crypto_utils.load_privatekey( self.privatekey_path, - self.privatekey_passphrase + self.privatekey_passphrase, + backend=self.backend ) except crypto_utils.OpenSSLBadPassphraseError as exc: raise CertificateError(exc) - return _validate_privatekey() + return self._validate_privatekey() if self.csr_path: - self.csr = crypto_utils.load_certificate_request(self.csr_path) - if not _validate_csr(): + self.csr = crypto_utils.load_certificate_request(self.csr_path, backend=self.backend) + if not self._validate_csr(): return False return True +class SelfSignedCertificateCryptography(Certificate): + """Generate the self-signed certificate, using the cryptography backend""" + def __init__(self, module): + super(SelfSignedCertificateCryptography, self).__init__(module, 'cryptography') + self.notBefore = self.get_relative_time_option(module.params['selfsigned_not_before'], 'selfsigned_not_before') + self.notAfter = self.get_relative_time_option(module.params['selfsigned_not_after'], 'selfsigned_not_after') + self.digest = crypto_utils.select_message_digest(module.params['selfsigned_digest']) + self.version = module.params['selfsigned_version'] + self.serial_number = x509.random_serial_number() + self.csr = crypto_utils.load_certificate_request(self.csr_path, backend=self.backend) + self._module = module + + try: + self.privatekey = crypto_utils.load_privatekey( + self.privatekey_path, self.privatekey_passphrase, backend=self.backend + ) + except crypto_utils.OpenSSLBadPassphraseError as exc: + module.fail_json(msg=to_native(exc)) + + if self.digest is None: + raise CertificateError( + 'The digest %s is not supported with the cryptography backend' % module.params['selfsigned_digest'] + ) + + def generate(self, module): + if not os.path.exists(self.privatekey_path): + raise CertificateError( + 'The private key %s does not exist' % self.privatekey_path + ) + if not os.path.exists(self.csr_path): + raise CertificateError( + 'The certificate signing request file %s does not exist' % self.csr_path + ) + if not self.check(module, perms_required=False) or self.force: + try: + cert_builder = x509.CertificateBuilder() + cert_builder = cert_builder.subject_name(self.csr.subject) + cert_builder = cert_builder.issuer_name(self.csr.subject) + cert_builder = cert_builder.serial_number(self.serial_number) + cert_builder = cert_builder.not_valid_before(self.notBefore) + cert_builder = cert_builder.not_valid_after(self.notAfter) + cert_builder = cert_builder.public_key(self.privatekey.public_key()) + for extension in self.csr.extensions: + cert_builder = cert_builder.add_extension(extension.value, critical=extension.critical) + except ValueError as e: + raise CertificateError(str(e)) + + certificate = cert_builder.sign( + private_key=self.privatekey, algorithm=self.digest, + backend=default_backend() + ) + + self.cert = certificate + + try: + with open(self.path, 'wb') as cert_file: + cert_file.write(certificate.public_bytes(Encoding.PEM)) + except Exception as exc: + raise CertificateError(exc) + + self.changed = True + else: + self.cert = crypto_utils.load_certificate(self.path, backend=self.backend) + + file_args = module.load_file_common_arguments(module.params) + if module.set_fs_attributes_if_different(file_args, False): + self.changed = True + + def dump(self, check_mode=False): + + result = { + 'changed': self.changed, + 'filename': self.path, + 'privatekey': self.privatekey_path, + 'csr': self.csr_path + } + + if check_mode: + result.update({ + 'notBefore': self.notBefore.strftime("%Y%m%d%H%M%SZ"), + 'notAfter': self.notAfter.strftime("%Y%m%d%H%M%SZ"), + 'serial_number': self.serial_number, + }) + else: + result.update({ + 'notBefore': self.cert.not_valid_before.strftime("%Y%m%d%H%M%SZ"), + 'notAfter': self.cert.not_valid_after.strftime("%Y%m%d%H%M%SZ"), + 'serial_number': self.cert.serial_number, + }) + + return result + + class SelfSignedCertificate(Certificate): """Generate the self-signed certificate.""" def __init__(self, module): - super(SelfSignedCertificate, self).__init__(module) + super(SelfSignedCertificate, self).__init__(module, 'pyopenssl') self.notBefore = self.get_relative_time_option(module.params['selfsigned_not_before'], 'selfsigned_not_before') self.notAfter = self.get_relative_time_option(module.params['selfsigned_not_after'], 'selfsigned_not_after') self.digest = module.params['selfsigned_digest'] @@ -694,11 +866,108 @@ class SelfSignedCertificate(Certificate): return result +class OwnCACertificateCryptography(Certificate): + """Generate the own CA certificate. Using the cryptography backend""" + def __init__(self, module): + super(OwnCACertificateCryptography, self).__init__(module, 'cryptography') + self.notBefore = self.get_relative_time_option(module.params['ownca_not_before'], 'ownca_not_before') + self.notAfter = self.get_relative_time_option(module.params['ownca_not_after'], 'ownca_not_after') + self.digest = crypto_utils.select_message_digest(module.params['ownca_digest']) + self.version = module.params['ownca_version'] + self.serial_number = x509.random_serial_number() + self.ca_cert_path = module.params['ownca_path'] + self.ca_privatekey_path = module.params['ownca_privatekey_path'] + self.ca_privatekey_passphrase = module.params['ownca_privatekey_passphrase'] + self.csr = crypto_utils.load_certificate_request(self.csr_path, backend=self.backend) + self.ca_cert = crypto_utils.load_certificate(self.ca_cert_path, backend=self.backend) + try: + self.ca_private_key = crypto_utils.load_privatekey( + self.ca_privatekey_path, self.ca_privatekey_passphrase, backend=self.backend + ) + except crypto_utils.OpenSSLBadPassphraseError as exc: + module.fail_json(msg=str(exc)) + + def generate(self, module): + + if not os.path.exists(self.ca_cert_path): + raise CertificateError( + 'The CA certificate %s does not exist' % self.ca_cert_path + ) + + if not os.path.exists(self.ca_privatekey_path): + raise CertificateError( + 'The CA private key %s does not exist' % self.ca_privatekey_path + ) + + if not os.path.exists(self.csr_path): + raise CertificateError( + 'The certificate signing request file %s does not exist' % self.csr_path + ) + + if not self.check(module, perms_required=False) or self.force: + cert_builder = x509.CertificateBuilder() + cert_builder = cert_builder.subject_name(self.csr.subject) + cert_builder = cert_builder.issuer_name(self.ca_cert.subject) + cert_builder = cert_builder.serial_number(self.serial_number) + cert_builder = cert_builder.not_valid_before(self.notBefore) + cert_builder = cert_builder.not_valid_after(self.notAfter) + cert_builder = cert_builder.public_key(self.csr.public_key()) + for extension in self.csr.extensions: + cert_builder = cert_builder.add_extension(extension.value, critical=extension.critical) + + certificate = cert_builder.sign( + private_key=self.ca_private_key, algorithm=self.digest, + backend=default_backend() + ) + + self.cert = certificate + + try: + with open(self.path, 'wb') as cert_file: + cert_file.write(certificate.public_bytes(Encoding.PEM)) + except Exception as exc: + raise CertificateError(exc) + + self.changed = True + else: + self.cert = crypto_utils.load_certificate(self.path, backend=self.backend) + + file_args = module.load_file_common_arguments(module.params) + if module.set_fs_attributes_if_different(file_args, False): + self.changed = True + + def dump(self, check_mode=False): + + result = { + 'changed': self.changed, + 'filename': self.path, + 'privatekey': self.privatekey_path, + 'csr': self.csr_path, + 'ca_cert': self.ca_cert_path, + 'ca_privatekey': self.ca_privatekey_path + } + + if check_mode: + result.update({ + 'notBefore': self.notBefore.strftime("%Y%m%d%H%M%SZ"), + 'notAfter': self.notAfter.strftime("%Y%m%d%H%M%SZ"), + 'serial_number': self.serial_number, + }) + else: + result.update({ + 'notBefore': self.cert.not_valid_before.strftime("%Y%m%d%H%M%SZ"), + 'notAfter': self.cert.not_valid_after.strftime("%Y%m%d%H%M%SZ"), + 'serial_number': self.cert.serial_number, + }) + + return result + + class OwnCACertificate(Certificate): """Generate the own CA certificate.""" def __init__(self, module): - super(OwnCACertificate, self).__init__(module) + super(OwnCACertificate, self).__init__(module, 'pyopenssl') self.notBefore = self.get_relative_time_option(module.params['ownca_not_before'], 'ownca_not_before') self.notAfter = self.get_relative_time_option(module.params['ownca_not_after'], 'ownca_not_after') self.digest = module.params['ownca_digest'] @@ -786,11 +1055,408 @@ class OwnCACertificate(Certificate): return result +class AssertOnlyCertificateCryptography(Certificate): + """Validate the supplied cert, using the cryptography backend""" + def __init__(self, module): + super(AssertOnlyCertificateCryptography, self).__init__(module, 'cryptography') + self.signature_algorithms = module.params['signature_algorithms'] + if module.params['subject']: + self.subject = crypto_utils.parse_name_field(module.params['subject']) + else: + self.subject = [] + self.subject_strict = module.params['subject_strict'] + if module.params['issuer']: + self.issuer = crypto_utils.parse_name_field(module.params['issuer']) + else: + self.issuer = [] + self.issuer_strict = module.params['issuer_strict'] + self.has_expired = module.params['has_expired'] + self.version = module.params['version'] + self.keyUsage = module.params['key_usage'] + self.keyUsage_strict = module.params['key_usage_strict'] + self.extendedKeyUsage = module.params['extended_key_usage'] + self.extendedKeyUsage_strict = module.params['extended_key_usage_strict'] + self.subjectAltName = module.params['subject_alt_name'] + self.subjectAltName_strict = module.params['subject_alt_name_strict'] + self.notBefore = module.params['not_before'], + self.notAfter = module.params['not_after'], + self.valid_at = module.params['valid_at'], + self.invalid_at = module.params['invalid_at'], + self.valid_in = module.params['valid_in'], + self.message = [] + + def _get_name_oid(self, id): + if id in ('CN', 'commonName'): + return cryptography.x509.oid.NameOID.COMMON_NAME + if id in ('C', 'countryName'): + return cryptography.x509.oid.NameOID.COUNTRY_NAME + if id in ('L', 'localityName'): + return cryptography.x509.oid.NameOID.LOCALITY_NAME + if id in ('ST', 'stateOrProvinceName'): + return cryptography.x509.oid.NameOID.STATE_OR_PROVINCE_NAME + if id in ('street', 'streetAddress'): + return cryptography.x509.oid.NameOID.STREET_ADDRESS + if id in ('O', 'organizationName'): + return cryptography.x509.oid.NameOID.ORGANIZATION_NAME + if id in ('OU', 'organizationalUnitName'): + return cryptography.x509.oid.NameOID.ORGANIZATIONAL_UNIT_NAME + if id in ('serialNumber', ): + return cryptography.x509.oid.NameOID.SERIAL_NUMBER + if id in ('SN', 'surname'): + return cryptography.x509.oid.NameOID.SURNAME + if id in ('GN', 'givenName'): + return cryptography.x509.oid.NameOID.GIVEN_NAME + if id in ('title', ): + return cryptography.x509.oid.NameOID.TITLE + if id in ('generationQualifier', ): + return cryptography.x509.oid.NameOID.GENERATION_QUALIFIER + if id in ('x500UniqueIdentifier', ): + return cryptography.x509.oid.NameOID.X500_UNIQUE_IDENTIFIER + if id in ('dnQualifier', ): + return cryptography.x509.oid.NameOID.DN_QUALIFIER + if id in ('pseudonym', ): + return cryptography.x509.oid.NameOID.PSEUDONYM + if id in ('UID', 'userId'): + return cryptography.x509.oid.NameOID.USER_ID + if id in ('DC', 'domainComponent'): + return cryptography.x509.oid.NameOID.DOMAIN_COMPONENT + if id in ('emailAddress', ): + return cryptography.x509.oid.NameOID.EMAIL_ADDRESS + if id in ('jurisdictionC', 'jurisdictionCountryName'): + return cryptography.x509.oid.NameOID.JURISDICTION_COUNTRY_NAME + if id in ('jurisdictionL', 'jurisdictionLocalityName'): + return cryptography.x509.oid.NameOID.JURISDICTION_LOCALITY_NAME + if id in ('jurisdictionST', 'jurisdictionStateOrProvinceName'): + return cryptography.x509.oid.NameOID.JURISDICTION_STATE_OR_PROVINCE_NAME + if id in ('businessCategory', ): + return cryptography.x509.oid.NameOID.BUSINESS_CATEGORY + if id in ('postalAddress', ): + return cryptography.x509.oid.NameOID.POSTAL_ADDRESS + if id in ('postalCode', ): + return cryptography.x509.oid.NameOID.POSTAL_CODE + + def _get_san(self, name): + if name.startswith('DNS:'): + return cryptography.x509.DNSName(to_native(name[4:])) + if name.startswith('IP:'): + return cryptography.x509.IPAddress(to_native(name[3:])) + if name.startswith('email:'): + return cryptography.x509.RFC822Name(to_native(name[6:])) + if name.startswith('URI:'): + return cryptography.x509.UniformResourceIdentifier(to_native(name[4:])) + if name.startswith('DirName:'): + return cryptography.x509.DirectoryName(to_native(name[8:])) + if ':' not in name: + raise CertificateError('Cannot parse Subject Alternative Name "{0}" (forgot "DNS:" prefix?)'.format(name)) + raise CertificateError('Cannot parse Subject Alternative Name "{0}" (potentially unsupported by cryptography backend)'.format(name)) + + def _get_keyusage(self, usage): + if usage in ('Digital Signature', 'digitalSignature'): + return 'digital_signature' + if usage in ('Non Repudiation', 'nonRepudiation'): + return 'content_commitment' + if usage in ('Key Encipherment', 'keyEncipherment'): + return 'key_encipherment' + if usage in ('Data Encipherment', 'dataEncipherment'): + return 'data_encipherment' + if usage in ('Key Agreement', 'keyAgreement'): + return 'key_agreement' + if usage in ('Certificate Sign', 'keyCertSign'): + return 'key_cert_sign' + if usage in ('CRL Sign', 'cRLSign'): + return 'crl_sign' + if usage in ('Encipher Only', 'encipherOnly'): + return 'encipher_only' + if usage in ('Decipher Only', 'decipherOnly'): + return 'decipher_only' + raise CertificateError('Unknown key usage "{0}"'.format(usage)) + + def _get_ext_keyusage(self, usage): + if usage in ('serverAuth', 'TLS Web Server Authentication'): + return cryptography.x509.oid.ExtendedKeyUsageOID.SERVER_AUTH + if usage in ('clientAuth', 'TLS Web Client Authentication'): + return cryptography.x509.oid.ExtendedKeyUsageOID.CLIENT_AUTH + if usage in ('codeSigning', 'Code Signing'): + return cryptography.x509.oid.ExtendedKeyUsageOID.CODE_SIGNING + if usage in ('emailProtection', 'E-mail Protection'): + return cryptography.x509.oid.ExtendedKeyUsageOID.EMAIL_PROTECTION + if usage in ('timeStamping', 'Time Stamping'): + return cryptography.x509.oid.ExtendedKeyUsageOID.TIME_STAMPING + if usage in ('OCSPSigning', 'OCSP Signing'): + return cryptography.x509.oid.ExtendedKeyUsageOID.OCSP_SIGNING + if usage in ('anyExtendedKeyUsage', 'Any Extended Key Usage'): + return cryptography.x509.oid.ExtendedKeyUsageOID.ANY_EXTENDED_KEY_USAGE + if usage in ('qcStatements', ): + return cryptography.x509.oid.ObjectIdentifier("1.3.6.1.5.5.7.1.3") + if usage in ('DVCS', ): + return cryptography.x509.oid.ObjectIdentifier("1.3.6.1.5.5.7.3.10") + if usage in ('IPSec User', 'ipsecUser'): + return cryptography.x509.oid.ObjectIdentifier("1.3.6.1.5.5.7.3.7") + if usage in ('Biometric Info', 'biometricInfo'): + return cryptography.x509.oid.ObjectIdentifier("1.3.6.1.5.5.7.1.2") + # FIXME need some more, probably all from https://www.iana.org/assignments/smi-numbers/smi-numbers.xhtml#smi-numbers-1.3.6.1.5.5.7.3 + raise CertificateError('Unknown extended key usage "{0}"'.format(usage)) + + def _get_basic_constraints(self, constraints): + ca = False + path_length = None + if constraints: + for constraint in constraints: + if constraint.startswith('CA:'): + if constraint == 'CA:TRUE': + ca = True + elif constraint == 'CA:FALSE': + ca = False + else: + raise CertificateError('Unknown basic constraint value "{0}" for CA'.format(constraint[3:])) + elif constraint.startswith('pathlen:'): + v = constraint[len('pathlen:'):] + try: + path_length = int(v) + except Exception as e: + raise CertificateError('Cannot parse path length constraint "{0}" ({1})'.format(v, e)) + else: + raise CertificateError('Unknown basic constraint "{0}"'.format(constraint)) + return ca, path_length + + def _parse_key_usage(self): + params = dict( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=False, + ) + for usage in self.keyUsage: + params[self._get_keyusage(usage)] = True + return params + + def assertonly(self): + self.cert = crypto_utils.load_certificate(self.path, backend=self.backend) + + def _validate_signature_algorithms(): + if self.signature_algorithms: + if self.cert.signature_algorithm_oid._name not in self.signature_algorithms: + self.message.append( + 'Invalid signature algorithm (got %s, expected one of %s)' % + (self.cert.signature_algorithm_oid._name, self.signature_algorithms) + ) + + def _validate_subject(): + if self.subject: + expected_subject = Name([NameAttribute(oid=self._get_name_oid(sub[0]), value=to_text(sub[1])) + for sub in self.subject]) + cert_subject = self.cert.subject + if (not self.subject_strict and not all(x in cert_subject for x in expected_subject)) or \ + (self.subject_strict and not set(expected_subject) == set(cert_subject)): + self.message.append( + 'Invalid subject component (got %s, expected all of %s to be present)' % + (cert_subject, expected_subject) + ) + + def _validate_issuer(): + if self.issuer: + expected_issuer = Name([NameAttribute(oid=self._get_name_oid(iss[0]), value=to_text(iss[1])) + for iss in self.issuer]) + cert_issuer = self.cert.issuer + if (not self.issuer_strict and not all(x in cert_issuer for x in expected_issuer)) or \ + (self.issuer_strict and not set(expected_issuer) == set(cert_issuer)): + self.message.append( + 'Invalid issuer component (got %s, expected all of %s to be present)' % (cert_issuer, self.issuer) + ) + + def _validate_has_expired(): + cert_not_after = self.cert.not_valid_after + cert_expired = cert_not_after < datetime.datetime.utcnow() + + if self.has_expired != cert_expired: + self.message.append( + 'Certificate expiration check failed (certificate expiration is %s, expected %s)' % (cert_expired, self.has_expired) + ) + + def _validate_version(): + # FIXME + if self.version: + expected_version = x509.Version(int(self.version) - 1) + if expected_version != self.cert.version: + self.message.append( + 'Invalid certificate version number (got %s, expected %s)' % (self.cert.version, self.version) + ) + + def _validate_keyUsage(): + if self.keyUsage: + try: + current_keyusage = self.cert.extensions.get_extension_for_class(x509.KeyUsage).value + expected_keyusage = x509.KeyUsage(**self._parse_key_usage()) + test_keyusage = dict( + digital_signature=current_keyusage.digital_signature, + content_commitment=current_keyusage.content_commitment, + key_encipherment=current_keyusage.key_encipherment, + data_encipherment=current_keyusage.data_encipherment, + key_agreement=current_keyusage.key_agreement, + key_cert_sign=current_keyusage.key_cert_sign, + crl_sign=current_keyusage.crl_sign, + ) + if test_keyusage['key_agreement']: + test_keyusage.update(dict( + encipher_only=current_keyusage.encipher_only, + decipher_only=current_keyusage.decipher_only + )) + else: + test_keyusage.update(dict( + encipher_only=False, + decipher_only=False + )) + + if (not self.keyUsage_strict and not all(self._parse_key_usage()[x] == test_keyusage[x] for x in self._parse_key_usage())) or \ + (self.keyUsage_strict and current_keyusage != expected_keyusage): + self.message.append( + 'Invalid keyUsage components (got %s, expected all of %s to be present)' % + ([x for x in test_keyusage if x is True], [x for x in self.keyUsage if x is True]) + ) + + except cryptography.x509.ExtensionNotFound: + self.message.append('Found no keyUsage extension') + + def _validate_extendedKeyUsage(): + if self.extendedKeyUsage: + try: + current_ext_keyusage = self.cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage).value + usages = [self._get_ext_keyusage(usage) for usage in self.extendedKeyUsage] + expected_ext_keyusage = x509.ExtendedKeyUsage(usages) + if (not self.extendedKeyUsage_strict and not all(x in expected_ext_keyusage for x in current_ext_keyusage)) or \ + (self.extendedKeyUsage_strict and not current_ext_keyusage == expected_ext_keyusage): + self.message.append( + 'Invalid extendedKeyUsage component (got %s, expected all of %s to be present)' % ([xku.value for xku in current_ext_keyusage], + [exku.value for exku in expected_ext_keyusage]) + ) + + except cryptography.x509.ExtensionNotFound: + self.message.append('Found no extendedKeyUsage extension') + + def _validate_subjectAltName(): + if self.subjectAltName: + try: + current_san = self.cert.extensions.get_extension_for_class(x509.SubjectAlternativeName).value + expected_san = [self._get_san(san) for san in self.subjectAltName] + if (not self.subjectAltName_strict and not all(x in current_san for x in expected_san)) or \ + (self.subjectAltName_strict and not set(current_san) == set(expected_san)): + self.message.append( + 'Invalid subjectAltName component (got %s, expected all of %s to be present)' % + (current_san, self.subjectAltName) + ) + except cryptography.x509.ExtensionNotFound: + self.message.append('Found no subjectAltName extension') + + def _validate_notBefore(): + if self.notBefore[0]: + # try: + if self.cert.not_valid_before != self.get_relative_time_option(self.notBefore[0], 'not_before'): + self.message.append( + 'Invalid notBefore component (got %s, expected %s to be present)' % (self.cert.not_valid_before, self.notBefore) + ) + # except AttributeError: + # self.message.append(str(self.notBefore)) + + def _validate_notAfter(): + if self.notAfter[0]: + if self.cert.not_valid_after != self.get_relative_time_option(self.notAfter[0], 'not_after'): + self.message.append( + 'Invalid notAfter component (got %s, expected %s to be present)' % (self.cert.not_valid_after, self.notAfter) + ) + + def _validate_valid_at(): + if self.valid_at[0]: + rt = self.get_relative_time_option(self.valid_at[0], 'valid_at') + if not (self.cert.not_valid_before <= rt <= self.cert.not_valid_after): + self.message.append( + 'Certificate is not valid for the specified date (%s) - notBefore: %s - notAfter: %s' % (self.valid_at, + self.cert.not_valid_before, + self.cert.not_valid_after) + ) + + def _validate_invalid_at(): + if self.invalid_at[0]: + if (self.get_relative_time_option(self.invalid_at[0], 'invalid_at') > self.cert.not_valid_before) \ + or (self.get_relative_time_option(self.invalid_at, 'invalid_at') >= self.cert.not_valid_after): + self.message.append( + 'Certificate is not invalid for the specified date (%s) - notBefore: %s - notAfter: %s' % (self.invalid_at, + self.cert.not_valid_before, + self.cert.not_valid_after) + ) + + def _validate_valid_in(): + if self.valid_in[0]: + if not self.valid_in[0].startswith("+") and not self.valid_in[0].startswith("-"): + try: + int(self.valid_in[0]) + except ValueError: + raise CertificateError( + 'The supplied value for "valid_in" (%s) is not an integer or a valid timespec' % self.valid_in) + self.valid_in = "+" + self.valid_in + "s" + valid_in_date = self.get_relative_time_option(self.valid_in[0], "valid_in") + if not self.cert.not_valid_before <= valid_in_date <= self.cert.not_valid_after: + self.message.append( + 'Certificate is not valid in %s from now (that would be %s) - notBefore: %s - notAfter: %s' + % (self.valid_in, valid_in_date, + self.cert.not_valid_before, + self.cert.not_valid_after)) + + for validation in ['signature_algorithms', 'subject', 'issuer', + 'has_expired', 'version', 'keyUsage', + 'extendedKeyUsage', 'subjectAltName', + 'notBefore', 'notAfter', 'valid_at', 'valid_in', 'invalid_at']: + f_name = locals()['_validate_%s' % validation] + f_name() + + def generate(self, module): + """Don't generate anything - only assert""" + + self.assertonly() + + try: + if self.privatekey_path and \ + not super(AssertOnlyCertificateCryptography, self).check(module, perms_required=False): + self.message.append( + 'Certificate %s and private key %s do not match' % (self.path, self.privatekey_path) + ) + except CertificateError as e: + self.message.append( + 'Error while reading private key %s: %s' % (self.privatekey_path, str(e)) + ) + + if len(self.message): + module.fail_json(msg=' | '.join(self.message)) + + def check(self, module, perms_required=False): + """Ensure the resource is in its desired state.""" + + parent_check = super(AssertOnlyCertificateCryptography, self).check(module, perms_required) + self.assertonly() + assertonly_check = not len(self.message) + self.message = [] + + return parent_check and assertonly_check + + def dump(self, check_mode=False): + result = { + 'changed': self.changed, + 'filename': self.path, + 'privatekey': self.privatekey_path, + 'csr': self.csr_path, + } + return result + + class AssertOnlyCertificate(Certificate): """validate the supplied certificate.""" def __init__(self, module): - super(AssertOnlyCertificate, self).__init__(module) + super(AssertOnlyCertificate, self).__init__(module, 'pyopenssl') self.signature_algorithms = module.params['signature_algorithms'] if module.params['subject']: self.subject = crypto_utils.parse_name_field(module.params['subject']) @@ -991,8 +1657,7 @@ class AssertOnlyCertificate(Certificate): self.valid_in = "+" + self.valid_in + "s" valid_in_asn1 = self.get_relative_time_option(self.valid_in, "valid_in") valid_in_date = to_bytes(valid_in_asn1, errors='surrogate_or_strict') - if not (self.cert.get_notBefore() <= valid_in_date <= - self.cert.get_notAfter()): + if not (self.cert.get_notBefore() <= valid_in_date <= self.cert.get_notAfter()): self.message.append( 'Certificate is not valid in %s from now (that would be %s) - notBefore: %s - notAfter: %s' % (self.valid_in, valid_in_date, @@ -1051,8 +1716,11 @@ class AssertOnlyCertificate(Certificate): class AcmeCertificate(Certificate): """Retrieve a certificate using the ACME protocol.""" - def __init__(self, module): - super(AcmeCertificate, self).__init__(module) + # Since there's no real use of the backend, + # other than the 'self.check' function, we just pass the backend to the constructor + + def __init__(self, module, backend): + super(AcmeCertificate, self).__init__(module, backend) self.accountkey_path = module.params['acme_accountkey_path'] self.challenge_path = module.params['acme_challenge_path'] self.use_chain = module.params['acme_chain'] @@ -1123,6 +1791,7 @@ def main(): provider=dict(type='str', choices=['acme', 'assertonly', 'ownca', 'selfsigned']), force=dict(type='bool', default=False,), csr_path=dict(type='path'), + select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'pyopenssl']), # General properties of a certificate privatekey_path=dict(type='path'), @@ -1172,14 +1841,6 @@ def main(): add_file_common_args=True, ) - if not pyopenssl_found: - module.fail_json(msg=missing_required_lib('pyOpenSSL'), exception=PYOPENSSL_IMP_ERR) - if module.params['provider'] in ['selfsigned', 'ownca', 'assertonly']: - try: - getattr(crypto.X509Req, 'get_extensions') - except AttributeError: - module.fail_json(msg='You need to have PyOpenSSL>=0.15') - if module.params['provider'] != 'assertonly' and module.params['csr_path'] is None: module.fail_json(msg='csr_path is required when provider is not assertonly') @@ -1192,14 +1853,60 @@ def main(): provider = module.params['provider'] - if provider == 'selfsigned': - certificate = SelfSignedCertificate(module) - elif provider == 'acme': - certificate = AcmeCertificate(module) - elif provider == 'ownca': - certificate = OwnCACertificate(module) - else: - certificate = AssertOnlyCertificate(module) + backend = module.params['select_crypto_backend'] + if backend == 'auto': + # Detect what backend we can use + can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION) + can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION) + + # If cryptography is available we'll use it + if can_use_cryptography: + backend = 'cryptography' + elif can_use_pyopenssl: + backend = 'pyopenssl' + + if module.params['selfsigned_version'] == 2 or module.params['ownca_version'] == 2: + module.warn('crypto backend forced to pyopenssl. The cryptography library does not support v2 certificates') + backend = 'pyopenssl' + + # Fail if no backend has been found + if backend == 'auto': + module.fail_json(msg=("Can't detect none of the required Python libraries " + "cryptography (>= {0}) or PyOpenSSL (>= {1})").format( + MINIMAL_CRYPTOGRAPHY_VERSION, + MINIMAL_PYOPENSSL_VERSION)) + + if backend == 'pyopenssl': + if not PYOPENSSL_FOUND: + module.fail_json(msg=missing_required_lib('pyOpenSSL'), exception=PYOPENSSL_IMP_ERR) + if module.params['provider'] in ['selfsigned', 'ownca', 'assertonly']: + try: + getattr(crypto.X509Req, 'get_extensions') + except AttributeError: + module.fail_json(msg='You need to have PyOpenSSL>=0.15') + + if provider == 'selfsigned': + certificate = SelfSignedCertificate(module) + elif provider == 'acme': + certificate = AcmeCertificate(module, 'pyopenssl') + elif provider == 'ownca': + certificate = OwnCACertificate(module) + else: + certificate = AssertOnlyCertificate(module) + elif backend == 'cryptography': + if not CRYPTOGRAPHY_FOUND: + module.fail_json(msg=missing_required_lib('cryptography'), exception=CRYPTOGRAPHY_IMP_ERR) + if module.params['selfsigned_version'] == 2 or module.params['ownca_version'] == 2: + module.fail_json(msg='The cryptography backend does not support v2 certificates, ' + 'use select_crypto_backend=pyopenssl for v2 certificates') + if provider == 'selfsigned': + certificate = SelfSignedCertificateCryptography(module) + elif provider == 'acme': + certificate = AcmeCertificate(module, 'cryptography') + elif provider == 'ownca': + certificate = OwnCACertificateCryptography(module) + else: + certificate = AssertOnlyCertificateCryptography(module) if module.params['state'] == 'present': diff --git a/test/integration/targets/openssl_certificate/tasks/assertonly.yml b/test/integration/targets/openssl_certificate/tasks/assertonly.yml index cc1f955a84..4209c9d179 100644 --- a/test/integration/targets/openssl_certificate/tasks/assertonly.yml +++ b/test/integration/targets/openssl_certificate/tasks/assertonly.yml @@ -1,16 +1,16 @@ --- -- name: Generate privatekey +- name: (Assertonly, {{select_crypto_backend}}) - Generate privatekey openssl_privatekey: path: '{{ output_dir }}/privatekey.pem' -- name: Generate privatekey with password +- name: (Assertonly, {{select_crypto_backend}}) - Generate privatekey with password openssl_privatekey: path: '{{ output_dir }}/privatekeypw.pem' passphrase: hunter2 cipher: auto select_crypto_backend: cryptography -- name: Generate CSR (no extensions) +- name: (Assertonly, {{select_crypto_backend}}) - Generate CSR (no extensions) openssl_csr: path: '{{ output_dir }}/csr_noext.csr' privatekey_path: '{{ output_dir }}/privatekey.pem' @@ -18,38 +18,42 @@ commonName: www.example.com useCommonNameForSAN: no -- name: Generate selfsigned certificate (no extensions) +- name: (Assertonly, {{select_crypto_backend}}) - Generate selfsigned certificate (no extensions) openssl_certificate: path: '{{ output_dir }}/cert_noext.pem' csr_path: '{{ output_dir }}/csr_noext.csr' privatekey_path: '{{ output_dir }}/privatekey.pem' provider: selfsigned selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' -- name: Assert that subject_alt_name is there (should fail) +- name: (Assertonly, {{select_crypto_backend}}) - Assert that subject_alt_name is there (should fail) openssl_certificate: path: '{{ output_dir }}/cert_noext.pem' provider: assertonly subject_alt_name: - "DNS:example.com" + select_crypto_backend: '{{ select_crypto_backend }}' ignore_errors: yes register: extension_missing_san -- name: Assert that key_usage is there (should fail) +- name: (Assertonly, {{select_crypto_backend}}) - Assert that key_usage is there (should fail) openssl_certificate: path: '{{ output_dir }}/cert_noext.pem' provider: assertonly key_usage: - digitalSignature + select_crypto_backend: '{{ select_crypto_backend }}' ignore_errors: yes register: extension_missing_ku -- name: Assert that extended_key_usage is there (should fail) +- name: (Assertonly, {{select_crypto_backend}}) - Assert that extended_key_usage is there (should fail) openssl_certificate: path: '{{ output_dir }}/cert_noext.pem' provider: assertonly extended_key_usage: - biometricInfo + select_crypto_backend: '{{ select_crypto_backend }}' ignore_errors: yes register: extension_missing_eku @@ -62,33 +66,36 @@ - extension_missing_eku is failed - "'Found no extendedKeyUsage extension' in extension_missing_eku.msg" -- name: Check private key passphrase fail 1 +- name: (Assertonly, {{select_crypto_backend}}) - Check private key passphrase fail 1 openssl_certificate: path: '{{ output_dir }}/cert_noext.pem' privatekey_path: '{{ output_dir }}/privatekey.pem' privatekey_passphrase: hunter2 provider: assertonly + select_crypto_backend: '{{ select_crypto_backend }}' ignore_errors: yes register: passphrase_error_1 -- name: Check private key passphrase fail 2 +- name: (Assertonly, {{select_crypto_backend}}) - Check private key passphrase fail 2 openssl_certificate: path: '{{ output_dir }}/cert_noext.pem' privatekey_path: '{{ output_dir }}/privatekeypw.pem' privatekey_passphrase: wrong_password provider: assertonly + select_crypto_backend: '{{ select_crypto_backend }}' ignore_errors: yes register: passphrase_error_2 -- name: Check private key passphrase fail 3 +- name: (Assertonly, {{select_crypto_backend}}) - Check private key passphrase fail 3 openssl_certificate: path: '{{ output_dir }}/cert_noext.pem' privatekey_path: '{{ output_dir }}/privatekeypw.pem' provider: assertonly + select_crypto_backend: '{{ select_crypto_backend }}' ignore_errors: yes register: passphrase_error_3 -- name: +- name: (Assertonly, {{select_crypto_backend}}) - assert: that: - passphrase_error_1 is failed diff --git a/test/integration/targets/openssl_certificate/tasks/expired.yml b/test/integration/targets/openssl_certificate/tasks/expired.yml index d1a862812c..248ce4a239 100644 --- a/test/integration/targets/openssl_certificate/tasks/expired.yml +++ b/test/integration/targets/openssl_certificate/tasks/expired.yml @@ -1,16 +1,16 @@ --- -- name: Generate privatekey +- name: (Expired, {{select_crypto_backend}}) Generate privatekey openssl_privatekey: path: '{{ output_dir }}/has_expired_privatekey.pem' -- name: Generate CSR +- name: (Expired, {{select_crypto_backend}}) Generate CSR openssl_csr: path: '{{ output_dir }}/has_expired_csr.csr' privatekey_path: '{{ output_dir }}/has_expired_privatekey.pem' subject: commonName: www.example.com -- name: Generate expired selfsigned certificate +- name: (Expired, {{select_crypto_backend}}) Generate expired selfsigned certificate openssl_certificate: path: '{{ output_dir }}/has_expired_cert.pem' csr_path: '{{ output_dir }}/has_expired_csr.csr' @@ -18,22 +18,31 @@ provider: selfsigned selfsigned_digest: sha256 selfsigned_not_after: "-1s" + selfsigned_not_before: "-100s" + select_crypto_backend: '{{ select_crypto_backend }}' + when: select_crypto_backend == 'pyopenssl' # cryptography won't allow creating expired certificates -- name: "Check task fails because cert is expired (has_expired: false)" +- name: (Expired, {{select_crypto_backend}}) Generate expired selfsigned certificate + command: "openssl x509 -req -days -1 -in {{ output_dir }}/has_expired_csr.csr -signkey {{ output_dir }}/has_expired_privatekey.pem -out {{ output_dir }}/has_expired_cert.pem" + when: select_crypto_backend == 'cryptography' # So we create it with 'command' + +- name: "(Expired) Check task fails because cert is expired (has_expired: false)" openssl_certificate: provider: assertonly path: "{{ output_dir }}/has_expired_cert.pem" has_expired: false + select_crypto_backend: '{{ select_crypto_backend }}' ignore_errors: true register: expired_cert_check -- name: Ensure previous task failed +- name: (Expired, {{select_crypto_backend}}) Ensure previous task failed assert: that: expired_cert_check is failed -- name: "Check expired cert check is ignored (has_expired: true)" +- name: "(Expired) Check expired cert check is ignored (has_expired: true)" openssl_certificate: provider: assertonly path: "{{ output_dir }}/has_expired_cert.pem" has_expired: true + select_crypto_backend: '{{ select_crypto_backend }}' register: expired_cert_skip diff --git a/test/integration/targets/openssl_certificate/tasks/impl.yml b/test/integration/targets/openssl_certificate/tasks/impl.yml new file mode 100644 index 0000000000..aab83cbf08 --- /dev/null +++ b/test/integration/targets/openssl_certificate/tasks/impl.yml @@ -0,0 +1,7 @@ +--- +- debug: + msg: "Executing tests with backend {{ select_crypto_backend }}" +- import_tasks: assertonly.yml +- import_tasks: expired.yml +- import_tasks: selfsigned.yml +- import_tasks: ownca.yml diff --git a/test/integration/targets/openssl_certificate/tasks/main.yml b/test/integration/targets/openssl_certificate/tasks/main.yml index f8d6424cc9..f8c1c5fefb 100644 --- a/test/integration/targets/openssl_certificate/tasks/main.yml +++ b/test/integration/targets/openssl_certificate/tasks/main.yml @@ -1,12 +1,22 @@ --- -- block: - - - import_tasks: assertonly.yml - - - import_tasks: expired.yml - - - import_tasks: selfsigned.yml - - - import_tasks: ownca.yml - +- name: Running tests with pyOpenSSL backend + include_tasks: impl.yml + vars: + select_crypto_backend: pyopenssl when: pyopenssl_version.stdout is version('0.15', '>=') + +- name: Remove output directory + file: + path: "{{ output_dir }}" + state: absent + +- name: Re-create output directory + file: + path: "{{ output_dir }}" + state: directory + +- name: Running tests with cryptography backend + include_tasks: impl.yml + vars: + select_crypto_backend: cryptography + when: cryptography_version.stdout is version('1.6', '>=') diff --git a/test/integration/targets/openssl_certificate/tasks/ownca.yml b/test/integration/targets/openssl_certificate/tasks/ownca.yml index 6b9abcc007..06d33c0a14 100644 --- a/test/integration/targets/openssl_certificate/tasks/ownca.yml +++ b/test/integration/targets/openssl_certificate/tasks/ownca.yml @@ -1,9 +1,9 @@ --- -- name: Generate CA privatekey +- name: (OwnCA, {{select_crypto_backend}}) Generate CA privatekey openssl_privatekey: path: '{{ output_dir }}/ca_privatekey.pem' -- name: Generate CA CSR +- name: (OwnCA, {{select_crypto_backend}}) Generate CA CSR openssl_csr: path: '{{ output_dir }}/ca_csr.csr' privatekey_path: '{{ output_dir }}/ca_privatekey.pem' @@ -14,15 +14,16 @@ - 'CA:TRUE' basic_constraints_critical: yes -- name: Generate selfsigned CA certificate +- name: (OwnCA, {{select_crypto_backend}}) Generate selfsigned CA certificate openssl_certificate: path: '{{ output_dir }}/ca_cert.pem' csr_path: '{{ output_dir }}/ca_csr.csr' privatekey_path: '{{ output_dir }}/ca_privatekey.pem' provider: selfsigned selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' -- name: Generate ownca certificate +- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate openssl_certificate: path: '{{ output_dir }}/ownca_cert.pem' csr_path: '{{ output_dir }}/csr.csr' @@ -31,9 +32,10 @@ ownca_privatekey_path: '{{ output_dir }}/ca_privatekey.pem' provider: ownca ownca_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' register: ownca_certificate -- name: Generate ownca certificate +- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate openssl_certificate: path: '{{ output_dir }}/ownca_cert.pem' csr_path: '{{ output_dir }}/csr.csr' @@ -42,9 +44,10 @@ ownca_privatekey_path: '{{ output_dir }}/ca_privatekey.pem' provider: ownca ownca_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' register: ownca_certificate_idempotence -- name: Generate ownca certificate (check mode) +- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate (check mode) openssl_certificate: path: '{{ output_dir }}/ownca_cert.pem' csr_path: '{{ output_dir }}/csr.csr' @@ -53,9 +56,10 @@ ownca_privatekey_path: '{{ output_dir }}/ca_privatekey.pem' provider: ownca ownca_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' check_mode: yes -- name: Check ownca certificate +- name: (OwnCA, {{select_crypto_backend}}) Check ownca certificate openssl_certificate: path: '{{ output_dir }}/ownca_cert.pem' privatekey_path: '{{ output_dir }}/privatekey.pem' @@ -69,8 +73,9 @@ commonName: www.example.com issuer: commonName: Example CA + select_crypto_backend: '{{ select_crypto_backend }}' -- name: Generate ownca v2 certificate +- name: (OwnCA, {{select_crypto_backend}}) Generate ownca v2 certificate openssl_certificate: path: '{{ output_dir }}/ownca_cert_v2.pem' csr_path: '{{ output_dir }}/csr.csr' @@ -80,8 +85,11 @@ provider: ownca ownca_digest: sha256 ownca_version: 2 + select_crypto_backend: '{{ select_crypto_backend }}' + register: ownca_v2_certificate + ignore_errors: true -- name: Generate ownca certificate2 +- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate2 openssl_certificate: path: '{{ output_dir }}/ownca_cert2.pem' csr_path: '{{ output_dir }}/csr2.csr' @@ -90,8 +98,9 @@ ownca_privatekey_path: '{{ output_dir }}/ca_privatekey.pem' provider: ownca ownca_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' -- name: Check ownca certificate2 +- name: (OwnCA, {{select_crypto_backend}}) Check ownca certificate2 openssl_certificate: path: '{{ output_dir }}/ownca_cert2.pem' privatekey_path: '{{ output_dir }}/privatekey2.pem' @@ -117,8 +126,9 @@ - biometricInfo issuer: commonName: Example CA + select_crypto_backend: '{{ select_crypto_backend }}' -- name: Create ownca certificate with notBefore and notAfter +- name: (OwnCA, {{select_crypto_backend}}) Create ownca certificate with notBefore and notAfter openssl_certificate: provider: ownca ownca_not_before: 20181023133742Z @@ -128,8 +138,9 @@ privatekey_path: "{{ output_dir }}/privatekey3.pem" ownca_path: '{{ output_dir }}/ca_cert.pem' ownca_privatekey_path: '{{ output_dir }}/ca_privatekey.pem' + select_crypto_backend: '{{ select_crypto_backend }}' -- name: Create ownca certificate with relative notBefore and notAfter +- name: (OwnCA, {{select_crypto_backend}}) Create ownca certificate with relative notBefore and notAfter openssl_certificate: provider: ownca ownca_not_before: +1s @@ -139,8 +150,9 @@ privatekey_path: "{{ output_dir }}/privatekey3.pem" ownca_path: '{{ output_dir }}/ca_cert.pem' ownca_privatekey_path: '{{ output_dir }}/ca_privatekey.pem' + select_crypto_backend: '{{ select_crypto_backend }}' -- name: Generate ownca ECC certificate +- name: (OwnCA, {{select_crypto_backend}}) Generate ownca ECC certificate openssl_certificate: path: '{{ output_dir }}/ownca_cert_ecc.pem' csr_path: '{{ output_dir }}/csr_ecc.csr' @@ -149,9 +161,10 @@ ownca_privatekey_path: '{{ output_dir }}/ca_privatekey.pem' provider: ownca ownca_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' register: ownca_certificate_ecc -- name: Generate ownca certificate (failed passphrase 1) +- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate (failed passphrase 1) openssl_certificate: path: '{{ output_dir }}/ownca_cert_pw1.pem' csr_path: '{{ output_dir }}/csr_ecc.csr' @@ -160,10 +173,11 @@ ownca_privatekey_passphrase: hunter2 provider: ownca ownca_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' ignore_errors: yes register: passphrase_error_1 -- name: Generate ownca certificate (failed passphrase 2) +- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate (failed passphrase 2) openssl_certificate: path: '{{ output_dir }}/ownca_cert_pw1.pem' csr_path: '{{ output_dir }}/csr_ecc.csr' @@ -172,10 +186,11 @@ ownca_privatekey_passphrase: wrong_password provider: ownca ownca_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' ignore_errors: yes register: passphrase_error_2 -- name: Generate ownca certificate (failed passphrase 3) +- name: (OwnCA, {{select_crypto_backend}}) Generate ownca certificate (failed passphrase 3) openssl_certificate: path: '{{ output_dir }}/ownca_cert_pw3.pem' csr_path: '{{ output_dir }}/csr_ecc.csr' @@ -183,6 +198,7 @@ ownca_privatekey_path: '{{ output_dir }}/privatekeypw.pem' provider: ownca ownca_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' ignore_errors: yes register: passphrase_error_3 diff --git a/test/integration/targets/openssl_certificate/tasks/selfsigned.yml b/test/integration/targets/openssl_certificate/tasks/selfsigned.yml index f068e02778..ad09eef59f 100644 --- a/test/integration/targets/openssl_certificate/tasks/selfsigned.yml +++ b/test/integration/targets/openssl_certificate/tasks/selfsigned.yml @@ -1,50 +1,53 @@ --- -- name: Generate privatekey +- name: (Selfsigned, {{select_crypto_backend}}) Generate privatekey openssl_privatekey: path: '{{ output_dir }}/privatekey.pem' -- name: Generate privatekey with password +- name: (Selfsigned, {{select_crypto_backend}}) Generate privatekey with password openssl_privatekey: path: '{{ output_dir }}/privatekeypw.pem' passphrase: hunter2 cipher: auto select_crypto_backend: cryptography -- name: Generate CSR +- name: (Selfsigned, {{select_crypto_backend}}) Generate CSR openssl_csr: path: '{{ output_dir }}/csr.csr' privatekey_path: '{{ output_dir }}/privatekey.pem' subject: commonName: www.example.com -- name: Generate selfsigned certificate +- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate openssl_certificate: path: '{{ output_dir }}/cert.pem' csr_path: '{{ output_dir }}/csr.csr' privatekey_path: '{{ output_dir }}/privatekey.pem' provider: selfsigned selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' register: selfsigned_certificate -- name: Generate selfsigned certificate +- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate - idempotency openssl_certificate: path: '{{ output_dir }}/cert.pem' csr_path: '{{ output_dir }}/csr.csr' privatekey_path: '{{ output_dir }}/privatekey.pem' provider: selfsigned selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' register: selfsigned_certificate_idempotence -- name: Generate selfsigned certificate (check mode) +- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate (check mode) openssl_certificate: path: '{{ output_dir }}/cert.pem' csr_path: '{{ output_dir }}/csr.csr' privatekey_path: '{{ output_dir }}/privatekey.pem' provider: selfsigned selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' check_mode: yes -- name: Check selfsigned certificate +- name: (Selfsigned, {{select_crypto_backend}}) Check selfsigned certificate openssl_certificate: path: '{{ output_dir }}/cert.pem' privatekey_path: '{{ output_dir }}/privatekey.pem' @@ -56,8 +59,9 @@ - sha256WithECDSAEncryption subject: commonName: www.example.com + select_crypto_backend: '{{ select_crypto_backend }}' -- name: Generate selfsigned v2 certificate +- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned v2 certificate openssl_certificate: path: '{{ output_dir }}/cert_v2.pem' csr_path: '{{ output_dir }}/csr.csr' @@ -65,12 +69,15 @@ provider: selfsigned selfsigned_digest: sha256 selfsigned_version: 2 + select_crypto_backend: "{{ select_crypto_backend }}" + register: selfsigned_v2_cert + ignore_errors: true -- name: Generate privatekey2 +- name: (Selfsigned, {{select_crypto_backend}}) Generate privatekey2 openssl_privatekey: path: '{{ output_dir }}/privatekey2.pem' -- name: Generate CSR2 +- name: (Selfsigned, {{select_crypto_backend}}) Generate CSR2 openssl_csr: subject: CN: www.example.com @@ -89,15 +96,16 @@ - ipsecUser - biometricInfo -- name: Generate selfsigned certificate2 +- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate2 openssl_certificate: path: '{{ output_dir }}/cert2.pem' csr_path: '{{ output_dir }}/csr2.csr' privatekey_path: '{{ output_dir }}/privatekey2.pem' provider: selfsigned selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' -- name: Check selfsigned certificate2 +- name: (Selfsigned, {{select_crypto_backend}}) Check selfsigned certificate2 openssl_certificate: path: '{{ output_dir }}/cert2.pem' privatekey_path: '{{ output_dir }}/privatekey2.pem' @@ -121,19 +129,20 @@ extendedKeyUsage: - ipsecUser - biometricInfo + select_crypto_backend: '{{ select_crypto_backend }}' -- name: Create private key 3 +- name: (Selfsigned, {{select_crypto_backend}}) Create private key 3 openssl_privatekey: path: "{{ output_dir }}/privatekey3.pem" -- name: Create CSR 3 +- name: (Selfsigned, {{select_crypto_backend}}) Create CSR 3 openssl_csr: subject: CN: www.example.com privatekey_path: "{{ output_dir }}/privatekey3.pem" path: "{{ output_dir }}/csr3.pem" - -- name: Create certificate3 with notBefore and notAfter + +- name: (Selfsigned, {{select_crypto_backend}}) Create certificate3 with notBefore and notAfter openssl_certificate: provider: selfsigned selfsigned_not_before: 20181023133742Z @@ -141,30 +150,33 @@ path: "{{ output_dir }}/cert3.pem" csr_path: "{{ output_dir }}/csr3.pem" privatekey_path: "{{ output_dir }}/privatekey3.pem" + select_crypto_backend: '{{ select_crypto_backend }}' -- name: Generate privatekey +- name: (Selfsigned, {{select_crypto_backend}}) Generate privatekey openssl_privatekey: path: '{{ output_dir }}/privatekey_ecc.pem' type: ECC - curve: secp256k1 + curve: "{{ (ansible_distribution == 'CentOS' and ansible_distribution_major_version == '6') | ternary('secp521r1', 'secp256k1') }}" + # ^ cryptography on CentOS6 doesn't support secp256k1, so we use secp521r1 instead -- name: Generate CSR +- name: (Selfsigned, {{select_crypto_backend}}) Generate CSR openssl_csr: path: '{{ output_dir }}/csr_ecc.csr' privatekey_path: '{{ output_dir }}/privatekey_ecc.pem' subject: commonName: www.example.com -- name: Generate selfsigned certificate +- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate openssl_certificate: path: '{{ output_dir }}/cert_ecc.pem' csr_path: '{{ output_dir }}/csr_ecc.csr' privatekey_path: '{{ output_dir }}/privatekey_ecc.pem' provider: selfsigned selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' register: selfsigned_certificate_ecc -- name: Generate selfsigned certificate (failed passphrase 1) +- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate (failed passphrase 1) openssl_certificate: path: '{{ output_dir }}/cert_pw1.pem' csr_path: '{{ output_dir }}/csr_ecc.csr' @@ -172,10 +184,11 @@ privatekey_passphrase: hunter2 provider: selfsigned selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' ignore_errors: yes register: passphrase_error_1 -- name: Generate selfsigned certificate (failed passphrase 2) +- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate (failed passphrase 2) openssl_certificate: path: '{{ output_dir }}/cert_pw2.pem' csr_path: '{{ output_dir }}/csr_ecc.csr' @@ -183,16 +196,18 @@ privatekey_passphrase: wrong_password provider: selfsigned selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' ignore_errors: yes register: passphrase_error_2 -- name: Generate selfsigned certificate (failed passphrase 3) +- name: (Selfsigned, {{select_crypto_backend}}) Generate selfsigned certificate (failed passphrase 3) openssl_certificate: path: '{{ output_dir }}/cert_pw3.pem' csr_path: '{{ output_dir }}/csr_ecc.csr' privatekey_path: '{{ output_dir }}/privatekeypw.pem' provider: selfsigned selfsigned_digest: sha256 + select_crypto_backend: '{{ select_crypto_backend }}' ignore_errors: yes register: passphrase_error_3 diff --git a/test/integration/targets/openssl_certificate/tests/validate_ownca.yml b/test/integration/targets/openssl_certificate/tests/validate_ownca.yml index b22d57de23..c77aa2fd11 100644 --- a/test/integration/targets/openssl_certificate/tests/validate_ownca.yml +++ b/test/integration/targets/openssl_certificate/tests/validate_ownca.yml @@ -1,21 +1,21 @@ --- -- name: Validate ownca certificate (test - verify CA) +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate (test - verify CA) shell: 'openssl verify -CAfile {{ output_dir }}/ca_cert.pem {{ output_dir }}/ownca_cert.pem | sed "s/.*: \(.*\)/\1/g"' register: ownca_verify_ca -- name: Validate ownca certificate (test - ownca certificate modulus) +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate (test - ownca certificate modulus) shell: 'openssl x509 -noout -modulus -in {{ output_dir }}/ownca_cert.pem' register: ownca_cert_modulus -- name: Validate ownca certificate (test - ownca issuer value) +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate (test - ownca issuer value) shell: 'openssl x509 -noout -in {{ output_dir}}/ownca_cert.pem -text | grep "Issuer" | sed "s/.*: \(.*\)/\1/g"' register: ownca_cert_issuer -- name: Validate ownca certificate (test - ownca certficate version == default == 3) +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate (test - ownca certficate version == default == 3) shell: 'openssl x509 -noout -in {{ output_dir}}/ownca_cert.pem -text | grep "Version" | sed "s/.*: \(.*\) .*/\1/g"' register: ownca_cert_version -- name: Validate ownca certificate (assert) +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate (assert) assert: that: - ownca_verify_ca.stdout == 'OK' @@ -24,65 +24,75 @@ # openssl 1.1.x adds a space between the output - ownca_cert_issuer.stdout in ['CN=Example CA', 'CN = Example CA'] -- name: Validate ownca certificate idempotence +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate idempotence assert: that: - ownca_certificate.serial_number == ownca_certificate_idempotence.serial_number - ownca_certificate.notBefore == ownca_certificate_idempotence.notBefore - ownca_certificate.notAfter == ownca_certificate_idempotence.notAfter -- name: Validate ownca certificate v2 (test - ownca certificate version == 2) - shell: 'openssl x509 -noout -in {{ output_dir}}/ownca_cert_v2.pem -text | grep "Version" | sed "s/.*: \(.*\) .*/\1/g"' - register: ownca_cert_v2_version +- block: + - name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate v2 (test - ownca certificate version == 2) + shell: 'openssl x509 -noout -in {{ output_dir}}/ownca_cert_v2.pem -text | grep "Version" | sed "s/.*: \(.*\) .*/\1/g"' + register: ownca_cert_v2_version -- name: Validate ownca certificate version 2 (assert) + - name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate version 2 (assert) + assert: + that: + - ownca_cert_v2_version.stdout == '2' + when: "select_crypto_backend != 'cryptography'" + +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate v2 (test - ownca certificate version == 2) assert: that: - - ownca_cert_v2_version.stdout == '2' + - ownca_v2_certificate is failed + - "'The cryptography backend does not support v2 certificates' in ownca_v2_certificate.msg" + when: "select_crypto_backend == 'cryptography'" -- name: Validate ownca certificate2 (test - ownca certificate modulus) + +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate2 (test - ownca certificate modulus) shell: 'openssl x509 -noout -modulus -in {{ output_dir }}/ownca_cert2.pem' register: ownca_cert2_modulus -- name: Validate ownca certificate2 (assert) +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate2 (assert) assert: that: - ownca_cert2_modulus.stdout == privatekey2_modulus.stdout -- name: Validate owncal certificate3 (test - notBefore) +- name: (OwnCA validation, {{select_crypto_backend}}) Validate owncal certificate3 (test - notBefore) shell: 'openssl x509 -noout -in {{ output_dir }}/ownca_cert3.pem -text | grep "Not Before" | sed "s/.*: \(.*\) .*/\1/g"' register: ownca_cert3_notBefore -- name: Validate ownca certificate3 (test - notAfter) +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate3 (test - notAfter) shell: 'openssl x509 -noout -in {{ output_dir }}/ownca_cert3.pem -text | grep "Not After" | sed "s/.*: \(.*\) .*/\1/g"' register: ownca_cert3_notAfter -- name: Validate ownca certificate3 (assert - notBefore) +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate3 (assert - notBefore) assert: that: - ownca_cert3_notBefore.stdout == 'Oct 23 13:37:42 2018' -- name: Validate ownca certificate3 (assert - notAfter) +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate3 (assert - notAfter) assert: that: - ownca_cert3_notAfter.stdout == 'Oct 23 13:37:42 2019' -- name: Validate ownca ECC certificate (test - ownca certificate pubkey) +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca ECC certificate (test - ownca certificate pubkey) shell: 'openssl x509 -noout -pubkey -in {{ output_dir }}/ownca_cert_ecc.pem' register: ownca_cert_ecc_pubkey -- name: Validate ownca ECC certificate (test - ownca issuer value) +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca ECC certificate (test - ownca issuer value) shell: 'openssl x509 -noout -in {{ output_dir}}/ownca_cert_ecc.pem -text | grep "Issuer" | sed "s/.*: \(.*\)/\1/g"' register: ownca_cert_ecc_issuer -- name: Validate ownca ECC certificate (assert) +- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca ECC certificate (assert) assert: that: - ownca_cert_ecc_pubkey.stdout == privatekey_ecc_pubkey.stdout # openssl 1.1.x adds a space between the output - ownca_cert_ecc_issuer.stdout in ['CN=Example CA', 'CN = Example CA'] -- name: +- name: (OwnCA validation, {{select_crypto_backend}}) assert: that: - passphrase_error_1 is failed diff --git a/test/integration/targets/openssl_certificate/tests/validate_selfsigned.yml b/test/integration/targets/openssl_certificate/tests/validate_selfsigned.yml index 8749418a47..03b1c61928 100644 --- a/test/integration/targets/openssl_certificate/tests/validate_selfsigned.yml +++ b/test/integration/targets/openssl_certificate/tests/validate_selfsigned.yml @@ -1,89 +1,99 @@ --- -- name: Validate certificate (test - privatekey modulus) +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate (test - privatekey modulus) shell: 'openssl rsa -noout -modulus -in {{ output_dir }}/privatekey.pem' register: privatekey_modulus -- name: Validate certificate (test - certificate modulus) +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate (test - certificate modulus) shell: 'openssl x509 -noout -modulus -in {{ output_dir }}/cert.pem' register: cert_modulus -- name: Validate certificate (test - issuer value) +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate (test - issuer value) shell: 'openssl x509 -noout -in {{ output_dir}}/cert.pem -text | grep "Issuer" | sed "s/.*: \(.*\)/\1/g; s/ //g;"' register: cert_issuer -- name: Validate certificate (test - certficate version == default == 3) +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate (test - certficate version == default == 3) shell: 'openssl x509 -noout -in {{ output_dir}}/cert.pem -text | grep "Version" | sed "s/.*: \(.*\) .*/\1/g"' register: cert_version -- name: Validate certificate (assert) +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate (assert) assert: that: - cert_modulus.stdout == privatekey_modulus.stdout - cert_version.stdout == '3' - cert_issuer.stdout == 'CN=www.example.com' -- name: Validate certificate idempotence +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate idempotence assert: that: - selfsigned_certificate.serial_number == selfsigned_certificate_idempotence.serial_number - selfsigned_certificate.notBefore == selfsigned_certificate_idempotence.notBefore - selfsigned_certificate.notAfter == selfsigned_certificate_idempotence.notAfter -- name: Validate certificate v2 (test - certificate version == 2) - shell: 'openssl x509 -noout -in {{ output_dir}}/cert_v2.pem -text | grep "Version" | sed "s/.*: \(.*\) .*/\1/g"' - register: cert_v2_version +- block: + - name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate v2 (test - certificate version == 2) + shell: 'openssl x509 -noout -in {{ output_dir}}/cert_v2.pem -text | grep "Version" | sed "s/.*: \(.*\) .*/\1/g"' + register: cert_v2_version -- name: Validate certificate version 2 (assert) - assert: - that: - - cert_v2_version.stdout == '2' + - name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate version 2 (assert) + assert: + that: + - cert_v2_version.stdout == '2' + when: select_crypto_backend != 'cryptography' -- name: Validate certificate2 (test - privatekey modulus) +- block: + - name: (Selfsigned validateion, {{ select_crypto_backend }} Validate certificate v2 is failed + assert: + that: + - selfsigned_v2_cert is failed + - "'The cryptography backend does not support v2 certificates' in selfsigned_v2_cert.msg" + when: select_crypto_backend == 'cryptography' + +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate2 (test - privatekey modulus) shell: 'openssl rsa -noout -modulus -in {{ output_dir }}/privatekey2.pem' register: privatekey2_modulus -- name: Validate certificate2 (test - certificate modulus) +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate2 (test - certificate modulus) shell: 'openssl x509 -noout -modulus -in {{ output_dir }}/cert2.pem' register: cert2_modulus -- name: Validate certificate2 (assert) +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate2 (assert) assert: that: - cert2_modulus.stdout == privatekey2_modulus.stdout -- name: Validate certificate3 (test - notBefore) +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate3 (test - notBefore) shell: 'openssl x509 -noout -in {{ output_dir }}/cert3.pem -text | grep "Not Before" | sed "s/.*: \(.*\) .*/\1/g"' register: cert3_notBefore -- name: Validate certificate3 (test - notAfter) +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate3 (test - notAfter) shell: 'openssl x509 -noout -in {{ output_dir }}/cert3.pem -text | grep "Not After" | sed "s/.*: \(.*\) .*/\1/g"' register: cert3_notAfter -- name: Validate certificate3 (assert - notBefore) +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate3 (assert - notBefore) assert: that: - cert3_notBefore.stdout == 'Oct 23 13:37:42 2018' -- name: Validate certificate3 (assert - notAfter) +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate certificate3 (assert - notAfter) assert: that: - cert3_notAfter.stdout == 'Oct 23 13:37:42 2019' -- name: Validate ECC certificate (test - privatekey's pubkey) +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate ECC certificate (test - privatekey's pubkey) shell: 'openssl ec -pubout -in {{ output_dir }}/privatekey_ecc.pem' register: privatekey_ecc_pubkey -- name: Validate ECC certificate (test - certificate pubkey) +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate ECC certificate (test - certificate pubkey) shell: 'openssl x509 -noout -pubkey -in {{ output_dir }}/cert_ecc.pem' register: cert_ecc_pubkey -- name: Validate ECC certificate (assert) +- name: (Selfsigned validation, {{select_crypto_backend}}) Validate ECC certificate (assert) assert: that: - cert_ecc_pubkey.stdout == privatekey_ecc_pubkey.stdout -- name: +- name: (Selfsigned validation, {{select_crypto_backend}}) assert: that: - passphrase_error_1 is failed