# Copyright (c) 2019, Felix Fontein # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later # Note that this module util is **PRIVATE** to the collection. It can have breaking changes at any time. # Do not use this from other collections or standalone plugins/modules! from __future__ import annotations import typing as t from ansible_collections.community.crypto.plugins.module_utils._version import ( LooseVersion as _LooseVersion, ) try: import cryptography from cryptography import x509 except ImportError: # Error handled in the calling module. pass from ansible_collections.community.crypto.plugins.module_utils._crypto._obj2txt import ( obj2txt, ) from ansible_collections.community.crypto.plugins.module_utils._crypto.basic import ( HAS_CRYPTOGRAPHY, ) from ansible_collections.community.crypto.plugins.module_utils._crypto.cryptography_support import ( CRYPTOGRAPHY_TIMEZONE, cryptography_decode_name, ) if t.TYPE_CHECKING: import datetime # pragma: no cover # TODO: once cryptography has a _utc variant of InvalidityDate.invalidity_date, set this # to True and adjust get_invalidity_date() accordingly. # (https://github.com/pyca/cryptography/issues/10818) CRYPTOGRAPHY_TIMEZONE_INVALIDITY_DATE = False # pylint: disable=invalid-name if HAS_CRYPTOGRAPHY: CRYPTOGRAPHY_TIMEZONE_INVALIDITY_DATE = _LooseVersion( cryptography.__version__ ) >= _LooseVersion("43.0.0") TIMESTAMP_FORMAT = "%Y%m%d%H%M%SZ" if HAS_CRYPTOGRAPHY: REVOCATION_REASON_MAP = { "unspecified": x509.ReasonFlags.unspecified, "key_compromise": x509.ReasonFlags.key_compromise, "ca_compromise": x509.ReasonFlags.ca_compromise, "affiliation_changed": x509.ReasonFlags.affiliation_changed, "superseded": x509.ReasonFlags.superseded, "cessation_of_operation": x509.ReasonFlags.cessation_of_operation, "certificate_hold": x509.ReasonFlags.certificate_hold, "privilege_withdrawn": x509.ReasonFlags.privilege_withdrawn, "aa_compromise": x509.ReasonFlags.aa_compromise, "remove_from_crl": x509.ReasonFlags.remove_from_crl, } REVOCATION_REASON_MAP_INVERSE = {} for k, v in REVOCATION_REASON_MAP.items(): REVOCATION_REASON_MAP_INVERSE[v] = k else: REVOCATION_REASON_MAP = {} REVOCATION_REASON_MAP_INVERSE = {} def cryptography_decode_revoked_certificate( cert: x509.RevokedCertificate, ) -> dict[str, t.Any]: result = { "serial_number": cert.serial_number, "revocation_date": get_revocation_date(cert), "issuer": None, "issuer_critical": False, "reason": None, "reason_critical": False, "invalidity_date": None, "invalidity_date_critical": False, } try: ext_ci = cert.extensions.get_extension_for_class(x509.CertificateIssuer) result["issuer"] = list(ext_ci.value) result["issuer_critical"] = ext_ci.critical except x509.ExtensionNotFound: pass try: ext_cr = cert.extensions.get_extension_for_class(x509.CRLReason) result["reason"] = ext_cr.value.reason result["reason_critical"] = ext_cr.critical except x509.ExtensionNotFound: pass try: ext_id = cert.extensions.get_extension_for_class(x509.InvalidityDate) result["invalidity_date"] = get_invalidity_date(ext_id.value) result["invalidity_date_critical"] = ext_id.critical except x509.ExtensionNotFound: pass return result def cryptography_dump_revoked( entry: dict[str, t.Any], *, idn_rewrite: t.Literal["ignore", "idna", "unicode"] = "ignore", ) -> dict[str, t.Any]: return { "serial_number": entry["serial_number"], "revocation_date": entry["revocation_date"].strftime(TIMESTAMP_FORMAT), "issuer": ( [ cryptography_decode_name(issuer, idn_rewrite=idn_rewrite) for issuer in entry["issuer"] ] if entry["issuer"] is not None else None ), "issuer_critical": entry["issuer_critical"], "reason": ( REVOCATION_REASON_MAP_INVERSE.get(entry["reason"]) if entry["reason"] is not None else None ), "reason_critical": entry["reason_critical"], "invalidity_date": ( entry["invalidity_date"].strftime(TIMESTAMP_FORMAT) if entry["invalidity_date"] is not None else None ), "invalidity_date_critical": entry["invalidity_date_critical"], } def cryptography_get_signature_algorithm_oid_from_crl( crl: x509.CertificateRevocationList, ) -> x509.oid.ObjectIdentifier: try: return crl.signature_algorithm_oid except AttributeError: # Older cryptography versions do not have signature_algorithm_oid yet dotted = obj2txt( crl._backend._lib, # type: ignore[attr-defined] # pylint: disable=protected-access crl._backend._ffi, # type: ignore[attr-defined] # pylint: disable=protected-access crl._x509_crl.sig_alg.algorithm, # type: ignore[attr-defined] # pylint: disable=protected-access ) return x509.oid.ObjectIdentifier(dotted) def get_next_update(obj: x509.CertificateRevocationList) -> datetime.datetime | None: if CRYPTOGRAPHY_TIMEZONE: return obj.next_update_utc return obj.next_update def get_last_update(obj: x509.CertificateRevocationList) -> datetime.datetime: if CRYPTOGRAPHY_TIMEZONE: return obj.last_update_utc return obj.last_update def get_revocation_date(obj: x509.RevokedCertificate) -> datetime.datetime: if CRYPTOGRAPHY_TIMEZONE: return obj.revocation_date_utc return obj.revocation_date def get_invalidity_date(obj: x509.InvalidityDate) -> datetime.datetime: if CRYPTOGRAPHY_TIMEZONE_INVALIDITY_DATE: return obj.invalidity_date_utc return obj.invalidity_date def set_next_update( builder: x509.CertificateRevocationListBuilder, *, value: datetime.datetime ) -> x509.CertificateRevocationListBuilder: return builder.next_update(value) def set_last_update( builder: x509.CertificateRevocationListBuilder, *, value: datetime.datetime ) -> x509.CertificateRevocationListBuilder: return builder.last_update(value) def set_revocation_date( builder: x509.RevokedCertificateBuilder, *, value: datetime.datetime ) -> x509.RevokedCertificateBuilder: return builder.revocation_date(value) __all__ = ( "REVOCATION_REASON_MAP", "REVOCATION_REASON_MAP_INVERSE", "cryptography_decode_revoked_certificate", "cryptography_dump_revoked", "cryptography_get_signature_algorithm_oid_from_crl", "get_invalidity_date", "get_last_update", "get_next_update", "get_revocation_date", "set_last_update", "set_next_update", "set_revocation_date", )