80 lines
2.5 KiB
Go
80 lines
2.5 KiB
Go
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)
|
|
}
|