105 lines
3.1 KiB
Go
105 lines
3.1 KiB
Go
package certificate
|
|
|
|
import (
|
|
"context"
|
|
"crypto"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
acme "github.com/go-acme/lego/v5/acme"
|
|
"github.com/go-acme/lego/v5/certcrypto"
|
|
legoCertificate "github.com/go-acme/lego/v5/certificate"
|
|
"github.com/go-acme/lego/v5/challenge"
|
|
lego "github.com/go-acme/lego/v5/lego"
|
|
"github.com/go-acme/lego/v5/registration"
|
|
)
|
|
|
|
type AccountUser struct {
|
|
Email string
|
|
PrivateKey crypto.Signer
|
|
Registration *acme.ExtendedAccount
|
|
}
|
|
|
|
func (u *AccountUser) GetEmail() string { return u.Email }
|
|
func (u *AccountUser) GetRegistration() *acme.ExtendedAccount { return u.Registration }
|
|
func (u *AccountUser) GetPrivateKey() crypto.Signer { return u.PrivateKey }
|
|
|
|
type LegoIssuer struct {
|
|
User *AccountUser
|
|
Provider challenge.Provider
|
|
Environment string
|
|
HTTPClient *http.Client
|
|
Now func() time.Time
|
|
}
|
|
|
|
func (i *LegoIssuer) Obtain(ctx context.Context, request ObtainRequest) (Resource, error) {
|
|
if i == nil || i.User == nil || i.User.PrivateKey == nil {
|
|
return Resource{}, errors.New("ACME 账户未配置")
|
|
}
|
|
if i.Provider == nil {
|
|
return Resource{}, errors.New("DNS 验证服务商未配置")
|
|
}
|
|
domains, err := NormalizeDomains(request.Domains)
|
|
if err != nil {
|
|
return Resource{}, err
|
|
}
|
|
environment := strings.ToLower(strings.TrimSpace(request.Environment))
|
|
if environment == "" {
|
|
environment = "production"
|
|
}
|
|
caURL := lego.DirectoryURLLetsEncrypt
|
|
if environment == "staging" {
|
|
caURL = lego.DirectoryURLLetsEncryptStaging
|
|
} else if environment != "production" {
|
|
return Resource{}, fmt.Errorf("ACME 环境无效:%s", environment)
|
|
}
|
|
clientConfig := lego.NewConfig(i.User)
|
|
clientConfig.CADirURL = caURL
|
|
clientConfig.UserAgent = "Scert/0.1"
|
|
if i.HTTPClient != nil {
|
|
clientConfig.HTTPClient = i.HTTPClient
|
|
}
|
|
client, err := lego.NewClient(clientConfig)
|
|
if err != nil {
|
|
return Resource{}, fmt.Errorf("创建 ACME 客户端失败:%w", err)
|
|
}
|
|
if err := client.Challenge.SetDNS01Provider(i.Provider); err != nil {
|
|
return Resource{}, fmt.Errorf("配置 DNS-01 验证失败:%w", err)
|
|
}
|
|
if i.User.Registration == nil {
|
|
account, err := client.Registration.Register(ctx, registration.RegisterOptions{TermsOfServiceAgreed: true})
|
|
if err != nil {
|
|
return Resource{}, fmt.Errorf("注册 ACME 账户失败:%w", err)
|
|
}
|
|
i.User.Registration = account
|
|
}
|
|
issued, err := client.Certificate.Obtain(ctx, legoCertificate.ObtainRequest{
|
|
Domains: domains, EmailAddresses: nonEmptyEmail(request.AccountEmail, i.User.Email),
|
|
KeyType: certcrypto.RSA2048, Bundle: true,
|
|
PreferredChain: request.PreferredChain,
|
|
})
|
|
if err != nil {
|
|
return Resource{}, fmt.Errorf("申请证书失败:%w", err)
|
|
}
|
|
resource := Resource{
|
|
Domains: issued.Domains, Certificate: issued.Certificate,
|
|
IssuerCertificate: issued.IssuerCertificate, PrivateKey: issued.PrivateKey,
|
|
}
|
|
if i.Now != nil {
|
|
_ = i.Now()
|
|
}
|
|
return ValidateResource(resource)
|
|
}
|
|
|
|
func nonEmptyEmail(values ...string) []string {
|
|
for _, value := range values {
|
|
if strings.TrimSpace(value) != "" {
|
|
return []string{strings.TrimSpace(value)}
|
|
}
|
|
}
|
|
return nil
|
|
}
|