本文目录导读:

我们可以通过多种方式用脚本获取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)、记录历史数据等,建议结合定时任务运行,实现自动化的证书监控。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播