mirror of
https://github.com/certimate-go/certimate.git
synced 2026-06-22 21:05:48 +08:00
129 lines
3.4 KiB
Go
129 lines
3.4 KiB
Go
package certapply
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/go-acme/lego/v4/lego"
|
|
"github.com/go-acme/lego/v4/registration"
|
|
"golang.org/x/sync/singleflight"
|
|
|
|
"github.com/certimate-go/certimate/internal/domain"
|
|
"github.com/certimate-go/certimate/internal/repository"
|
|
xcert "github.com/certimate-go/certimate/pkg/utils/cert"
|
|
)
|
|
|
|
var registrationSg singleflight.Group
|
|
|
|
type ACMEAccount = domain.ACMEAccount
|
|
|
|
func NewACMEAccount(config *ACMEConfig, email string, register bool) (*ACMEAccount, error) {
|
|
if config == nil {
|
|
return nil, errors.New("the acme config is nil")
|
|
}
|
|
if email == "" {
|
|
return nil, errors.New("the email is empty")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
accountRepo := repository.NewACMEAccountRepository()
|
|
account, err := accountRepo.GetByCAAndEmail(ctx, string(config.CAProvider), config.CADirUrl, email)
|
|
if err != nil {
|
|
if !domain.IsRecordNotFoundError(err) {
|
|
return nil, fmt.Errorf("failed to get acme account record: %w", err)
|
|
}
|
|
}
|
|
|
|
// register new acme account if not exists
|
|
if account == nil {
|
|
if !register {
|
|
return nil, errors.New("the acme account does not exist")
|
|
}
|
|
|
|
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
keyPEM, err := xcert.ConvertECPrivateKeyToPEM(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
account = &ACMEAccount{
|
|
CA: string(config.CAProvider),
|
|
Email: email,
|
|
PrivateKey: keyPEM,
|
|
ACMEDirUrl: config.CADirUrl,
|
|
}
|
|
legoCfg := lego.NewConfig(account)
|
|
legoCfg.CADirURL = config.CADirUrl
|
|
legoClient, err := lego.NewClient(legoCfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var regres *registration.Resource
|
|
var regerr error
|
|
if legoClient.GetExternalAccountRequired() {
|
|
if config.EABKid == "" {
|
|
return nil, errors.New("missing or invalid eab kid")
|
|
}
|
|
if config.EABHmacKey == "" {
|
|
return nil, errors.New("missing or invalid eab hmac key")
|
|
}
|
|
|
|
// patch, see https://github.com/go-acme/lego/issues/2634
|
|
keyId := strings.TrimSpace(config.EABKid)
|
|
keyEncoded := strings.TrimSpace(config.EABHmacKey)
|
|
keyEncoded = strings.ReplaceAll(strings.ReplaceAll(keyEncoded, "+", "-"), "/", "_")
|
|
keyEncoded = strings.TrimRight(keyEncoded, "=")
|
|
|
|
regres, regerr = legoClient.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
|
|
TermsOfServiceAgreed: true,
|
|
Kid: keyId,
|
|
HmacEncoded: keyEncoded,
|
|
})
|
|
} else {
|
|
regres, regerr = legoClient.Registration.Register(registration.RegisterOptions{
|
|
TermsOfServiceAgreed: true,
|
|
})
|
|
}
|
|
if regerr != nil {
|
|
return nil, fmt.Errorf("failed to register acme account: %w", regerr)
|
|
}
|
|
|
|
account.ACMEAccount = ®res.Body
|
|
account.ACMEAcctUrl = regres.URI
|
|
|
|
if _, err := accountRepo.Save(ctx, account); err != nil {
|
|
return nil, fmt.Errorf("failed to save acme account record: %w", err)
|
|
}
|
|
}
|
|
|
|
return account, nil
|
|
}
|
|
|
|
func NewACMEAccountWithSingleFlight(config *ACMEConfig, email string) (*ACMEAccount, error) {
|
|
if config == nil {
|
|
return nil, errors.New("the acme config is nil")
|
|
}
|
|
if email == "" {
|
|
return nil, errors.New("the email is empty")
|
|
}
|
|
|
|
resp, err, _ := registrationSg.Do(fmt.Sprintf("%s|%s|%s", string(config.CAProvider), config.CADirUrl, email), func() (any, error) {
|
|
return NewACMEAccount(config, email, true)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return resp.(*ACMEAccount), nil
|
|
}
|