Files
mailgoserver/internal/acmecert/user.go
T

80 lines
2.5 KiB
Go
Raw Normal View History

2026-08-12 21:14:19 +01:00
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, &reg); err == nil {
user.Registration = &reg
}
}
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)
}