Python查看证书到期时间

把 /usr/local/nginx/conf 替换成你的证书实际存放地址,识别pem crt 过滤根证书和中间件

#!/usr/bin/env python3
# 查看证书到期时间
import subprocess, unicodedata
from pathlib import Path
from datetime import datetime, timezone

CERT_DIR = Path("/usr/local/nginx/conf")

def display_width(text):
    width = 0
    for char in str(text):
        if unicodedata.combining(char): continue
        width += 2 if unicodedata.east_asian_width(char) in ("W", "F") else 1
    return width

def pad(text, width, align="left"):
    text = str(text)
    spaces = max(0, width - display_width(text))
    return (" " * spaces + text) if align == "right" else (text + " " * spaces)

def get_cert_blocks(file):
    try: content = file.read_text(errors="ignore")
    except Exception: return []
    start, end, blocks, pos = "-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----", [], 0
    while True:
        begin = content.find(start, pos)
        if begin == -1: break
        finish = content.find(end, begin)
        if finish == -1: break
        finish += len(end)
        blocks.append(content[begin:finish])
        pos = finish
    return blocks

def parse_cert(cert_pem):
    try:
        result = subprocess.run(
            ["openssl", "x509", "-noout", "-subject", "-dates", "-ext", "subjectAltName"],
            input=cert_pem, text=True, capture_output=True, timeout=5)
        if result.returncode != 0: return None
        output, domains = result.stdout, []
        for line in output.splitlines():
            if "DNS:" not in line: continue
            for part in line.split(","):
                part = part.strip()
                if part.startswith("DNS:"):
                    domain = part[4:].strip()
                    if domain and domain not in domains: domains.append(domain)
        if not domains: return None
        expire_str = next((line.split("=", 1)[1].strip() for line in output.splitlines() if line.startswith("notAfter=")), None)
        if not expire_str: return None
        expire_time = datetime.strptime(expire_str, "%b %d %H:%M:%S %Y %Z").replace(tzinfo=timezone.utc)
        days = int((expire_time - datetime.now(timezone.utc)).total_seconds() / 86400)
        return {"domain": ", ".join(domains), "expire": expire_time.strftime("%Y-%m-%d %H:%M:%S UTC"), "days": days, "expired": days < 0}
    except Exception: return None

def main():
    if not CERT_DIR.exists():
        print(f"目录不存在: {CERT_DIR}"); return
    files = sorted(f for f in CERT_DIR.iterdir() if f.is_file() and f.suffix.lower() in [".pem", ".crt"] and "dhparam" not in f.name.lower())
    W_FILE, W_DOMAIN, W_EXPIRE, W_DAYS, W_STATUS = 36, 48, 25, 10, 12
    header = pad("文件", W_FILE) + "  " + pad("域名", W_DOMAIN) + "  " + pad("到期日期", W_EXPIRE) + "  " + pad("剩余天数", W_DAYS, "right") + "  " + pad("状态", W_STATUS)
    print(header)
    print("-" * display_width(header))
    for file in files:
        for cert_pem in get_cert_blocks(file):
            info = parse_cert(cert_pem)
            if not info: continue
            if info["expired"]: status = "❌ 已过期"
            elif info["days"] <= 30: status = "⚠️  30天内到期"
            else: status = "✅ 有效"
            print(pad(file.name, W_FILE) + "  " + pad(info["domain"], W_DOMAIN) + "  " + pad(info["expire"], W_EXPIRE) + "  " + pad(info["days"], W_DAYS, "right") + "  " + pad(status, W_STATUS))

if __name__ == "__main__":
    main()

效果如下

root@rockylinux:~# python3 ssl.py 
文件                                  域名                                              到期日期                     剩余天数  状态        
-------------------------------------------------------------------------------------------------------------------------------------------
123.pem                               123.com, *.123.com                              2026-12-11 04:59:59 UTC     66       ✅ 有效     

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注