强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 5 章:证书管理

第 5 章:证书管理

证书管理贯穿证书的整个生命周期——从添加、更新、信任到吊销和黑名单。本章介绍系统级和应用级的证书管理实践。


5.1 添加受信任证书

添加到系统信任存储

# Debian/Ubuntu
# 1. 将证书文件复制到指定目录
sudo cp enterprise-root-ca.crt /usr/local/share/ca-certificates/

# 2. 更新证书存储
sudo update-ca-certificates
# 输出: 1 added, 0 removed; done.

# 3. 验证
openssl verify -CApath /etc/ssl/certs /path/to/cert-signed-by-ca.pem
# RHEL/CentOS/Fedora
sudo cp enterprise-root-ca.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust extract

# 验证
trust list | grep "Enterprise"

添加多个 CA 证书

# 批量添加
for crt in /path/to/ca-bundle/*.crt; do
  sudo cp "$crt" /usr/local/share/ca-certificates/
done
sudo update-ca-certificates

# 查看添加结果
ls -la /etc/ssl/certs/ | grep "$(date +%Y)"

添加证书链

# 添加包含完整证书链的文件
cat root-ca.crt intermediate-ca.crt > full-chain-ca.crt
sudo cp full-chain-ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates

5.2 删除/移除证书

从系统信任存储移除

# Debian/Ubuntu
# 方法 1:直接删除文件
sudo rm /usr/local/share/ca-certificates/my-ca.crt
sudo update-ca-certificates

# 方法 2:在配置文件中禁用(保留文件)
sudo vim /etc/ca-certificates.conf
# 将 "mozilla/Some_CA.crt" 改为 "!mozilla/Some_CA.crt"
sudo update-ca-certificates
# RHEL/CentOS
sudo rm /etc/pki/ca-trust/source/anchors/my-ca.crt
sudo update-ca-trust extract

# 使用 trust 工具
sudo trust anchor --remove my-ca.crt

移除后的影响

影响范围说明
curl / wget立即生效,无法验证由该 CA 签发的证书
浏览器需要重启浏览器
Python requests使用 certifi 的需要重启应用
Java需要从 cacerts 中移除

5.3 证书更新/续期

手动更新证书

# 1. 生成新的 CSR
openssl req -new -key server.key -out server-new.csr \
  -subj "/C=CN/ST=Beijing/O=MyOrg/CN=example.com"

# 2. 提交给 CA 签发(或使用 ACME 自动化)

# 3. 部署新证书
sudo cp new-cert.pem /etc/nginx/ssl/cert.pem
sudo cp new-chain.pem /etc/nginx/ssl/chain.pem

# 4. 重新加载服务
sudo nginx -t && sudo nginx -s reload

# 5. 验证新证书
echo | openssl s_client -connect example.com:443 2>/dev/null \
  | openssl x509 -noout -dates -serial

自动续期(certbot)

# 测试续期
sudo certbot renew --dry-run

# 设置自动续期(systemd timer)
sudo systemctl enable certbot-renew.timer
sudo systemctl start certbot-renew.timer

# 查看定时器状态
sudo systemctl status certbot-renew.timer

# 或使用 cron
echo "0 3 * * 1 root certbot renew --quiet --post-hook 'nginx -s reload'" \
  | sudo tee /etc/cron.d/certbot-renew

Nginx 零停机证书更新

# 使用 nginx -s reload 实现零停机更新
# reload 不会中断现有连接

# 配置示例
server {
    listen 443 ssl;
    ssl_certificate      /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key  /etc/nginx/ssl/privkey.pem;
}
# 更新证书后
sudo nginx -t && sudo nginx -s reload
# 现有连接不受影响,新连接使用新证书

5.4 证书黑名单

为什么需要黑名单

  • CA 被攻破或不再受信任
  • 特定中间 CA 存在安全问题
  • 某些 CA 的行为不符合安全标准

添加到黑名单

# Debian/Ubuntu
# 方法 1:在 ca-certificates.conf 中禁用
sudo vim /etc/ca-certificates.conf
# 添加: !mozilla/Compromised_CA.crt
sudo update-ca-certificates

# 方法 2:删除证书文件
sudo rm /usr/share/ca-certificates/mozilla/Compromised_CA.crt
sudo update-ca-certificates
# RHEL/CentOS
# 将证书放入 blacklist 目录
sudo cp compromised-ca.crt /etc/pki/ca-trust/source/blacklist/
sudo update-ca-trust extract

# 验证
trust list | grep "Compromised"
# 应该不再出现

浏览器级黑名单

浏览器黑名单机制说明
ChromeCRLSetsGoogle 推送的增量吊销列表
FirefoxOneCRLMozilla 维护的吊销列表
Safari系统级 OCSP/CRL跟随 macOS 信任策略

真实案例:Symantec CA 事件

时间线:
  2015 - Google 发现 Symantec 签发了未授权的证书
  2017 - Google 宣布逐步不信任 Symantec 证书
  2018 - Chrome 70 开始不信任 Symantec 签发的证书
  2018 - DigiCert 收购 Symantec 证书业务

影响:
  - 所有由 Symantec 旧基础设施签发的证书需要替换
  - 大量网站需要更新证书链
  - DigiCert 重新签发了受影响的证书

5.5 证书透明度日志(CT Log)监控

什么是 CT Log

CT(Certificate Transparency)是一个开放的框架,所有公开信任的 CA 必须将签发的证书提交到公开的日志服务器。

查询 CT Log

# 使用 crt.sh 查询域名的证书记录
curl -s "https://crt.sh/?q=example.com&output=json" | \
  jq '.[] | {id, issuer_name, name_value, not_before, not_after}' | head -30

# 查询最近 30 天的证书
curl -s "https://crt.sh/?q=example.com&output=json" | \
  jq --arg since "$(date -d '30 days ago' +%Y-%m-%d)" \
  '[.[] | select(.not_after > $since)] | length'

# 查询特定子域名
curl -s "https://crt.sh/?q=%.example.com&output=json" | \
  jq '.[].name_value' | sort -u

使用 CT Log 发现异常证书

#!/usr/bin/env bash
# ct-monitor.sh - 监控 CT 日志中的新证书
# 用法: ./ct-monitor.sh <domain> [known_domains_file]

DOMAIN="${1:?用法: $0 <domain> [known_domains_file]}"
KNOWN_FILE="${2:-}"

# 获取 CT 日志中的所有域名
FOUND=$(curl -s "https://crt.sh/?q=%.${DOMAIN}&output=json" 2>/dev/null \
  | jq -r '.[].name_value' | sort -u)

if [ -n "$KNOWN_FILE" ] && [ -f "$KNOWN_FILE" ]; then
  # 对比已知域名列表,发现未知证书
  NEW=$(comm -23 <(echo "$FOUND") <(sort "$KNOWN_FILE"))
  if [ -n "$NEW" ]; then
    echo "⚠️ 发现未知域名的证书:"
    echo "$NEW"
    # 可接入告警系统
  else
    echo "✅ 未发现异常证书"
  fi
else
  echo "域名 ${DOMAIN} 的 CT 日志记录:"
  echo "$FOUND"
fi
chmod +x ct-monitor.sh
./ct-monitor.sh example.com known-domains.txt

CT Log 服务器

日志服务器运营方URL
Google ArgonGooglect.googleapis.com/logs/argon2025h1
Google XenonGooglect.googleapis.com/logs/xenon2025h1
Cloudflare NimbusCloudflarect.cloudflare.com/logs/nimbus2025h1
DigiCert YetiDigiCertyeti2025.ct.digicert.com/log
# 查询 CT Log 的状态
curl -s "https://ct.googleapis.com/logs/argon2025h1/ct/v1/get-sth" | jq .

5.6 证书吊销管理

CRL 吊销列表管理

# 查看证书的 CRL 分发点
echo | openssl s_client -connect example.com:443 2>/dev/null \
  | openssl x509 -noout -text | grep -A4 "CRL Distribution"

# 下载 CRL
curl -s "http://crl.example.com/ca.crl" -o ca.crl

# 查看 CRL 内容
openssl crl -in ca.crl -noout -text | head -30

# 检查特定证书是否在 CRL 中
openssl crl -in ca.crl -noout -text | grep -A2 "Serial Number: 1234ABCD"

OCSP 吊销查询

# 获取 OCSP 响应者 URL
OCSP_URL=$(echo | openssl s_client -connect example.com:443 2>/dev/null \
  | openssl x509 -noout -ocsp_uri)

# 查询证书状态
openssl ocsp \
  -issuer intermediate.pem \
  -cert leaf.pem \
  -url "$OCSP_URL" \
  -resp_text

# 可能的状态:
# good     - 证书有效
# revoked  - 证书已吊销
# unknown  - 未知证书

5.7 证书管理自动化脚本

批量证书状态检查

#!/usr/bin/env bash
# cert-batch-check.sh - 批量检查证书状态
# 用法: ./cert-batch-check.sh hosts.txt

HOSTS_FILE="${1:?用法: $0 <hosts_file>}"
WARN_DAYS=30

printf "%-30s %-12s %-12s %-8s %s\n" "HOST" "NOT_BEFORE" "NOT_AFTER" "DAYS" "STATUS"
printf "%-30s %-12s %-12s %-8s %s\n" "----" "----------" "---------" "----" "------"

while IFS= read -r host || [ -n "$host" ]; do
  [ -z "$host" ] && continue
  
  # 获取证书信息
  CERT_INFO=$(echo | openssl s_client -connect "${host}:443" -servername "$host" \
    -verify 5 -CApath /etc/ssl/certs 2>/dev/null)
  
  VERIFY=$(echo "$CERT_INFO" | grep "Verify return code" | head -1)
  DATES=$(echo "$CERT_INFO" | openssl x509 -noout -dates 2>/dev/null)
  
  NOT_BEFORE=$(echo "$DATES" | grep notBefore | cut -d= -f2)
  NOT_AFTER=$(echo "$DATES" | grep notAfter | cut -d= -f2)
  
  if [ -n "$NOT_AFTER" ]; then
    EXPIRE_EPOCH=$(date -d "$NOT_AFTER" +%s 2>/dev/null)
    NOW_EPOCH=$(date +%s)
    DAYS_LEFT=$(( (EXPIRE_EPOCH - NOW_EPOCH) / 86400 ))
    
    if [ "$DAYS_LEFT" -gt "$WARN_DAYS" ]; then
      STATUS="✅ OK"
    elif [ "$DAYS_LEFT" -gt 0 ]; then
      STATUS="⚠️ 即将过期"
    else
      STATUS="❌ 已过期"
    fi
    
    printf "%-30s %-12s %-12s %-8d %s\n" \
      "$host" \
      "$(date -d "$NOT_BEFORE" +%Y-%m-%d 2>/dev/null)" \
      "$(date -d "$NOT_AFTER" +%Y-%m-%d 2>/dev/null)" \
      "$DAYS_LEFT" \
      "$STATUS"
  else
    printf "%-30s %-12s %-12s %-8s %s\n" \
      "$host" "-" "-" "-" "❌ 连接失败"
  fi
done < "$HOSTS_FILE"
# 创建主机列表
cat > hosts.txt << 'EOF'
www.baidu.com
github.com
www.google.com
expired.badssl.com
EOF

# 运行检查
chmod +x cert-batch-check.sh
./cert-batch-check.sh hosts.txt

证书过期告警

#!/usr/bin/env bash
# cert-alert.sh - 证书过期告警
# 用法: ./cert-alert.sh hosts.txt [warn_days] [alert_hook_url]

HOSTS_FILE="${1:?用法: $0 <hosts_file> [warn_days] [alert_hook_url]}"
WARN_DAYS="${2:-30}"
HOOK_URL="${3:-}"

ALERT_HOSTS=()

while IFS= read -r host || [ -n "$host" ]; do
  [ -z "$host" ] && continue
  
  NOT_AFTER=$(echo | openssl s_client -connect "${host}:443" -servername "$host" \
    2>/dev/null | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
  
  if [ -n "$NOT_AFTER" ]; then
    EXPIRE_EPOCH=$(date -d "$NOT_AFTER" +%s 2>/dev/null)
    NOW_EPOCH=$(date +%s)
    DAYS_LEFT=$(( (EXPIRE_EPOCH - NOW_EPOCH) / 86400 ))
    
    if [ "$DAYS_LEFT" -le "$WARN_DAYS" ]; then
      ALERT_HOSTS+=("${host}: ${DAYS_LEFT} 天")
    fi
  fi
done < "$HOSTS_FILE"

if [ ${#ALERT_HOSTS[@]} -gt 0 ]; then
  MESSAGE="⚠️ 以下证书即将过期或已过期:\n"
  for item in "${ALERT_HOSTS[@]}"; do
    MESSAGE+="  - ${item}\n"
  done
  
  echo -e "$MESSAGE"
  
  # 发送到 Webhook(如钉钉、飞书、企业微信)
  if [ -n "$HOOK_URL" ]; then
    curl -s -X POST "$HOOK_URL" \
      -H "Content-Type: application/json" \
      -d "{\"msgtype\":\"text\",\"text\":{\"content\":\"$(echo -e "$MESSAGE")\"}}"
  fi
fi

5.8 证书管理最佳实践

组织级证书管理清单

□ 建立证书清单(所有使用的证书列表)
□ 设置证书过期监控(提前 30/14/7 天告警)
□ 自动化证书续期(certbot / ACME)
□ 定期审计 CT 日志(发现未授权证书)
□ 建立应急响应流程(私钥泄露时的处理步骤)
□ 记录证书的密钥管理方式(HSM / Vault / 文件)
□ 定期轮换证书(建议每年至少一次)
□ 备份私钥和证书链(加密存储)

证书清单表

域名证书类型CA签发日期过期日期服务器管理方式
www.example.comOV WildcardDigiCert2025-01-012026-01-01Nginxcertbot
api.example.comDVLet’s Encrypt2025-04-012025-07-01Nginxcertbot
mail.example.comOVSectigo2024-06-012025-06-01Postfix手动

5.9 本章小结

主题关键要点
添加证书放入指定目录后执行更新命令
移除证书删除文件或在配置文件中禁用
证书续期推荐使用 certbot 自动续期
黑名单用于撤销对特定 CA 的信任
CT Log 监控发现未授权签发的证书
自动化批量检查 + 告警脚本

📚 扩展阅读


上一章第 4 章:系统证书存储 下一章第 6 章:OpenSSL 工具 — 掌握 OpenSSL 的核心命令,生成 CSR、自签名、验证和格式转换。