mirror of
https://github.com/usual2970/certimate.git
synced 2025-06-08 21:49:52 +00:00
207 lines
5.4 KiB
Go
207 lines
5.4 KiB
Go
package applicant
|
||
|
||
import (
|
||
"context"
|
||
"crypto"
|
||
"crypto/ecdsa"
|
||
"crypto/elliptic"
|
||
"crypto/rand"
|
||
"fmt"
|
||
"strings"
|
||
|
||
"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 {
|
||
// 证书颁发机构标识。
|
||
// 通常等同于 [CAProviderType] 的值。
|
||
// 对于自定义 ACME CA,值为 "custom#{access_id}"。
|
||
CA string
|
||
// 邮箱。
|
||
Email string
|
||
// 注册信息。
|
||
Registration *registration.Resource
|
||
|
||
// CSR 私钥。
|
||
privkey string
|
||
}
|
||
|
||
func newAcmeUser(ca, caAccessId, email string) (*acmeUser, error) {
|
||
repo := repository.NewAcmeAccountRepository()
|
||
|
||
applyUser := &acmeUser{
|
||
CA: ca,
|
||
Email: email,
|
||
}
|
||
if ca == caCustom {
|
||
applyUser.CA = fmt.Sprintf("%s#%s", ca, caAccessId)
|
||
}
|
||
|
||
acmeAccount, err := repo.GetByCAAndEmail(applyUser.CA, applyUser.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) getCAProvider() string {
|
||
return strings.Split(u.CA, "#")[0]
|
||
}
|
||
|
||
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.getCAProvider() {
|
||
case caLetsEncrypt, caLetsEncryptStaging:
|
||
reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||
|
||
case caBuypass:
|
||
{
|
||
reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||
}
|
||
|
||
case caGoogleTrustServices:
|
||
{
|
||
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 caSSLCom:
|
||
{
|
||
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 caZeroSSL:
|
||
{
|
||
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,
|
||
})
|
||
}
|
||
|
||
case caCustom:
|
||
{
|
||
access := domain.AccessConfigForACMECA{}
|
||
if err := maputil.Populate(userRegisterOptions, &access); err != nil {
|
||
return nil, fmt.Errorf("failed to populate provider access config: %w", err)
|
||
}
|
||
|
||
if access.EabKid == "" && access.EabHmacKey == "" {
|
||
reg, err = client.Registration.Register(registration.RegisterOptions{
|
||
TermsOfServiceAgreed: true,
|
||
})
|
||
} else {
|
||
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
|
||
}
|