本文目录导读:

我们可以通过多种方式用脚本获取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)、记录历史数据等,建议结合定时任务运行,实现自动化的证书监控。