package applicant import ( "context" "crypto" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "fmt" "github.com/go-acme/lego/v4/lego" "github.com/go-acme/lego/v4/registration" "golang.org/x/sync/singleflight" "github.com/usual2970/certimate/internal/domain" certutil "github.com/usual2970/certimate/internal/pkg/utils/cert" maputil "github.com/usual2970/certimate/internal/pkg/utils/map" "github.com/usual2970/certimate/internal/repository" ) type acmeUser struct { CA string Email string Registration *registration.Resource privkey string } func newAcmeUser(ca, email string) (*acmeUser, error) { repo := repository.NewAcmeAccountRepository() applyUser := &acmeUser{ CA: ca, Email: email, } acmeAccount, err := repo.GetByCAAndEmail(ca, email) if err != nil { key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return nil, err } keyPEM, err := certutil.ConvertECPrivateKeyToPEM(key) if err != nil { return nil, err } applyUser.privkey = keyPEM return applyUser, nil } applyUser.Registration = acmeAccount.Resource applyUser.privkey = acmeAccount.Key return applyUser, nil } func (u *acmeUser) GetEmail() string { return u.Email } func (u acmeUser) GetRegistration() *registration.Resource { return u.Registration } func (u *acmeUser) GetPrivateKey() crypto.PrivateKey { rs, _ := certutil.ParseECPrivateKeyFromPEM(u.privkey) return rs } func (u *acmeUser) hasRegistration() bool { return u.Registration != nil } func (u *acmeUser) getPrivateKeyPEM() string { return u.privkey } var registerGroup singleflight.Group func registerAcmeUserWithSingleFlight(client *lego.Client, user *acmeUser, userRegisterOptions map[string]any) (*registration.Resource, error) { resp, err, _ := registerGroup.Do(fmt.Sprintf("register_acme_user_%s_%s", user.CA, user.Email), func() (interface{}, error) { return registerAcmeUser(client, user, userRegisterOptions) }) if err != nil { return nil, err } return resp.(*registration.Resource), nil } func registerAcmeUser(client *lego.Client, user *acmeUser, userRegisterOptions map[string]any) (*registration.Resource, error) { var reg *registration.Resource var err error switch user.CA { case sslProviderLetsEncrypt, sslProviderLetsEncryptStaging: reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) case sslProviderBuypass: { reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) } case sslProviderGoogleTrustServices: { access := domain.AccessConfigForGoogleTrustServices{} if err := maputil.Populate(userRegisterOptions, &access); err != nil { return nil, fmt.Errorf("failed to populate provider access config: %w", err) } reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{ TermsOfServiceAgreed: true, Kid: access.EabKid, HmacEncoded: access.EabHmacKey, }) } case sslProviderSSLCom: { access := domain.AccessConfigForSSLCom{} if err := maputil.Populate(userRegisterOptions, &access); err != nil { return nil, fmt.Errorf("failed to populate provider access config: %w", err) } reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{ TermsOfServiceAgreed: true, Kid: access.EabKid, HmacEncoded: access.EabHmacKey, }) } case sslProviderZeroSSL: { access := domain.AccessConfigForZeroSSL{} if err := maputil.Populate(userRegisterOptions, &access); err != nil { return nil, fmt.Errorf("failed to populate provider access config: %w", err) } reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{ TermsOfServiceAgreed: true, Kid: access.EabKid, HmacEncoded: access.EabHmacKey, }) } default: err = fmt.Errorf("unsupported ca provider '%s'", user.CA) } if err != nil { return nil, err } repo := repository.NewAcmeAccountRepository() resp, err := repo.GetByCAAndEmail(user.CA, user.Email) if err == nil { user.privkey = resp.Key return resp.Resource, nil } if _, err := repo.Save(context.Background(), &domain.AcmeAccount{ CA: user.CA, Email: user.Email, Key: user.getPrivateKeyPEM(), Resource: reg, }); err != nil { return nil, fmt.Errorf("failed to save acme account registration: %w", err) } return reg, nil }