使用python获取网站的ssl信息

有时候我们会有一些需求,需要批量检查自己的域名证书的到期时间,用于提前告警续费,使用python可以轻松的完成这个任务;
这里有用到第三方库pip install -U cryptography,这里没有考虑站点重定向等情况,低版本python或cryptography需要改一些内容,自行斟酌。

import socket
import ssl
from concurrent.futures import ThreadPoolExecutor, as_completed
from typing import Sequence
from zoneinfo import ZoneInfo

from cryptography import x509

context = ssl.create_default_context()
context.load_default_certs()

def get_certificate(domain, port=443, timeout=5) -> dict:
    """https://docs.python.org/3/library/ssl.html"""
    with socket.create_connection((domain, port), timeout=timeout) as _sock:
        with context.wrap_socket(_sock, server_hostname=domain) as sslsock:
            # cert = ssl.DER_cert_to_PEM_cert(sslsock.getpeercert(True))  # 证书加载为pem格式
            cert_bytes = sslsock.getpeercert(True)
            if cert_bytes is None:
                return {}

            certificate = x509.load_der_x509_certificate(cert_bytes)

            expired = certificate.not_valid_after_utc.astimezone(ZoneInfo("Asia/Shanghai"))
            created = certificate.not_valid_before_utc.astimezone(ZoneInfo("Asia/Shanghai"))

            return {
                "domain": domain,
                "version": certificate.version.name,
                "algorithm": certificate.signature_hash_algorithm.name,
                "subject": certificate.subject.rfc4514_string(),
                "issuer": certificate.issuer.rfc4514_string(),
                "expired_at": expired.strftime("%F %T"),
                "created_at": created.strftime("%F %T"),
            }

def multi_get_certificate(domains: Sequence[str]) -> list[dict]:
    result = []
    with ThreadPoolExecutor(max_workers=3) as pool:
        tasks = {pool.submit(get_certificate, domain): domain for domain in domains}
        for task in as_completed(tasks):
            domain = tasks[task]
            try:
                result.append(task.result(timeout=10))  # 10s强行断开
            except Exception as exc:
                print(f"{domain=}: failed to get certificate: {exc}")
    return result

if __name__ == "__main__":
    domains = ("baidu.com", "jd.com", "qq.com", "aliyun.com")
    print(multi_get_certificate(domains))

输出以下内容

[{'domain': 'qq.com', 'version': 'v3', 'algorithm': 'sha256', 'subject': 'CN=qq.com,O=Shenzhen Tencent Computer Systems Company Limited,L=Shenzhen,ST=Guangdong Province,C=CN', 'issuer': 'CN=DigiCert Secure Site CN CA G3,O=DigiCert Inc,C=US', 'expired_at': '2024-06-10 07:59:59', 'created_at': '2023-05-10 08:00:00'},
{'domain': 'jd.com', 'version': 'v3', 'algorithm': 'sha256', 'subject': 'CN=*.jd.com,O=BEIJING JINGDONG SHANGKE INFORMATION TECHNOLOGY CO.\\, LTD.,L=Beijing,ST=Beijing,C=CN', 'issuer': 'CN=GlobalSign RSA OV SSL CA 2018,O=GlobalSign nv-sa,C=BE', 'expired_at': '2024-12-09 09:34:41', 'created_at': '2023-11-08 09:34:42'},
{'domain': 'baidu.com', 'version': 'v3', 'algorithm': 'sha256', 'subject': 'CN=www.baidu.cn,O=BeiJing Baidu Netcom Science Technology Co.\\, Ltd,ST=北京市,C=CN', 'issuer': 'CN=DigiCert Secure Site Pro CN CA G3,O=DigiCert Inc,C=US', 'expired_at': '2025-03-02 07:59:59', 'created_at': '2024-01-30 08:00:00'},
{'domain': 'aliyun.com', 'version': 'v3', 'algorithm': 'sha256', 'subject': 'CN=*.aliyun.com,O=Alibaba (China) Technology Co.\\, Ltd.,L=HangZhou,ST=ZheJiang,C=CN', 'issuer': 'CN=GlobalSign Organization Validation CA - SHA256 - G3,O=GlobalSign nv-sa,C=BE', 'expired_at': '2024-12-30 16:21:02', 'created_at': '2023-12-07 15:21:04'}]