2026-09-23 09:40:59 +08:00
|
|
|
package acme
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
"github.com/go-acme/lego/v4/lego"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
CALetsEncrypt = "letsencrypt"
|
|
|
|
|
CALetsEncryptStaging = "letsencrypt-staging"
|
|
|
|
|
CAZeroSSL = "zerossl"
|
|
|
|
|
CABuypass = "buypass"
|
2026-09-27 13:44:03 +08:00
|
|
|
// CALiteSSL 亚数 TrustAsia 的公益免费证书(90 天,支持单域名/通配符/多域名,强制 EAB)
|
|
|
|
|
CALiteSSL = "litessl"
|
2026-09-23 09:40:59 +08:00
|
|
|
)
|
|
|
|
|
|
2026-09-27 10:10:15 +08:00
|
|
|
// caRetired 记录已停止签发的 CA:仍然认识这些值(历史证书记录里可能存着,用于展示与清理),
|
|
|
|
|
// 但不允许再用来申请——否则用户只会看到一个难懂的 ACME 报错而不是「这家 CA 不干了」。
|
|
|
|
|
var caRetired = map[string]string{
|
2026-09-27 13:44:03 +08:00
|
|
|
CABuypass: "Buypass 已于 2025-10-15 停止签发证书(ACME 服务 2026-04-15 终止),请改用 Let's Encrypt、LiteSSL 或 ZeroSSL",
|
2026-09-27 10:10:15 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CAUnavailableReason 返回该 CA 已停用的原因;可用的 CA 返回空字符串。
|
|
|
|
|
func CAUnavailableReason(ca string) string {
|
|
|
|
|
return caRetired[NormalizeCA(ca)]
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-23 09:40:59 +08:00
|
|
|
const (
|
|
|
|
|
zeroSSLDirectoryURL = "https://acme.zerossl.com/v2/DV90"
|
|
|
|
|
buypassDirectoryURL = "https://api.buypass.com/acme/directory"
|
2026-09-27 13:44:03 +08:00
|
|
|
// liteSSLDirectoryURL 实测 2026-09-27 可访问,目录里 meta.externalAccountRequired = true
|
|
|
|
|
liteSSLDirectoryURL = "https://acme.litessl.com/acme/v2/directory"
|
2026-09-23 09:40:59 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func NormalizeCA(ca string) string {
|
|
|
|
|
switch ca {
|
|
|
|
|
case "", CALetsEncrypt, "production":
|
|
|
|
|
return CALetsEncrypt
|
|
|
|
|
case CALetsEncryptStaging, "staging", "letsencrypt-test":
|
|
|
|
|
return CALetsEncryptStaging
|
|
|
|
|
case CAZeroSSL:
|
|
|
|
|
return CAZeroSSL
|
|
|
|
|
case CABuypass, "buypass-go":
|
|
|
|
|
return CABuypass
|
2026-09-27 13:44:03 +08:00
|
|
|
case CALiteSSL, "trustasia":
|
|
|
|
|
return CALiteSSL
|
2026-09-23 09:40:59 +08:00
|
|
|
default:
|
|
|
|
|
return ca
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func DirectoryURL(ca string) (string, error) {
|
|
|
|
|
switch NormalizeCA(ca) {
|
|
|
|
|
case CALetsEncrypt:
|
|
|
|
|
return lego.LEDirectoryProduction, nil
|
|
|
|
|
case CALetsEncryptStaging:
|
|
|
|
|
return lego.LEDirectoryStaging, nil
|
|
|
|
|
case CAZeroSSL:
|
|
|
|
|
return zeroSSLDirectoryURL, nil
|
|
|
|
|
case CABuypass:
|
|
|
|
|
return buypassDirectoryURL, nil
|
2026-09-27 13:44:03 +08:00
|
|
|
case CALiteSSL:
|
|
|
|
|
return liteSSLDirectoryURL, nil
|
2026-09-23 09:40:59 +08:00
|
|
|
default:
|
|
|
|
|
return "", fmt.Errorf("不支持的颁发机构:%s", ca)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func CALabel(ca string) string {
|
|
|
|
|
switch NormalizeCA(ca) {
|
|
|
|
|
case CALetsEncrypt:
|
|
|
|
|
return "Let's Encrypt"
|
|
|
|
|
case CALetsEncryptStaging:
|
|
|
|
|
return "Let's Encrypt 测试"
|
|
|
|
|
case CAZeroSSL:
|
|
|
|
|
return "ZeroSSL"
|
|
|
|
|
case CABuypass:
|
|
|
|
|
return "Buypass"
|
2026-09-27 13:44:03 +08:00
|
|
|
case CALiteSSL:
|
|
|
|
|
return "LiteSSL"
|
2026-09-23 09:40:59 +08:00
|
|
|
default:
|
|
|
|
|
if ca == "" {
|
|
|
|
|
return "手动导入"
|
|
|
|
|
}
|
|
|
|
|
return ca
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func ValidateCA(ca string) error {
|
|
|
|
|
_, err := DirectoryURL(ca)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-27 13:44:03 +08:00
|
|
|
// ValidateCADomains 校验各 CA 自己的限制。
|
|
|
|
|
// LiteSSL 官方支持单域名 / 通配符 / 多域名,因此这里不额外限制。
|
2026-09-23 09:40:59 +08:00
|
|
|
func ValidateCADomains(ca string, domains []string) error {
|
|
|
|
|
switch NormalizeCA(ca) {
|
|
|
|
|
case CABuypass:
|
|
|
|
|
if len(domains) > 5 {
|
|
|
|
|
return fmt.Errorf("Buypass 单张证书最多支持 5 个域名")
|
|
|
|
|
}
|
|
|
|
|
for _, domain := range domains {
|
|
|
|
|
if strings.HasPrefix(domain, "*.") {
|
|
|
|
|
return fmt.Errorf("Buypass 不支持通配符证书")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|