如何用脚本获取证书信息

wen 实用脚本 3

本文目录导读:

如何用脚本获取证书信息

  1. 使用 OpenSSL 命令行(最通用)
  2. 使用 Python(跨平台,功能最全)
  3. 使用 Node.js(适合JavaScript开发者)
  4. 批量监控脚本(实用版)
  5. 实用技巧

我们可以通过多种方式用脚本获取SSL/TLS证书信息,以下是三种主流方法的实现:

使用 OpenSSL 命令行(最通用)

Bash 脚本(Linux/macOS)

#!/bin/bash
# 获取证书信息
DOMAIN="example.com"
PORT=443
echo "=== 获取 $DOMAIN 的证书信息 ==="
echo
# 获取并解析证书信息
echo | openssl s_client -connect $DOMAIN:$PORT -servername $DOMAIN 2>/dev/null | \
openssl x509 -noout -text 2>/dev/null | head -50
# 获取关键信息摘要
echo
echo "=== 关键信息摘要 ==="
echo | openssl s_client -connect $DOMAIN:$PORT -servername $DOMAIN 2>/dev/null | \
openssl x509 -noout -dates -subject -issuer 2>/dev/null
# 获取证书过期剩余天数
EXPIRY_DATE=$(echo | openssl s_client -connect $DOMAIN:$PORT -servername $DOMAIN 2>/dev/null | \
openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
if [ -n "$EXPIRY_DATE" ]; then
    EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
    CURRENT_EPOCH=$(date +%s)
    REMAINING_DAYS=$(( ($EXPIRY_EPOCH - $CURRENT_EPOCH) / 86400 ))
    echo "证书剩余天数: $REMAINING_DAYS"
fi

Windows PowerShell 版本

# 定义目标域名和端口
$domain = "example.com"
$port = 443
# 创建TCP连接并获取证书
$tcpClient = New-Object System.Net.Sockets.TcpClient
$tcpClient.Connect($domain, $port)
$sslStream = New-Object System.Net.Security.SslStream($tcpClient.GetStream(), $false, 
    { param($sender, $certificate, $chain, $sslPolicyErrors) return $true })
$sslStream.AuthenticateAsClient($domain)
# 获取证书对象
$certificate = $sslStream.RemoteCertificate
# 显示证书信息
Write-Host "=== 证书信息 ===" -ForegroundColor Green
Write-Host "主题: $($certificate.Subject)"
Write-Host "颁发者: $($certificate.Issuer)"
Write-Host "有效起始: $($certificate.GetEffectiveDateString())"
Write-Host "有效截止: $($certificate.GetExpirationDateString())"
Write-Host "序列号: $($certificate.GetSerialNumberString())"
Write-Host "算法: $($certificate.GetKeyAlgorithm())"
# 计算剩余天数
$expiryDate = [DateTime]::Parse($certificate.GetExpirationDateString())
$remainingDays = ($expiryDate - (Get-Date)).Days
Write-Host "剩余天数: $remainingDays" -ForegroundColor Yellow
# 关闭连接
$sslStream.Close()
$tcpClient.Close()

使用 Python(跨平台,功能最全)

Python 脚本

import ssl
import socket
import datetime
import json
import certifi
def get_certificate_info(hostname, port=443):
    """
    获取SSL证书信息
    """
    context = ssl.create_default_context(cafile=certifi.where())
    with socket.create_connection((hostname, port), timeout=10) as sock:
        with context.wrap_socket(sock, server_hostname=hostname) as ssock:
            cert = ssock.getpeercert()
            # 提取证书信息
            info = {
                'subject': dict(x[0] for x in cert['subject']),
                'issuer': dict(x[0] for x in cert['issuer']),
                'version': cert.get('version', 'N/A'),
                'serial_number': cert.get('serialNumber', 'N/A'),
                'not_before': cert['notBefore'],
                'not_after': cert['notAfter'],
                'subject_alt_name': cert.get('subjectAltName', []),
                'OCSP': [x for x in cert.get('OCSP', [])],
                'caIssuers': [x for x in cert.get('caIssuers', [])]
            }
            # 计算剩余天数
            expires = datetime.datetime.strptime(cert['notAfter'], '%b %d %H:%M:%S %Y %Z')
            remaining = (expires - datetime.datetime.now()).days
            info['remaining_days'] = remaining
            info['is_expired'] = remaining < 0
            return info
def print_certificate_info(hostname):
    """打印格式化证书信息"""
    try:
        info = get_certificate_info(hostname)
        print(f"{'='*50}")
        print(f"证书信息 - {hostname}")
        print(f"{'='*50}")
        print(f"主题: {info['subject'].get('commonName', 'N/A')}")
        print(f"组织: {info['subject'].get('organizationName', 'N/A')}")
        print(f"颁发者: {info['issuer'].get('organizationName', 'N/A')}")
        print(f"有效开始: {info['not_before']}")
        print(f"有效结束: {info['not_after']}")
        print(f"剩余天数: {info['remaining_days']}")
        print(f"是否过期: {'是' if info['is_expired'] else '否'}")
        if info['subject_alt_name']:
            print(f"SANs: {', '.join([x[1] for x in info['subject_alt_name'][:5]])}")
            if len(info['subject_alt_name']) > 5:
                print(f"  ... 还有 {len(info['subject_alt_name'])-5} 个")
    except Exception as e:
        print(f"获取证书信息失败: {e}")
# 使用示例
if __name__ == "__main__":
    # 单个域名
    print_certificate_info("google.com")
    # 批量检查多个域名
    domains = ["google.com", "github.com", "stackoverflow.com"]
    for domain in domains:
        print_certificate_info(domain)
        print("\n")

使用 Node.js(适合JavaScript开发者)

Node.js 脚本

const https = require('https');
const tls = require('tls');
const { performance } = require('perf_hooks');
async function getCertificateInfo(hostname, port = 443) {
    return new Promise((resolve, reject) => {
        const options = {
            host: hostname,
            port: port,
            rejectUnauthorized: false,
            requestCert: true,
        };
        const socket = tls.connect(options, () => {
            const certificate = socket.getPeerCertificate();
            if (!certificate || Object.keys(certificate).length === 0) {
                reject(new Error('无法获取证书'));
                return;
            }
            const info = {
                subject: certificate.subject || {},
                issuer: certificate.issuer || {},
                validFrom: certificate.valid_from,
                validTo: certificate.valid_to,
                serialNumber: certificate.serialNumber,
                fingerprint: certificate.fingerprint,
                subjectaltname: certificate.subjectaltname,
                issuerCertificate: certificate.issuerCertificate ? true : false,
            };
            // 计算剩余天数
            const expiryDate = new Date(certificate.valid_to);
            const now = new Date();
            const remainingDays = Math.floor((expiryDate - now) / (1000 * 60 * 60 * 24));
            info.remainingDays = remainingDays;
            info.isExpired = remainingDays < 0;
            socket.end();
            resolve(info);
        });
        socket.on('error', reject);
        // 设置超时
        setTimeout(() => {
            socket.destroy();
            reject(new Error('连接超时'));
        }, 10000);
    });
}
async function printCertificateInfo(hostname) {
    try {
        const info = await getCertificateInfo(hostname);
        console.log('='.repeat(50));
        console.log(`证书信息 - ${hostname}`);
        console.log('='.repeat(50));
        console.log(`主题: ${info.subject.CN || 'N/A'}`);
        console.log(`组织: ${info.subject.O || 'N/A'}`);
        console.log(`颁发者: ${info.issuer.O || 'N/A'}`);
        console.log(`有效开始: ${info.validFrom}`);
        console.log(`有效结束: ${info.validTo}`);
        console.log(`剩余天数: ${info.remainingDays}`);
        console.log(`是否过期: ${info.isExpired ? '是' : '否'}`);
        if (info.subjectaltname) {
            console.log(`SANs: ${info.subjectaltname.split(', ').slice(0, 5).join(', ')}`);
        }
    } catch (error) {
        console.error(`获取证书信息失败: ${error.message}`);
    }
}
// 使用示例
async function main() {
    // 单个域名
    await printCertificateInfo('google.com');
    // 批量检查
    const domains = ['google.com', 'github.com', 'stackoverflow.com'];
    for (const domain of domains) {
        await printCertificateInfo(domain);
        console.log('\n');
    }
}
main().catch(console.error);

批量监控脚本(实用版)

多域名监控脚本 (Python)

#!/usr/bin/env python3
import ssl
import socket
import datetime
import json
import sys
import smtplib
from email.mime.text import MIMEText
# 配置
DOMAINS = [
    "google.com",
    "github.com",
    "example.com",
    # 添加更多域名
]
WARNING_DAYS = 30  # 提前30天警告
CRITICAL_DAYS = 7  # 提前7天严重警告
def check_domain(hostname, port=443):
    """检查单个域名证书"""
    context = ssl.create_default_context()
    try:
        with socket.create_connection((hostname, port), timeout=10) as sock:
            with context.wrap_socket(sock, server_hostname=hostname) as ssock:
                cert = ssock.getpeercert()
                expires = datetime.datetime.strptime(
                    cert['notAfter'], 
                    '%b %d %H:%M:%S %Y %Z'
                )
                remaining = (expires - datetime.datetime.now()).days
                return {
                    'domain': hostname,
                    'status': 'valid',
                    'expires': cert['notAfter'],
                    'remaining_days': remaining,
                    'subject': dict(x[0] for x in cert['subject']),
                    'issuer': dict(x[0] for x in cert['issuer']),
                }
    except Exception as e:
        return {
            'domain': hostname,
            'status': 'error',
            'error': str(e)
        }
def send_alert(domain_info):
    """发送告警通知(示例为邮件)"""
    # 实际使用时配置SMTP服务器
    msg = MIMEText(f"""
    域名: {domain_info['domain']}
    状态: {domain_info['status']}
    过期时间: {domain_info.get('expires', 'N/A')}
    剩余天数: {domain_info.get('remaining_days', 'N/A')}
    """)
    msg['Subject'] = f"证书即将过期 - {domain_info['domain']}"
    msg['From'] = "monitor@example.com"
    msg['To'] = "admin@example.com"
    # 发送邮件(需要配置SMTP)
    # with smtplib.SMTP('smtp.example.com') as server:
    #     server.send_message(msg)
def main():
    """主函数"""
    results = []
    print(f"{'='*60}")
    print(f"证书检查报告 - {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
    print(f"{'='*60}")
    for domain in DOMAINS:
        info = check_domain(domain)
        results.append(info)
        # 打印状态
        if info['status'] == 'error':
            print(f"❌ {domain}: 检查失败 - {info['error']}")
        else:
            days = info['remaining_days']
            icon = "✅" if days > 30 else ("⚠️" if days > 7 else "🔴")
            print(f"{icon} {domain}: {days}天剩余 (过期: {info['expires']})")
        # 触发告警
        if info['status'] == 'valid' and info['remaining_days'] < WARNING_DAYS:
            # send_alert(info)
            print(f"   ⚠️ 需要关注该域名,即将过期!")
    # 生成JSON报告
    report = {
        'timestamp': datetime.datetime.now().isoformat(),
        'results': results
    }
    with open('cert_report.json', 'w') as f:
        json.dump(report, f, indent=2)
    print(f"\n完整报告已保存到 cert_report.json")
if __name__ == "__main__":
    main()

实用技巧

定时检查(Crontab设置)

# 每天凌晨2点检查证书
0 2 * * * /usr/bin/python3 /path/to/cert_checker.py >> /var/log/cert_check.log 2>&1

使用环境变量配置

#!/bin/bash
# 从环境变量读取域名列表
IFS=',' read -ra DOMAINS <<< "${CERT_DOMAINS:-example.com,google.com}"
for domain in "${DOMAINS[@]}"; do
    echo "检查: $domain"
    # 执行检查代码...
done

输出格式自定义

# 支持JSON/CSV/HTML等多种输出格式
def output_formatter(results, format='json'):
    if format == 'json':
        return json.dumps(results, indent=2)
    elif format == 'csv':
        import csv
        # CSV输出逻辑...
    elif format == 'html':
        # HTML表格输出...
        pass

这些脚本可以根据实际需求进行定制,比如添加Webhook通知、集成到监控系统(Prometheus、Zabbix)、记录历史数据等,建议结合定时任务运行,实现自动化的证书监控。

抱歉,评论功能暂时关闭!