110 lines
3.2 KiB
Go
110 lines
3.2 KiB
Go
package certificate
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/go-acme/lego/v5/acme"
|
|
"github.com/go-acme/lego/v5/certcrypto"
|
|
)
|
|
|
|
type AccountSecretStore interface {
|
|
Put(ctx context.Context, reference string, value map[string]string) error
|
|
Get(ctx context.Context, reference string) (map[string]string, error)
|
|
Has(ctx context.Context, reference string) (bool, error)
|
|
}
|
|
|
|
type AccountRepository struct {
|
|
store AccountSecretStore
|
|
}
|
|
|
|
func NewAccountRepository(store AccountSecretStore) *AccountRepository {
|
|
return &AccountRepository{store: store}
|
|
}
|
|
|
|
func (r *AccountRepository) LoadOrCreate(ctx context.Context, environment, email string) (*AccountUser, error) {
|
|
if r == nil || r.store == nil {
|
|
return nil, errors.New("ACME 账户存储未初始化")
|
|
}
|
|
reference, err := accountReference(environment)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
exists, err := r.store.Has(ctx, reference)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("检查 ACME 账户失败:%w", err)
|
|
}
|
|
if !exists {
|
|
key, err := certcrypto.GeneratePrivateKey(certcrypto.EC256)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("生成 ACME 账户私钥失败:%w", err)
|
|
}
|
|
user := &AccountUser{Email: strings.TrimSpace(email), PrivateKey: key}
|
|
if err := r.Save(ctx, environment, user); err != nil {
|
|
return nil, err
|
|
}
|
|
return user, nil
|
|
}
|
|
|
|
payload, err := r.store.Get(ctx, reference)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("读取 ACME 账户失败:%w", err)
|
|
}
|
|
key, err := certcrypto.ParsePEMPrivateKey([]byte(payload["private_key"]))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("解析 ACME 账户私钥失败:%w", err)
|
|
}
|
|
storedEmail := strings.TrimSpace(payload["email"])
|
|
if strings.TrimSpace(email) != "" {
|
|
storedEmail = strings.TrimSpace(email)
|
|
}
|
|
user := &AccountUser{Email: storedEmail, PrivateKey: key}
|
|
if value := strings.TrimSpace(payload["registration"]); value != "" {
|
|
var registration acme.ExtendedAccount
|
|
if err := json.Unmarshal([]byte(value), ®istration); err != nil {
|
|
return nil, fmt.Errorf("解析 ACME 注册状态失败:%w", err)
|
|
}
|
|
user.Registration = ®istration
|
|
}
|
|
return user, nil
|
|
}
|
|
|
|
func (r *AccountRepository) Save(ctx context.Context, environment string, user *AccountUser) error {
|
|
if r == nil || r.store == nil {
|
|
return errors.New("ACME 账户存储未初始化")
|
|
}
|
|
if user == nil || user.PrivateKey == nil {
|
|
return errors.New("ACME 账户私钥未配置")
|
|
}
|
|
reference, err := accountReference(environment)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
payload := map[string]string{
|
|
"email": strings.TrimSpace(user.Email),
|
|
"private_key": string(certcrypto.PEMEncode(user.PrivateKey)),
|
|
}
|
|
if user.Registration != nil {
|
|
value, err := json.Marshal(user.Registration)
|
|
if err != nil {
|
|
return fmt.Errorf("编码 ACME 注册状态失败:%w", err)
|
|
}
|
|
payload["registration"] = string(value)
|
|
}
|
|
if err := r.store.Put(ctx, reference, payload); err != nil {
|
|
return fmt.Errorf("保存 ACME 账户失败:%w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func accountReference(environment string) (string, error) {
|
|
environment = strings.ToLower(strings.TrimSpace(environment))
|
|
if environment != "staging" && environment != "production" {
|
|
return "", fmt.Errorf("ACME 环境无效:%s", environment)
|
|
}
|
|
return "secret://acme/letsencrypt/" + environment, nil
|
|
}
|