# Buggy code
def _check_csr(self):
    def _check_subject(csr):
        subject = [
            (OpenSSL._util.lib.OBJ_txt2nid(to_bytes(sub[0])), to_bytes(sub[1]))
            for sub in self.subject
        ]
        current_subject = [
            (OpenSSL._util.lib.OBJ_txt2nid(to_bytes(sub[0])), to_bytes(sub[1]))
            for sub in csr.get_subject().get_components()
        ]
        if not set(subject) == set(current_subject):
            return False

        return True

    def _check_subjectAltName(extensions):
        altnames_ext = next(
            (ext for ext in extensions if ext.get_short_name() == b"subjectAltName"), ""
        )
        altnames = [
            altname.strip()
            for altname in str(altnames_ext).split(",")
            if altname.strip()
        ]
        # apperently openssl returns 'IP address' not 'IP' as specifier when converting the subjectAltName to string
        # although it won't accept this specifier when generating the CSR. (https://github.com/openssl/openssl/issues/4004)
        altnames = [
            name
            if not name.startswith("IP Address:")
            else "IP:" + name.split(":", 1)[1]
            for name in altnames
        ]
        if self.subjectAltName:
            if (
                set(altnames) != set(self.subjectAltName)
                or altnames_ext.get_critical() != self.subjectAltName_critical
            ):
                return False
        else:
            if altnames:
                return False

        return True

    def _check_keyUsage_(extensions, extName, expected, critical):
        usages_ext = [ext for ext in extensions if ext.get_short_name() == extName]
        if (not usages_ext and expected) or (usages_ext and not expected):
            return False
        elif not usages_ext and not expected:
            return True
        else:
            current = [
                OpenSSL._util.lib.OBJ_txt2nid(to_bytes(usage.strip()))
                for usage in str(usages_ext[0]).split(",")
            ]
            expected = [
                OpenSSL._util.lib.OBJ_txt2nid(to_bytes(usage)) for usage in expected
            ]
            return (
                set(current) == set(expected)
                and usages_ext[0].get_critical() == critical
            )

    def _check_keyUsage(extensions):
        usages_ext = [ext for ext in extensions if ext.get_short_name() == b"keyUsage"]
        if (not usages_ext and self.keyUsage) or (usages_ext and not self.keyUsage):
            return False
        elif not usages_ext and not self.keyUsage:
            return True
        else:
            # OpenSSL._util.lib.OBJ_txt2nid() always returns 0 for all keyUsage values
            # (since keyUsage has a fixed bitfield for these values and is not extensible).
            # Therefore, we create an extension for the wanted values, and compare the
            # data of the extensions (which is the serialized bitfield).
            expected_ext = crypto.X509Extension(
                b"keyUsage", False, ", ".join(self.keyUsage).encode("ascii")
            )
            return (
                usages_ext[0].get_data() == expected_ext.get_data()
                and usages_ext[0].get_critical() == self.keyUsage_critical
            )

    def _check_extenededKeyUsage(extensions):
        return _check_keyUsage_(
            extensions,
            b"extendedKeyUsage",
            self.extendedKeyUsage,
            self.extendedKeyUsage_critical,
        )

    def _check_basicConstraints(extensions):
        return _check_keyUsage_(
            extensions,
            b"basicConstraints",
            self.basicConstraints,
            self.basicConstraints_critical,
        )

    def _check_ocspMustStaple(extensions):
        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 self.ocspMustStaple:
            return (
                len(oms_ext) > 0
                and oms_ext[0].get_critical() == self.ocspMustStaple_critical
            )
        else:
            return len(oms_ext) == 0

    def _check_extensions(csr):
        extensions = csr.get_extensions()
        return (
            _check_subjectAltName(extensions)
            and _check_keyUsage(extensions)
            and _check_extenededKeyUsage(extensions)
            and _check_basicConstraints(extensions)
            and _check_ocspMustStaple(extensions)
        )

    def _check_signature(csr):
        try:
            return csr.verify(self.privatekey)
        except crypto.Error:
            return False

    try:
        csr = crypto_utils.load_certificate_request(self.path, backend="pyopenssl")
    except Exception as dummy:
        return False

    return _check_subject(csr) and _check_extensions(csr) and _check_signature(csr)


# Traceback (nAttributeError)
root@test:/ansible/playbooks# ansible-playbook reproduce.yml

PLAY [localhost] *************************************************************************************************************************************************

TASK [Gathering Facts] *******************************************************************************************************************************************
ok: [localhost]

TASK [Generate root CA private key] ******************************************************************************************************************************
ok: [localhost]

TASK [openssl_csr] ***********************************************************************************************************************************************
fatal: [localhost]: FAILED! => {"changed": false, "module_stderr": "/root/.ansible/tmp/ansible-tmp-1554976577.3779902-7265526702622/AnsiballZ_openssl_csr.py:18: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses\n  import imp\nTraceback (most recent call last):\n  File \"/root/.ansible/tmp/ansible-tmp-1554976577.3779902-7265526702622/AnsiballZ_openssl_csr.py\", line 114, in <module>\n    _ansiballz_main()\n  File \"/root/.ansible/tmp/ansible-tmp-1554976577.3779902-7265526702622/AnsiballZ_openssl_csr.py\", line 106, in _ansiballz_main\n    invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)\n  File \"/root/.ansible/tmp/ansible-tmp-1554976577.3779902-7265526702622/AnsiballZ_openssl_csr.py\", line 49, in invoke_module\n    imp.load_module('__main__', mod, module, MOD_DESC)\n  File \"/usr/local/lib/python3.7/imp.py\", line 234, in load_module\n    return load_source(name, filename, file)\n  File \"/usr/local/lib/python3.7/imp.py\", line 169, in load_source\n    module = _exec(spec, sys.modules[name])\n  File \"<frozen importlib._bootstrap>\", line 630, in _exec\n  File \"<frozen importlib._bootstrap_external>\", line 728, in exec_module\n  File \"<frozen importlib._bootstrap>\", line 219, in _call_with_frames_removed\n  File \"/tmp/ansible_openssl_csr_payload_agiegmf7/__main__.py\", line 924, in <module>\n  File \"/tmp/ansible_openssl_csr_payload_agiegmf7/__main__.py\", line 907, in main\n  File \"/tmp/ansible_openssl_csr_payload_agiegmf7/__main__.py\", line 441, in generate\n  File \"/tmp/ansible_openssl_csr_payload_agiegmf7/__main__.py\", line 469, in check\n  File \"/tmp/ansible_openssl_csr_payload_agiegmf7/__main__.py\", line 826, in _check_csr\n  File \"/tmp/ansible_openssl_csr_payload_agiegmf7/__main__.py\", line 803, in _check_extensions\n  File \"/tmp/ansible_openssl_csr_payload_agiegmf7/__main__.py\", line 764, in _check_basicConstraints\nAttributeError: 'Extension' object has no attribute 'ca'\n", "module_stdout": "", "msg": "MODULE FAILURE\nSee stdout/stderr for the exact error", "rc": 1}

PLAY RECAP *******************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

# Fixed code
def _check_csr(self):
    def _check_subject(csr):
        subject = [
            (OpenSSL._util.lib.OBJ_txt2nid(to_bytes(sub[0])), to_bytes(sub[1]))
            for sub in self.subject
        ]
        current_subject = [
            (OpenSSL._util.lib.OBJ_txt2nid(to_bytes(sub[0])), to_bytes(sub[1]))
            for sub in csr.get_subject().get_components()
        ]
        if not set(subject) == set(current_subject):
            return False

        return True

    def _check_subjectAltName(extensions):
        altnames_ext = next(
            (ext for ext in extensions if ext.get_short_name() == b"subjectAltName"), ""
        )
        altnames = [
            self._normalize_san(altname.strip())
            for altname in to_text(altnames_ext, errors="surrogate_or_strict").split(
                ","
            )
            if altname.strip()
        ]
        if self.subjectAltName:
            if (
                set(altnames)
                != set(
                    [self._normalize_san(to_text(name)) for name in self.subjectAltName]
                )
                or altnames_ext.get_critical() != self.subjectAltName_critical
            ):
                return False
        else:
            if altnames:
                return False

        return True

    def _check_keyUsage_(extensions, extName, expected, critical):
        usages_ext = [ext for ext in extensions if ext.get_short_name() == extName]
        if (not usages_ext and expected) or (usages_ext and not expected):
            return False
        elif not usages_ext and not expected:
            return True
        else:
            current = [
                OpenSSL._util.lib.OBJ_txt2nid(to_bytes(usage.strip()))
                for usage in str(usages_ext[0]).split(",")
            ]
            expected = [
                OpenSSL._util.lib.OBJ_txt2nid(to_bytes(usage)) for usage in expected
            ]
            return (
                set(current) == set(expected)
                and usages_ext[0].get_critical() == critical
            )

    def _check_keyUsage(extensions):
        usages_ext = [ext for ext in extensions if ext.get_short_name() == b"keyUsage"]
        if (not usages_ext and self.keyUsage) or (usages_ext and not self.keyUsage):
            return False
        elif not usages_ext and not self.keyUsage:
            return True
        else:
            # OpenSSL._util.lib.OBJ_txt2nid() always returns 0 for all keyUsage values
            # (since keyUsage has a fixed bitfield for these values and is not extensible).
            # Therefore, we create an extension for the wanted values, and compare the
            # data of the extensions (which is the serialized bitfield).
            expected_ext = crypto.X509Extension(
                b"keyUsage", False, ", ".join(self.keyUsage).encode("ascii")
            )
            return (
                usages_ext[0].get_data() == expected_ext.get_data()
                and usages_ext[0].get_critical() == self.keyUsage_critical
            )

    def _check_extenededKeyUsage(extensions):
        return _check_keyUsage_(
            extensions,
            b"extendedKeyUsage",
            self.extendedKeyUsage,
            self.extendedKeyUsage_critical,
        )

    def _check_basicConstraints(extensions):
        return _check_keyUsage_(
            extensions,
            b"basicConstraints",
            self.basicConstraints,
            self.basicConstraints_critical,
        )

    def _check_ocspMustStaple(extensions):
        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 self.ocspMustStaple:
            return (
                len(oms_ext) > 0
                and oms_ext[0].get_critical() == self.ocspMustStaple_critical
            )
        else:
            return len(oms_ext) == 0

    def _check_extensions(csr):
        extensions = csr.get_extensions()
        return (
            _check_subjectAltName(extensions)
            and _check_keyUsage(extensions)
            and _check_extenededKeyUsage(extensions)
            and _check_basicConstraints(extensions)
            and _check_ocspMustStaple(extensions)
        )

    def _check_signature(csr):
        try:
            return csr.verify(self.privatekey)
        except crypto.Error:
            return False

    try:
        csr = crypto_utils.load_certificate_request(self.path, backend="pyopenssl")
    except Exception as dummy:
        return False

    return _check_subject(csr) and _check_extensions(csr) and _check_signature(csr)