windows在cygwin64下使用acme.sh批量签发Let's Encrypt的ssl证书,并用powershell重新分配iis证书
使用前提
本脚本是在使用阿里云Windows服务器的前提,如果使用其他dns服务,请参看acme.sh的dns相关文档
配置好cygwin64、acme.sh并配置好阿里云账户,openssl最好也安装上
cygwin64配置
如果windows server 08R2启动安装程序失败,请使用cmd运行
setup-x86_64.exe --allow-unsupported-windows --site http://ctm.crouchingtigerhiddenfruitbat.org/pub/cygwin/circa/64bit/2024/01/30/231215 --no-verify
其他老旧系统请参考cygwin64官网网页的How can I install the last Cygwin version for an old, unsupported Windows回答
acme.sh配置
openssl参考,添加-certpbe PBE-SHA1-3DES -keypbe PBE-SHA1-3DES -nomac 是为了应对pfx输入密钥不正确
最终路径就是项目路径
以下是PowerShell脚本,请保存在autoacme.ps1文件中
# 公共证书备份路径,务必带盘符
$commonPath = "E:\cert"
# cygwin64用户路径
$userPath = "C:\Cygwin64\home\Administrator"
# cygwin64内部用户路径
$cygwinUserPath = "/home/Administrator"
# pfx文件密钥
$pfxPassword = "dgfdgsdfg"
# 证书在以下列表中添加即可
$data = @(
[pscustomobject]@{
domain = "buy.test.com";
path = "D:\Web\Main"
},
[pscustomobject]@{
domain = "go.test.com";
},
[pscustomobject]@{
domain = "*.test.com";
}
)
# 如果公共路径不存在,那么创建,如果路径已存在,不影响命令继续执行
if (![System.IO.Directory]::Exists($commonPath)) {
md $commonPath
}
# 获取公共证书备份路径在cygwin64环境下的路径
$cygwinCommonPath = "/cygdrive/" + $commonPath.Replace(":", "").Replace("\", "/");
# 重试次数
$retryCnt = 0
function IssueKey {
param (
[string]$currDomain,
[string]$currPath,
[bool]$force
)
# 如果重试次数大于2,那么退出当前函数
if ($retryCnt -gt 2) {
return
}
# 替换特殊路径名
$domain = $currDomain.Replace("*", "_")
$cygDomain = $currDomain.Replace("*", "\*")
# 设置执行命令后缀,这里是acme.sh相关命令,修改dns api就在这里
$issueCmd = "--issue --dns dns_ali -d $($currDomain) --domain-alias $($currDomain) --key-file $($cygwinCommonPath)/$($domain).key --fullchain-file $($cygwinCommonPath)/$($domain)_fullchain.cer"
#如果是强制重发
if ($force) {
$issueCmd += " --force"
}
Write-Host 被执行的acme.sh后缀命令 $issueCmd
bash --login -i -c "acme.sh $($issueCmd)"
Write-Host 检查$currDomain key文件大小和backup目录是否存在文件
$commonFullChainPath = "$($commonPath)\$($domain)_fullchain.cer"
$commonKeyPath = "$($commonPath)\$($domain).key"
$commonPfxPath = "$($commonPath)\$($domain).pfx"
$commonPemPath = "$($commonPath)\$($domain).pem"
# cygwin环境下的目录
$cygwinCertPath = "$($cygwinUserPath)/.acme.sh/$($cygDomain)_ecc"
Write-Host 赋予权限
bash --login -i -c "chmod -R g+rw $($cygwinCertPath)"
Write-Host 拷贝Key、Fullchain文件到公共目录
bash --login -i -c "cp -f $($cygwinCertPath)/$($cygDomain).key $($cygwinCommonPath)/$($domain).key"
bash --login -i -c "cp -f $($cygwinCertPath)/fullchain.cer $($cygwinCommonPath)/$($domain)_fullchain.cer"
# 判断绝对路径下证书文件是否存在,如果不存在直接强制重新生成证书
Write-Host 第一次检查$commonKeyPath 文件是否存在
if (![System.IO.File]::Exists($commonKeyPath)) {
Write-Host 检查key.bak是否存在
# 尝试从备份中恢复文件到原目录
bash --login -i -c "cp -f $($cygwinCertPath)/backup/key.bak $($cygwinCertPath)/$($cygDomain).key"
# 尝试从原目录拷贝文件到公共目录
bash --login -i -c "cp -f $($cygwinCertPath)/$($cygDomain).key $($cygwinCommonPath)/$($domain).key"
Write-Host 第二次检查$commonKeyPath 文件是否存在
if (![System.IO.File]::Exists($commonKeyPath)) {
Write-Host 公共路径证书文件不存在 $commonKeyPath 即将强制重新申请
# 重试次数+1
$retryCnt += 1
IssueKey -currDomain $currDomain -currPath $currPath -force $true
}
}
Write-Host 第一次检查 $($domain).pfx 文件是否存在
if (![System.IO.File]::Exists($commonPfxPath)) {
Write-Host openssl转换pfx
# openssl 3.x 版本
# bash --login -i -c "openssl pkcs12 -export -certpbe PBE-SHA1-3DES -keypbe PBE-SHA1-3DES -nomac -out $($cygwinCommonPath)/$($domain).pfx -inkey $($cygwinCertPath)/$($cygDomain).key -in $($cygwinCertPath)/$($cygDomain).cer -certfile $($cygwinCertPath)/fullchain.cer -password pass:$($pfxPassword)"
# openssl 1.0 版本
openssl pkcs12 -export -out $commonPfxPath -inkey $commonKeyPath -in $commonFullChainPath -password pass:$pfxPassword
}
Write-Host 第二次检查 $($domain).pfx 文件是否存在
if (![System.IO.File]::Exists($commonPfxPath)) {
# 如果重试次数大于2,那么退出当前函数
if ($retryCnt -gt 2) {
return
}
else {
# 重试次数+1
$retryCnt += 1
IssueKey -currDomain $currDomain -currPath $currPath -force $true
}
}
# 如果pem格式文件不存在,那么使用openssl转换成pem格式
if (![System.IO.File]::Exists($commonPemPath)) {
Write-Host openssl转换pem
openssl pkcs12 -in $commonPfxPath -out $commonPemPath -nodes -password pass:$pfxPassword
}
# 如果对象path不为空且存在,将证书拷贝到项目路径下
if (![string]::IsNullOrEmpty($currPath)) {
Write-Host 拷贝$domain 证书文件到项目目录
Copy-Item -Path $commonKeyPath -Destination "$($currPath)\$($domain).key" -Force
Copy-Item -Path $commonPfxPath -Destination "$($currPath)\$($domain).pfx" -Force
Copy-Item -Path $commonPemPath -Destination "$($currPath)\$($domain).pem" -Force
}
}
foreach ($curr in $data) {
# 每次弄新的,就重置次数
$retryCnt = 0
try {
# 登录到cygwin使用acme.sh签发证书,并将文件拷贝到公共证书目录,并转成pfx格式,密码统一使用$pfxPassword
Write-Host 地址 $curr.path
Write-Host 签发 $curr.domain 证书
IssueKey -currDomain $curr.domain -currPath $curr.path -force $false
}
catch {
Write-Host "发生异常:$_"
break
}
}
# 执行完后退出
exit
PowerShell 脚本,使用前,更改执行策略
Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope CurrentUser
将以下脚本保存为reissueIISCert.ps1文件
# 使用前先将策略设置为不严格 Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Scope CurrentUser
# 保证证书有效的情况下再运行次脚本,将证书名称、证书目录、密钥放入以下数组
# 公共证书密钥
$pfxpassword = "dgfdgsdfg"
# 公共证书路径
$pfxCommandDir= "E:\cert"
# 域名
$domain="test.com"
# 服务器上的证书与端口映射关系
$data = @(
[pscustomobject]@{subDomain = '*';port=443}
[pscustomobject]@{subDomain = 'buy';port=8443}
[pscustomobject]@{subDomain = 'go';port=7443}
)
$certRootStore = "LocalMachine"
$certStore = "My"
# 创建证书存储
$store = new-object System.Security.Cryptography.X509Certificates.X509Store($certStore, $certRootStore)
$store.open("MaxAllowed")
# 开始循环数组操作
foreach ($element in $data) {
$currDomain=$element.subDomain.Replace("*","_");
$pfxPath = "$($pfxCommandDir)\$($currDomain).$($domain).pfx"
Write-Host $pfxPath
# 创建pfx对象
try {
$certificateObject = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($pfxPath, $pfxpassword)
if (!$certificateObject.Verify()) {
Write-Host $element.subDomain证书验证失败, 请检查相关配置
break
}
else {
Write-Host 证书验证成功
}
# 存储证书到个人
if (!$store.Certificates.Contains($certificateObject)) {
$store.Add($certificateObject)
Write-Host 导入证书成功
}
$newThumbprint = $certificateObject.Thumbprint
Write-Host 获取证书信息成功
$guid = [System.Guid]::NewGuid()
$applicationID = "{$($guid)}"
$addr = "0.0.0.0:$($element.port)"
Write-Host $addr $newThumbprint
# netsh删除原有监听端口
netsh http delete sslcert ipport=$addr
# netsh添加端口
netsh http add sslcert ipport=$addr certhash=$newThumbprint appid=$applicationID
# 如果对象path不为空且存在,将证书拷贝到项目路径下
if (![string]::IsNullOrEmpty($element.path)) {
$dest = "$($element.path)\$($currDomain).$($domain).pfx"
Copy-Item -Path $pfxPath -Destination $dest -Force
Write-Host 拷贝文件到项目目录成功
}
}
catch {
Write-Host "发生异常:$_"
break
}
}
# 关闭证书存储
$store.close()
# 执行完后退出
exit
创建任务计划程序参考
在常规页面中,勾选“只在用户登录时运行”以及“使用最高权限”,保存即可
任务计划程序是按照顺序执行的。