From bf04282ac4774fef1bc32f5858dc8f96e369d804 Mon Sep 17 00:00:00 2001 From: Kajetan Staszkiewicz Date: Mon, 13 Jul 2026 18:03:15 +0200 Subject: [PATCH] Fix check_ssl_cert_expire on newer pyOpenSSL pyOpenSSL 26.2.0 removed the deprecated X509Extension API (get_extension/get_extension_count), which broke SAN extraction with: AttributeError: 'X509' object has no attribute 'get_extension' Read the subjectAltName via cert.to_cryptography() instead, iterating the extension's DNSName/IPAddress entries directly. While here, migrate get_issued_to() off get_subject()/get_components(), both deprecated in pyOpenSSL 26.3.0 and on the same removal path, to cert.to_cryptography().subject. cryptography is already a pyOpenSSL dependency, so no new requirement is introduced. get_notAfter() and load_certificate() are left as-is; they are not deprecated. --- src/check_ssl_cert_expire.py | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/check_ssl_cert_expire.py b/src/check_ssl_cert_expire.py index b8eae260..aa95f096 100755 --- a/src/check_ssl_cert_expire.py +++ b/src/check_ssl_cert_expire.py @@ -23,6 +23,8 @@ from builtins import str from OpenSSL import crypto +from cryptography import x509 +from cryptography.x509.oid import ExtensionOID, NameOID from argparse import ArgumentParser, FileType from datetime import datetime, timedelta from dateutil.tz import tzutc @@ -146,8 +148,7 @@ def interval(arg): def verify_domains(cert, names): cert_domains = {get_issued_to(cert)} - for alt_domain in decode_san(get_extension_value(cert, b'subjectAltName')): - cert_domains.add(alt_domain) + cert_domains.update(get_san(cert)) cert_domains = set(map(expand_ip, cert_domains)) @@ -165,26 +166,24 @@ def verify_domains(cert, names): def get_issued_to(cert): - for components in cert.get_subject().get_components(): - if components[0] == b'CN': - return components[1].decode() + cn = cert.to_cryptography().subject.get_attributes_for_oid( + NameOID.COMMON_NAME) + if cn: + return cn[0].value -def get_extension_value(cert, short_name): - for ext_num in range(cert.get_extension_count()): - if cert.get_extension(ext_num).get_short_name() == short_name: - return str(cert.get_extension(ext_num)) - - -def decode_san(san_string): - if not san_string: +def get_san(cert): + try: + ext = cert.to_cryptography().extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME) + except x509.ExtensionNotFound: return - for item in san_string.split(','): - key, value = item.split(':', 1) - key = key.strip().lower() - if key == 'dns' or key == 'ip address': - yield value.strip() + for name in ext.value: + if isinstance(name, x509.DNSName): + yield name.value + elif isinstance(name, x509.IPAddress): + yield str(name.value) def expand_ip(name):