如何编写自动清理无用证书脚本

wen 实用脚本 1

本文目录导读:

如何编写自动清理无用证书脚本

  1. Linux 系统证书清理脚本
  2. Windows 系统证书清理脚本 (PowerShell)
  3. 使用方法
  4. 定时任务设置

我来帮你编写一个自动清理无用证书的脚本,以下是针对不同系统的实现方案:

Linux 系统证书清理脚本

#!/bin/bash
# 证书清理脚本
# 功能:自动清理过期、无效或未使用的证书
# 配置参数
CERT_PATHS=(
    "/etc/ssl/certs"
    "/etc/pki/tls/certs"
    "/usr/local/share/ca-certificates"
    "/usr/share/ca-certificates"
)
LOG_FILE="/var/log/cert-cleanup.log"
DRY_RUN=false  # 设为 true 进行预检查,不实际删除
# 日志函数
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
# 检查证书是否过期
check_expired() {
    local cert_file="$1"
    local expire_date=$(openssl x509 -enddate -noout -in "$cert_file" 2>/dev/null | cut -d= -f2)
    local expire_epoch=$(date -d "$expire_date" +%s 2>/dev/null)
    local current_epoch=$(date +%s)
    if [ -n "$expire_epoch" ] && [ "$expire_epoch" -lt "$current_epoch" ]; then
        return 0  # 已过期
    fi
    return 1  # 未过期
}
# 检查证书是否有效
check_valid() {
    local cert_file="$1"
    # 使用 openssl 验证证书
    openssl x509 -noout -in "$cert_file" 2>/dev/null
    if [ $? -ne 0 ]; then
        return 0  # 无效
    fi
    # 检查证书链
    openssl verify -CApath /etc/ssl/certs "$cert_file" 2>/dev/null
    if [ $? -ne 0 ]; then
        return 1  # 证书链问题
    fi
    return 2  # 完全有效
}
# 查找并清理证书
cleanup_certs() {
    local cleaned=0
    local expired=0
    local invalid=0
    log "开始清理证书..."
    for cert_dir in "${CERT_PATHS[@]}"; do
        if [ ! -d "$cert_dir" ]; then
            log "目录不存在: $cert_dir"
            continue
        fi
        log "检查目录: $cert_dir"
        # 查找所有证书文件
        find "$cert_dir" -type f \( -name "*.crt" -o -name "*.pem" -o -name "*.cert" \) 2>/dev/null | while read cert; do
            # 跳过符号链接
            if [ -L "$cert" ]; then
                continue
            fi
            # 检查过期
            if check_expired "$cert"; then
                expired=$((expired + 1))
                log "过期证书: $cert"
                if [ "$DRY_RUN" = false ]; then
                    rm -f "$cert"
                    cleaned=$((cleaned + 1))
                fi
                continue
            fi
            # 检查有效性
            check_valid "$cert"
            case $? in
                0)
                    invalid=$((invalid + 1))
                    log "无效证书: $cert"
                    if [ "$DRY_RUN" = false ]; then
                        rm -f "$cert"
                        cleaned=$((cleaned + 1))
                    fi
                    ;;
            esac
        done
    done
    log "清理完成: 清理 $cleaned 个证书"
    log "  - 过期证书: $expired"
    log "  - 无效证书: $invalid"
}
# 更新证书存储
update_cert_store() {
    log "更新证书存储..."
    # Debian/Ubuntu
    if command -v update-ca-certificates &> /dev/null; then
        update-ca-certificates --fresh 2>&1 | tee -a "$LOG_FILE"
    fi
    # CentOS/RHEL/Fedora
    if command -v update-ca-trust &> /dev/null; then
        update-ca-trust extract 2>&1 | tee -a "$LOG_FILE"
    fi
}
# 清理重复证书
cleanup_duplicates() {
    log "检查重复证书..."
    local hash_map=()
    for cert_dir in "${CERT_PATHS[@]}"; do
        if [ ! -d "$cert_dir" ]; then
            continue
        fi
        find "$cert_dir" -type f \( -name "*.crt" -o -name "*.pem" \) 2>/dev/null | while read cert; do
            if [ -f "$cert" ]; then
                local hash=$(openssl x509 -noout -hash -in "$cert" 2>/dev/null)
                if [ -n "$hash" ]; then
                    if [[ " ${hash_map[@]} " =~ " ${hash} " ]]; then
                        log "发现重复证书: $cert"
                        if [ "$DRY_RUN" = false ]; then
                            rm -f "$cert"
                        fi
                    else
                        hash_map+=("$hash")
                    fi
                fi
            fi
        done
    done
}
# 主函数
main() {
    log "=== 证书清理脚本开始 ==="
    # 检查 root 权限
    if [ "$EUID" -ne 0 ]; then
        log "错误: 需要 root 权限运行此脚本"
        exit 1
    fi
    # 处理命令行参数
    while [[ $# -gt 0 ]]; do
        case $1 in
            --dry-run)
                DRY_RUN=true
                log "运行在预览模式(不实际删除)"
                ;;
            --help)
                echo "用法: $0 [选项]"
                echo "选项:"
                echo "  --dry-run  预览模式,不实际删除"
                echo "  --help     显示此帮助信息"
                exit 0
                ;;
            *)
                echo "未知选项: $1"
                exit 1
                ;;
        esac
        shift
    done
    # 执行清理
    cleanup_certs
    cleanup_duplicates
    update_cert_store
    log "=== 证书清理脚本结束 ==="
}
# 运行主函数
main "$@"

Windows 系统证书清理脚本 (PowerShell)

# Windows 证书清理脚本
param(
    [switch]$DryRun = $false
)
# 配置
$LogFile = "C:\Windows\Temp\cert-cleanup.log"
$CurrentDate = Get-Date
# 日志函数
function Write-Log {
    param([string]$Message)
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
    "$timestamp - $Message" | Out-File -FilePath $LogFile -Append
    Write-Host "$timestamp - $Message"
}
# 检查证书是否过期
function Test-CertExpired {
    param(
        [System.Security.Cryptography.X509Certificates.X509Certificate2]$Cert
    )
    if ($Cert.NotAfter -lt $CurrentDate) {
        return $true
    }
    return $false
}
# 清理指定存储中的证书
function Cleanup-CertStore {
    param(
        [string]$StoreName,
        [string]$StoreLocation
    )
    Write-Log "检查证书存储: $StoreName ($StoreLocation)"
    try {
        $store = New-Object System.Security.Cryptography.X509Certificates.X509Store(
            $StoreName, 
            [System.Security.Cryptography.X509Certificates.StoreLocation]::$StoreLocation
        )
        $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
        $cleanedCount = 0
        foreach ($cert in $store.Certificates) {
            $removeCert = $false
            $reason = ""
            # 检查过期
            if (Test-CertExpired -Cert $cert) {
                $removeCert = $true
                $reason = "已过期 (过期日: $($cert.NotAfter))"
            }
            # 检查自签名证书(非根证书)
            if ($cert.Subject -eq $cert.Issuer -and $StoreName -ne "Root") {
                # 可以自定义规则
            }
            if ($removeCert) {
                Write-Log "  清理证书: $($cert.Subject) - $reason"
                if (-not $DryRun) {
                    try {
                        $store.Remove($cert)
                        $cleanedCount++
                    }
                    catch {
                        Write-Log "  错误: 无法删除证书 - $($_.Exception.Message)"
                    }
                }
                else {
                    $cleanedCount++
                }
            }
        }
        $store.Close()
        Write-Log "  完成: 清理 $cleanedCount 个证书"
    }
    catch {
        Write-Log "  错误: $($_.Exception.Message)"
    }
}
# 清理用户和计算机证书存储
function Cleanup-AllStores {
    Write-Log "开始清理 Windows 证书存储..."
    $stores = @(
        @{Name="My"; Location="CurrentUser"},
        @{Name="My"; Location="LocalMachine"},
        @{Name="Root"; Location="CurrentUser"},
        @{Name="Root"; Location="LocalMachine"},
        @{Name="CA"; Location="CurrentUser"},
        @{Name="CA"; Location="LocalMachine"},
        @{Name="TrustedPublisher"; Location="CurrentUser"},
        @{Name="TrustedPublisher"; Location="LocalMachine"},
        @{Name="Disallowed"; Location="CurrentUser"},
        @{Name="Disallowed"; Location="LocalMachine"}
    )
    foreach ($store in $stores) {
        Cleanup-CertStore -StoreName $store.Name -StoreLocation $store.Location
    }
}
# 清理过期且未使用的根证书
function Cleanup-UnusedRootCerts {
    Write-Log "检查未使用的根证书..."
    try {
        $store = New-Object System.Security.Cryptography.X509Certificates.X509Store(
            "Root",
            [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine
        )
        $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
        foreach ($cert in $store.Certificates) {
            # 检查是否为微软根证书计划中的证书
            $isMicrosoftCert = $cert.Issuer -match "Microsoft"
            # 如果证书已过期且不是微软证书
            if ((Test-CertExpired -Cert $cert) -and -not $isMicrosoftCert) {
                if (-not $DryRun) {
                    Write-Log "  删除过期非微软根证书: $($cert.Subject)"
                    $store.Remove($cert)
                }
                else {
                    Write-Log "  [预览] 将删除过期非微软根证书: $($cert.Subject)"
                }
            }
        }
        $store.Close()
    }
    catch {
        Write-Log "  错误: $($_.Exception.Message)"
    }
}
# 主函数
function Main {
    Write-Log "========================================"
    Write-Log "Windows 证书清理脚本开始"
    Write-Log "运行模式: $(if ($DryRun) { "预览模式" } else { "实际执行" })"
    if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
        [Security.Principal.WindowsBuiltInRole]::Administrator)) {
        Write-Log "错误: 需要管理员权限运行此脚本"
        exit 1
    }
    Cleanup-AllStores
    Cleanup-UnusedRootCerts
    Write-Log "证书清理完成"
    Write-Log "========================================"
}
# 执行主函数
Main

使用方法

Linux 版本:

# 赋予执行权限
chmod +x cleanup-certs.sh
# 预览模式(不实际删除)
sudo ./cleanup-certs.sh --dry-run
# 实际执行
sudo ./cleanup-certs.sh
# 查看日志
cat /var/log/cert-cleanup.log

Windows 版本:

# 以管理员身份运行 PowerShell
Set-ExecutionPolicy Bypass -Scope Process
# 预览模式
.\cleanup-certs.ps1 -DryRun
# 实际执行
.\cleanup-certs.ps1
# 查看日志
Get-Content C:\Windows\Temp\cert-cleanup.log

定时任务设置

Linux (cron):

# 每月1号凌晨3点执行
0 3 1 * * /root/scripts/cleanup-certs.sh >> /var/log/cert-cleanup-cron.log 2>&1

Windows (Task Scheduler):

# 创建定时任务
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-File C:\scripts\cleanup-certs.ps1"
$trigger = New-ScheduledTaskTrigger -Monthly -DaysOfMonth 1 -At 3am
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -TaskName "Certificate Cleanup" -Action $action -Trigger $trigger -Principal $principal

这个脚本会自动:

  1. 检查并清理过期的证书
  2. 清理无效或损坏的证书
  3. 清理重复的证书
  4. 提供预览模式用于安全检查
  5. 生成详细的日志记录

使用时请先在预览模式测试,确认不会误删重要证书后再实际执行。

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