package acmecert import ( "crypto" "encoding/json" "os" "path/filepath" "github.com/go-acme/lego/v4/certcrypto" "github.com/go-acme/lego/v4/registration" ) // acmeUser implements registration.User. Its private key and (once registered) // registration resource are persisted to disk under a data directory, mirroring the // generate-if-missing file pattern already used by mailstore.LoadOrCreateMasterKey and // tlsutil.GenerateSelfSignedCert — so the ACME account survives restarts and is never // re-registered unnecessarily. type acmeUser struct { Email string Registration *registration.Resource key crypto.PrivateKey } func (u *acmeUser) GetEmail() string { return u.Email } func (u *acmeUser) GetRegistration() *registration.Resource { return u.Registration } func (u *acmeUser) GetPrivateKey() crypto.PrivateKey { return u.key } func accountKeyPath(dataDir string) string { return filepath.Join(dataDir, "account.key") } func accountRegPath(dataDir string) string { return filepath.Join(dataDir, "account.json") } // loadOrCreateAccount loads the persisted ACME account key/registration from dataDir, // generating a fresh key if none exists yet. A nil Registration means no account has // been registered with the CA yet — the caller is responsible for registering and then // calling saveRegistration. func loadOrCreateAccount(dataDir, email string) (*acmeUser, error) { if err := os.MkdirAll(dataDir, 0o755); err != nil { return nil, err } key, err := loadOrCreateAccountKey(dataDir) if err != nil { return nil, err } user := &acmeUser{Email: email, key: key} if regBytes, err := os.ReadFile(accountRegPath(dataDir)); err == nil { var reg registration.Resource if err := json.Unmarshal(regBytes, ®); err == nil { user.Registration = ® } } return user, nil } func loadOrCreateAccountKey(dataDir string) (crypto.PrivateKey, error) { path := accountKeyPath(dataDir) if pemBytes, err := os.ReadFile(path); err == nil { return certcrypto.ParsePEMPrivateKey(pemBytes) } key, err := certcrypto.GeneratePrivateKey(certcrypto.EC256) if err != nil { return nil, err } if err := os.WriteFile(path, certcrypto.PEMEncode(key), 0o600); err != nil { return nil, err } return key, nil } // saveRegistration persists the account's registration resource so future runs don't // re-register with the CA. func saveRegistration(dataDir string, reg *registration.Resource) error { data, err := json.Marshal(reg) if err != nil { return err } return os.WriteFile(accountRegPath(dataDir), data, 0o600) }