From de7d45700c9e7688e8a031906a729d88080f52be Mon Sep 17 00:00:00 2001
From: ale <ale@incal.net>
Date: Thu, 25 Aug 2022 17:26:16 +0100
Subject: [PATCH] Implement an import command

Used to import existing certificates into the database, and upload
them to storage. Allows for migration of an existing acmeserver v2
setup.
---
 acme/acme.go                                  |   3 +-
 acme/util.go                                  |  38 -------
 cmd/acmeserver/acmeserver.go                  |  22 +++-
 common/certio.go                              | 102 +++++++++++++++++
 config.go                                     |  27 +++--
 import.go                                     | 103 ++++++++++++++++++
 import_test.go                                |  70 ++++++++++++
 manager.go                                    |  24 ++--
 manager_test.go                               |   4 +-
 storage/config.go                             |  32 ++++--
 testdata/import/admin.autistici.org/cert.pem  |  19 ++++
 .../import/admin.autistici.org/fullchain.pem  |  19 ++++
 .../import/admin.autistici.org/privkey.pem    |  27 +++++
 testdata/import/alerts.autistici.org/cert.pem |  19 ++++
 .../import/alerts.autistici.org/fullchain.pem |  19 ++++
 .../import/alerts.autistici.org/privkey.pem   |  27 +++++
 testdata/import/assets.autistici.org/cert.pem |  19 ++++
 .../import/assets.autistici.org/fullchain.pem |  19 ++++
 .../import/assets.autistici.org/privkey.pem   |  27 +++++
 .../import/backups.autistici.org/cert.pem     |  19 ++++
 .../backups.autistici.org/fullchain.pem       |  19 ++++
 .../import/backups.autistici.org/privkey.pem  |  27 +++++
 .../import/grafana.autistici.org/cert.pem     |  19 ++++
 .../grafana.autistici.org/fullchain.pem       |  19 ++++
 .../import/grafana.autistici.org/privkey.pem  |  27 +++++
 testdata/import/lists.autistici.org/cert.pem  |  19 ++++
 .../import/lists.autistici.org/fullchain.pem  |  19 ++++
 .../import/lists.autistici.org/privkey.pem    |  27 +++++
 testdata/import/mail.autistici.org/cert.pem   |  19 ++++
 .../import/mail.autistici.org/fullchain.pem   |  19 ++++
 .../import/mail.autistici.org/privkey.pem     |  27 +++++
 .../import/monitor.autistici.org/cert.pem     |  19 ++++
 .../monitor.autistici.org/fullchain.pem       |  19 ++++
 .../import/monitor.autistici.org/privkey.pem  |  27 +++++
 testdata/import/prober.autistici.org/cert.pem |  19 ++++
 .../import/prober.autistici.org/fullchain.pem |  19 ++++
 .../import/prober.autistici.org/privkey.pem   |  27 +++++
 testdata/import/smtp.autistici.org/cert.pem   |  19 ++++
 .../import/smtp.autistici.org/fullchain.pem   |  19 ++++
 .../import/smtp.autistici.org/privkey.pem     |  27 +++++
 testdata/import/trace.autistici.org/cert.pem  |  19 ++++
 .../import/trace.autistici.org/fullchain.pem  |  19 ++++
 .../import/trace.autistici.org/privkey.pem    |  27 +++++
 .../import/webmail.autistici.org/cert.pem     |  20 ++++
 .../webmail.autistici.org/fullchain.pem       |  20 ++++
 .../import/webmail.autistici.org/privkey.pem  |  27 +++++
 util.go                                       |  55 +---------
 47 files changed, 1128 insertions(+), 134 deletions(-)
 create mode 100644 common/certio.go
 create mode 100644 import.go
 create mode 100644 import_test.go
 create mode 100644 testdata/import/admin.autistici.org/cert.pem
 create mode 100644 testdata/import/admin.autistici.org/fullchain.pem
 create mode 100644 testdata/import/admin.autistici.org/privkey.pem
 create mode 100644 testdata/import/alerts.autistici.org/cert.pem
 create mode 100644 testdata/import/alerts.autistici.org/fullchain.pem
 create mode 100644 testdata/import/alerts.autistici.org/privkey.pem
 create mode 100644 testdata/import/assets.autistici.org/cert.pem
 create mode 100644 testdata/import/assets.autistici.org/fullchain.pem
 create mode 100644 testdata/import/assets.autistici.org/privkey.pem
 create mode 100644 testdata/import/backups.autistici.org/cert.pem
 create mode 100644 testdata/import/backups.autistici.org/fullchain.pem
 create mode 100644 testdata/import/backups.autistici.org/privkey.pem
 create mode 100644 testdata/import/grafana.autistici.org/cert.pem
 create mode 100644 testdata/import/grafana.autistici.org/fullchain.pem
 create mode 100644 testdata/import/grafana.autistici.org/privkey.pem
 create mode 100644 testdata/import/lists.autistici.org/cert.pem
 create mode 100644 testdata/import/lists.autistici.org/fullchain.pem
 create mode 100644 testdata/import/lists.autistici.org/privkey.pem
 create mode 100644 testdata/import/mail.autistici.org/cert.pem
 create mode 100644 testdata/import/mail.autistici.org/fullchain.pem
 create mode 100644 testdata/import/mail.autistici.org/privkey.pem
 create mode 100644 testdata/import/monitor.autistici.org/cert.pem
 create mode 100644 testdata/import/monitor.autistici.org/fullchain.pem
 create mode 100644 testdata/import/monitor.autistici.org/privkey.pem
 create mode 100644 testdata/import/prober.autistici.org/cert.pem
 create mode 100644 testdata/import/prober.autistici.org/fullchain.pem
 create mode 100644 testdata/import/prober.autistici.org/privkey.pem
 create mode 100644 testdata/import/smtp.autistici.org/cert.pem
 create mode 100644 testdata/import/smtp.autistici.org/fullchain.pem
 create mode 100644 testdata/import/smtp.autistici.org/privkey.pem
 create mode 100644 testdata/import/trace.autistici.org/cert.pem
 create mode 100644 testdata/import/trace.autistici.org/fullchain.pem
 create mode 100644 testdata/import/trace.autistici.org/privkey.pem
 create mode 100644 testdata/import/webmail.autistici.org/cert.pem
 create mode 100644 testdata/import/webmail.autistici.org/fullchain.pem
 create mode 100644 testdata/import/webmail.autistici.org/privkey.pem

diff --git a/acme/acme.go b/acme/acme.go
index 1ca52f55..22a2d084 100644
--- a/acme/acme.go
+++ b/acme/acme.go
@@ -16,6 +16,7 @@ import (
 	"golang.org/x/crypto/acme"
 
 	as "git.autistici.org/ai3/tools/acmeserver"
+	"git.autistici.org/ai3/tools/acmeserver/common"
 )
 
 const (
@@ -109,7 +110,7 @@ func NewACME(config *Config) (*ACME, error) {
 
 // Return the account key, possibly initializing it if it does not exist.
 func (a *ACME) accountKey() (crypto.Signer, error) {
-	if key, err := parsePrivateKeyFromFile(a.accountKeyPath); err == nil {
+	if key, err := common.ParsePrivateKeyFromFile(a.accountKeyPath); err == nil {
 		return key, nil
 	}
 
diff --git a/acme/util.go b/acme/util.go
index 139dd79e..a83bf738 100644
--- a/acme/util.go
+++ b/acme/util.go
@@ -4,14 +4,10 @@ import (
 	"crypto"
 	"crypto/ecdsa"
 	"crypto/rand"
-	"crypto/rsa"
 	"crypto/x509"
 	"crypto/x509/pkix"
 	"encoding/pem"
-	"errors"
 	"io"
-	"io/ioutil"
-	"strings"
 )
 
 func certRequest(key crypto.Signer, domains []string) ([]byte, error) {
@@ -30,37 +26,3 @@ func encodeECDSAKey(w io.Writer, key *ecdsa.PrivateKey) error {
 	pb := &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
 	return pem.Encode(w, pb)
 }
-
-func parsePrivateKey(der []byte) (crypto.Signer, error) {
-	if key, err := x509.ParsePKCS1PrivateKey(der); err == nil {
-		return key, nil
-	}
-	if key, err := x509.ParsePKCS8PrivateKey(der); err == nil {
-		switch key := key.(type) {
-		case *rsa.PrivateKey:
-			return key, nil
-		case *ecdsa.PrivateKey:
-			return key, nil
-		default:
-			return nil, errors.New("unknown private key type in PKCS#8 wrapping")
-		}
-	}
-	if key, err := x509.ParseECPrivateKey(der); err == nil {
-		return key, nil
-	}
-
-	return nil, errors.New("failed to parse private key")
-}
-
-func parsePrivateKeyFromFile(path string) (crypto.Signer, error) {
-	data, err := ioutil.ReadFile(path) // nolint: gosec
-	if err != nil {
-		return nil, err
-	}
-
-	priv, _ := pem.Decode(data)
-	if priv == nil || !strings.Contains(priv.Type, "PRIVATE") {
-		return nil, errors.New("invalid account key")
-	}
-	return parsePrivateKey(priv.Bytes)
-}
diff --git a/cmd/acmeserver/acmeserver.go b/cmd/acmeserver/acmeserver.go
index 26868b58..e033f683 100644
--- a/cmd/acmeserver/acmeserver.go
+++ b/cmd/acmeserver/acmeserver.go
@@ -19,6 +19,7 @@ import (
 var (
 	addr       = flag.String("addr", ":2780", "tcp `address` to listen on")
 	configFile = flag.String("config", "/etc/acme/config.yml", "configuration `file`")
+	importPath = flag.String("import", "", "import existing certificates from `path` and exit")
 )
 
 // Config ties together the acmeserver Config and the standard
@@ -26,6 +27,7 @@ var (
 type Config struct {
 	Server  acmeserver.Config        `yaml:",inline"`
 	ACME    acmeproto.Config         `yaml:"acme"`
+	Output  storage.Config           `yaml:"output"`
 	HTTP    *serverutil.ServerConfig `yaml:"http_server"`
 	Testing bool                     `yaml:"testing"`
 }
@@ -50,7 +52,19 @@ func main() {
 
 	config, err := loadConfig(*configFile)
 	if err != nil {
-		log.Fatal(err)
+		log.Fatalf("could not read configuration: %v", err)
+	}
+
+	stor, err := storage.FromConfig(&config.Output)
+	if err != nil {
+		log.Fatalf("error initializing storage: %v", err)
+	}
+
+	if *importPath != "" {
+		if err := acmeserver.Import(context.Background(), &config.Server, stor, *importPath); err != nil {
+			log.Fatalf("import failed: %v", err)
+		}
+		os.Exit(0)
 	}
 
 	var h http.Handler
@@ -73,15 +87,11 @@ func main() {
 		})
 	}
 
-	stor, err := storage.FromConfig(&config.Server)
-	if err != nil {
-		log.Fatal(err)
-	}
-
 	m, err := acmeserver.NewManager(&config.Server, cg, stor)
 	if err != nil {
 		log.Fatal(err)
 	}
+	defer m.Close()
 
 	// Start the acmeserver.Manager in the background. The
 	// serverutil package installs SIGTERM handlers that stop the
diff --git a/common/certio.go b/common/certio.go
new file mode 100644
index 00000000..c06af029
--- /dev/null
+++ b/common/certio.go
@@ -0,0 +1,102 @@
+package common
+
+import (
+	"bytes"
+	"crypto"
+	"crypto/ecdsa"
+	"crypto/rsa"
+	"crypto/x509"
+	"encoding/pem"
+	"errors"
+	"io/ioutil"
+	"strings"
+)
+
+func EncodeCerts(der [][]byte) ([]byte, error) {
+	var buf bytes.Buffer
+	for _, b := range der {
+		pb := &pem.Block{Type: "CERTIFICATE", Bytes: b}
+		if err := pem.Encode(&buf, pb); err != nil {
+			return nil, err
+		}
+	}
+	return buf.Bytes(), nil
+}
+
+func ParseCertsFromFile(path string) ([][]byte, error) {
+	data, err := ioutil.ReadFile(path)
+	if err != nil {
+		return nil, err
+	}
+	var der [][]byte
+	for {
+		block, rest := pem.Decode(data)
+		if block == nil {
+			break
+		}
+		der = append(der, block.Bytes)
+		data = rest
+	}
+	return der, nil
+}
+
+func EncodePrivateKey(key crypto.Signer) ([]byte, error) {
+	var pb *pem.Block
+	switch priv := key.(type) {
+	case *rsa.PrivateKey:
+		pb = &pem.Block{
+			Type:  "RSA PRIVATE KEY",
+			Bytes: x509.MarshalPKCS1PrivateKey(priv),
+		}
+	case *ecdsa.PrivateKey:
+		b, err := x509.MarshalECPrivateKey(priv)
+		if err != nil {
+			return nil, err
+		}
+		pb = &pem.Block{
+			Type:  "EC PRIVATE KEY",
+			Bytes: b,
+		}
+	default:
+		return nil, errors.New("unknown private key type")
+	}
+	var buf bytes.Buffer
+	if err := pem.Encode(&buf, pb); err != nil {
+		return nil, err
+	}
+	return buf.Bytes(), nil
+}
+
+func ParsePrivateKey(der []byte) (crypto.Signer, error) {
+	if key, err := x509.ParsePKCS1PrivateKey(der); err == nil {
+		return key, nil
+	}
+	if key, err := x509.ParsePKCS8PrivateKey(der); err == nil {
+		switch key := key.(type) {
+		case *rsa.PrivateKey:
+			return key, nil
+		case *ecdsa.PrivateKey:
+			return key, nil
+		default:
+			return nil, errors.New("unknown private key type in PKCS#8 wrapping")
+		}
+	}
+	if key, err := x509.ParseECPrivateKey(der); err == nil {
+		return key, nil
+	}
+
+	return nil, errors.New("failed to parse private key")
+}
+
+func ParsePrivateKeyFromFile(path string) (crypto.Signer, error) {
+	data, err := ioutil.ReadFile(path) // nolint: gosec
+	if err != nil {
+		return nil, err
+	}
+
+	priv, _ := pem.Decode(data)
+	if priv == nil || !strings.Contains(priv.Type, "PRIVATE") {
+		return nil, errors.New("invalid account key")
+	}
+	return ParsePrivateKey(priv.Bytes)
+}
diff --git a/config.go b/config.go
index f4659fe4..a64aa84f 100644
--- a/config.go
+++ b/config.go
@@ -11,6 +11,8 @@ import (
 	"gopkg.in/yaml.v3"
 )
 
+var defaultRenewalDays = 21
+
 // Config holds the configuration for an acmeserver instance.
 //
 // The reason for supporting multiple config_dirs is to allow
@@ -25,19 +27,20 @@ type Config struct {
 
 	Dirs   []string `yaml:"config_dirs"`
 	DBPath string   `yaml:"db_path"`
+}
 
-	Output struct {
-		Path   string `yaml:"path"`
-		ReplDS struct {
-			Endpoint string `yaml:"endpoint"`
-			Prefix   string `yaml:"prefix"`
-			TLS      struct {
-				Cert string `yaml:"cert"`
-				Key  string `yaml:"key"`
-				CA   string `yaml:"ca"`
-			} `yaml:"tls"`
-		} `yaml:"replds"`
-	} `yaml:"output"`
+// Check the configuration for validity, set defaults.
+func (c *Config) check() error {
+	if len(c.Dirs) == 0 {
+		return errors.New("configuration parameter 'config_dirs' is unset")
+	}
+	if c.DBPath == "" {
+		return errors.New("configuration parameter 'db_path' is unset")
+	}
+	if c.RenewalDays <= 0 {
+		c.RenewalDays = defaultRenewalDays
+	}
+	return nil
 }
 
 // This is all the configuration we need to generate a certificate.
diff --git a/import.go b/import.go
new file mode 100644
index 00000000..e0b43ea7
--- /dev/null
+++ b/import.go
@@ -0,0 +1,103 @@
+package acmeserver
+
+import (
+	"context"
+	"crypto/x509"
+	"database/sql"
+	"log"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"git.autistici.org/ai3/go-common/sqlutil"
+	"git.autistici.org/ai3/tools/acmeserver/common"
+)
+
+func importCert(ctx context.Context, tx *sql.Tx, storage Storage, renewalDays int) func(*Certificate) error {
+	return func(cert *Certificate) error {
+		// Create a fake certConfig just to get the ID.
+		conf := &certConfig{
+			Names: cert.Leaf.DNSNames,
+		}
+		conf.normalize()
+
+		// Upload to storage and save state to db.
+		log.Printf("found certificate key pair for %s", strings.Join(conf.Names, ", "))
+		if err := uploadCert(ctx, storage, conf.path(), cert); err != nil {
+			return err
+		}
+		if err := addCertState(tx, conf); err != nil {
+			return err
+		}
+		return updateCertState(tx, conf.hash(), cert, nil, renewalDays)
+	}
+}
+
+func withImportedCertificate(fn func(*Certificate) error) func(string) error {
+	return func(path string) error {
+		certs, err := common.ParseCertsFromFile(filepath.Join(path, "cert.pem"))
+		if err != nil {
+			return err
+		}
+		leaf, err := x509.ParseCertificate(certs[0])
+		if err != nil {
+			return err
+		}
+		key, err := common.ParsePrivateKeyFromFile(filepath.Join(path, "privkey.pem"))
+		if err != nil {
+			return err
+		}
+
+		return fn(&Certificate{
+			DER:  certs,
+			Leaf: leaf,
+			Key:  key,
+		})
+	}
+}
+
+func forAllCertificatePaths(root string, fn func(string) error) error {
+	return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			return nil // nolint: nilerr
+		}
+		if info.Name() != "privkey.pem" {
+			return nil
+		}
+		dir := filepath.Dir(path)
+		if _, err := os.Stat(filepath.Join(dir, "cert.pem")); err != nil {
+			return nil // nolint: nilerr
+		}
+
+		// We've found a directory with a certificate and private key, load them.
+		if err := fn(dir); err != nil {
+			log.Printf("%s: error: %v", dir, err)
+		}
+		return nil
+	})
+}
+
+// Import an existing certificate hierarchy from the local
+// filesystem. Certificates found (in any directory containing a
+// 'cert.pem' and 'privkey.pem' keypair) will be loaded into the
+// database and uploaded to the storage
+func Import(ctx context.Context, config *Config, storage Storage, certpath string) error {
+	if err := config.check(); err != nil {
+		return err
+	}
+
+	db, err := openDB(config.DBPath)
+	if err != nil {
+		return err
+	}
+	defer db.Close()
+
+	return sqlutil.WithTx(ctx, db, func(tx *sql.Tx) error {
+		return forAllCertificatePaths(
+			certpath,
+			withImportedCertificate(
+				importCert(ctx, tx, storage, config.RenewalDays),
+			),
+		)
+	})
+}
diff --git a/import_test.go b/import_test.go
new file mode 100644
index 00000000..ae21ad0d
--- /dev/null
+++ b/import_test.go
@@ -0,0 +1,70 @@
+package acmeserver
+
+import (
+	"context"
+	"io/ioutil"
+	"os"
+	"testing"
+	"time"
+)
+
+func TestImport(t *testing.T) {
+	dir, err := ioutil.TempDir("", "")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(dir)
+
+	config := newTestConfig(dir, false)
+
+	err = Import(
+		context.Background(),
+		config,
+		newFakeStorage(),
+		"testdata/import",
+	)
+	if err != nil {
+		t.Fatalf("Import(): %v", err)
+	}
+
+	// Manual test that the database contains expected values.
+	db, err := openDB(config.DBPath)
+	if err != nil {
+		t.Fatalf("OpenDB(): %v", err)
+	}
+	defer db.Close()
+
+	// Expiration dates of the certificates in testdata/import, minus defaultRenewalDays.
+	expectations := []struct {
+		domain  string
+		retryAt time.Time
+	}{
+		{"admin.autistici.org", time.Date(2023, 8, 4, 9, 3, 0, 0, time.UTC)},
+		{"alerts.autistici.org", time.Date(2023, 8, 4, 9, 3, 2, 0, time.UTC)},
+		{"assets.autistici.org", time.Date(2023, 8, 4, 9, 3, 0, 0, time.UTC)},
+		{"backups.autistici.org", time.Date(2023, 8, 4, 9, 3, 0, 0, time.UTC)},
+		{"grafana.autistici.org", time.Date(2023, 8, 4, 9, 3, 2, 0, time.UTC)},
+		{"lists.autistici.org", time.Date(2023, 8, 4, 9, 3, 1, 0, time.UTC)},
+		{"mail.autistici.org", time.Date(2023, 8, 4, 9, 3, 1, 0, time.UTC)},
+		{"monitor.autistici.org", time.Date(2023, 8, 4, 9, 3, 2, 0, time.UTC)},
+		{"prober.autistici.org", time.Date(2023, 8, 4, 9, 3, 2, 0, time.UTC)},
+		{"smtp.autistici.org", time.Date(2023, 8, 4, 9, 3, 1, 0, time.UTC)},
+		{"trace.autistici.org", time.Date(2023, 8, 4, 9, 3, 3, 0, time.UTC)},
+		{"webmail.autistici.org,1.webmail.autistici.org,2.webmail.autistici.org",
+			time.Date(2023, 8, 4, 9, 3, 1, 0, time.UTC)},
+	}
+
+	for _, exp := range expectations {
+		var retryAt time.Time
+		var isValid bool
+		if err := db.QueryRow("SELECT is_valid, retry_at FROM cert_state WHERE names=?", exp.domain).Scan(&isValid, &retryAt); err != nil {
+			t.Fatalf("database error: %v", err)
+		}
+		if !isValid {
+			t.Errorf("imported certificate %s is not valid", exp.domain)
+		}
+		if !retryAt.Equal(exp.retryAt) {
+			t.Errorf("unexpected retry_at for %s: got %s, expected %s", exp.domain, retryAt, exp.retryAt)
+		}
+	}
+}
diff --git a/manager.go b/manager.go
index 2f90f7d0..1d645e39 100644
--- a/manager.go
+++ b/manager.go
@@ -19,10 +19,9 @@ import (
 )
 
 var (
-	defaultRenewalDays = 21
-	updateInterval     = 1 * time.Minute
-	renewalTimeout     = 30 * time.Minute
-	errorRetryTimeout  = 1 * time.Hour
+	updateInterval    = 1 * time.Minute
+	renewalTimeout    = 30 * time.Minute
+	errorRetryTimeout = 1 * time.Hour
 )
 
 // Certificate signed by the remote ACME authority.
@@ -73,15 +72,8 @@ type Manager struct {
 
 // NewManager creates a new Manager with the given configuration.
 func NewManager(config *Config, certGen CertGenerator, storage Storage) (*Manager, error) {
-	// Validate the configuration.
-	if len(config.Dirs) == 0 {
-		return nil, errors.New("configuration parameter 'config_dirs' is unset")
-	}
-	if config.DBPath == "" {
-		return nil, errors.New("configuration parameter 'db_path' is unset")
-	}
-	if config.RenewalDays <= 0 {
-		config.RenewalDays = defaultRenewalDays
+	if err := config.check(); err != nil {
+		return nil, err
 	}
 
 	db, err := openDB(config.DBPath)
@@ -193,12 +185,12 @@ func (m *Manager) createCert(ctx context.Context, cert *certConfig) (*Certificat
 
 // Upload a certificate to the storage. The individual files (cert,
 // private key, chain, etc) are uploaded as a single Batch.
-func (m *Manager) uploadCert(ctx context.Context, path string, cert *Certificate) error {
+func uploadCert(ctx context.Context, storage Storage, path string, cert *Certificate) error {
 	b, err := cert.dump(path)
 	if err != nil {
 		return err
 	}
-	return m.storage.Write(ctx, b)
+	return storage.Write(ctx, b)
 }
 
 // Attempt to renew a certificate. The function only returns an error
@@ -211,7 +203,7 @@ func (m *Manager) updateCertificate(ctx context.Context, tx *sql.Tx, id string,
 	signed, err := m.createCert(ctx, cert)
 
 	if err == nil {
-		err = m.uploadCert(ctx, cert.path(), signed)
+		err = uploadCert(ctx, m.storage, cert.path(), signed)
 	}
 
 	// Report on the result of the operation, though it can still
diff --git a/manager_test.go b/manager_test.go
index 24762034..bdde8a45 100644
--- a/manager_test.go
+++ b/manager_test.go
@@ -88,13 +88,11 @@ func (s *slowACME) GetCertificate(ctx context.Context, _ crypto.Signer, _ []stri
 }
 
 func newTestConfig(dir string, useRSA bool) *Config {
-	c := Config{
+	return &Config{
 		UseRSA: useRSA,
 		Dirs:   []string{filepath.Join(dir, "config")},
 		DBPath: filepath.Join(dir, "acme.db"),
 	}
-	c.Output.Path = filepath.Join(dir, "certs")
-	return &c
 }
 
 // Create a new test function.
diff --git a/storage/config.go b/storage/config.go
index 1c8d6157..8d6a512a 100644
--- a/storage/config.go
+++ b/storage/config.go
@@ -6,19 +6,33 @@ import (
 	as "git.autistici.org/ai3/tools/acmeserver"
 )
 
+// Config for the output storage layer.
+type Config struct {
+	Path   string `yaml:"path"`
+	ReplDS struct {
+		Endpoint string `yaml:"endpoint"`
+		Prefix   string `yaml:"prefix"`
+		TLS      struct {
+			Cert string `yaml:"cert"`
+			Key  string `yaml:"key"`
+			CA   string `yaml:"ca"`
+		} `yaml:"tls"`
+	} `yaml:"replds"`
+}
+
 // FromConfig returns a Storage instance as configured in the given
 // Config.
-func FromConfig(config *as.Config) (as.Storage, error) {
+func FromConfig(config *Config) (as.Storage, error) {
 	switch {
-	case config.Output.Path != "":
-		return &dirStorage{root: config.Output.Path}, nil
-	case config.Output.ReplDS.Endpoint != "":
+	case config.Path != "":
+		return &dirStorage{root: config.Path}, nil
+	case config.ReplDS.Endpoint != "":
 		return newReplDSStorage(
-			config.Output.ReplDS.Endpoint,
-			config.Output.ReplDS.Prefix,
-			config.Output.ReplDS.TLS.Cert,
-			config.Output.ReplDS.TLS.Key,
-			config.Output.ReplDS.TLS.CA,
+			config.ReplDS.Endpoint,
+			config.ReplDS.Prefix,
+			config.ReplDS.TLS.Cert,
+			config.ReplDS.TLS.Key,
+			config.ReplDS.TLS.CA,
 		)
 	default:
 		return nil, errors.New("no output configured")
diff --git a/testdata/import/admin.autistici.org/cert.pem b/testdata/import/admin.autistici.org/cert.pem
new file mode 100644
index 00000000..df0abf55
--- /dev/null
+++ b/testdata/import/admin.autistici.org/cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHDCCAgSgAwIBAgIRAKHxacv9HH2II+R0lDmda8kwDQYJKoZIhvcNAQELBQAw
+HjEcMBoGA1UEAxMTYWRtaW4uYXV0aXN0aWNpLm9yZzAeFw0yMjA4MjUwOTAzMDBa
+Fw0yMzA4MjUwOTAzMDBaMB4xHDAaBgNVBAMTE2FkbWluLmF1dGlzdGljaS5vcmcw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC81JYg5Cd5Q0fBGUkZlbTN
++LF4YZbOdziAHstRxTlkPJ6dML64zrFtC2ApcOX6MU1rsOMDcNJgH5IWq7PlP1uW
+QCkXfci5BYmlSxCy4WHgr081f0xL9SKoudvt5Lu0yXV7eJVAGl0xn+w+G19fa682
+xTz8bI3l1n/C9tYoEQv9MR57MsfiYpdGeIiq+icUqD4sS9DG27Zm23eVHTe9eQ4E
+HEreqs6HLO38T6OA0eqzbgRUy43cB8yVprUqM88ms3zwF3Ka/Is0wDmlWeFELyxN
+76D05MWA8CZ2V96JDHpYNyQBVhGq/VruI92NtnEm57xJd9v1xpyQ8pHRz/6lYeB7
+AgMBAAGjVTBTMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAM
+BgNVHRMBAf8EAjAAMB4GA1UdEQQXMBWCE2FkbWluLmF1dGlzdGljaS5vcmcwDQYJ
+KoZIhvcNAQELBQADggEBACSym7npIK5Qh8bQBgux7aOgQVMxI/ELmro1JCtbLjf/
+bIqfGWOyAxcF9RaxnuSy8EAdqpKqjYdPhMTQknCHZQuspBMkvyCWw8QxmfzfzBiy
+OEdEa92EQuWcXIuPYqpmf8w6iWdV+llIuoNJPhS0U33HLNer8du45zt4tJTZn+z9
+BHgjTHyKCmTyUAJfDZssbnlTYidn65EUdHVVKJePnczV3VbnF1kWehEAUaah7/79
+3yPqdCxm2s+HbTEpnOxIO/3eIsmd3OAFZh5/3S+fjk4LSjBF7ZixNaMuw+yTZRB7
+syerzIZ0RiXCz34DnqkVAULHAGGfqpEhpsA4rGS/9nE=
+-----END CERTIFICATE-----
diff --git a/testdata/import/admin.autistici.org/fullchain.pem b/testdata/import/admin.autistici.org/fullchain.pem
new file mode 100644
index 00000000..df0abf55
--- /dev/null
+++ b/testdata/import/admin.autistici.org/fullchain.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHDCCAgSgAwIBAgIRAKHxacv9HH2II+R0lDmda8kwDQYJKoZIhvcNAQELBQAw
+HjEcMBoGA1UEAxMTYWRtaW4uYXV0aXN0aWNpLm9yZzAeFw0yMjA4MjUwOTAzMDBa
+Fw0yMzA4MjUwOTAzMDBaMB4xHDAaBgNVBAMTE2FkbWluLmF1dGlzdGljaS5vcmcw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC81JYg5Cd5Q0fBGUkZlbTN
++LF4YZbOdziAHstRxTlkPJ6dML64zrFtC2ApcOX6MU1rsOMDcNJgH5IWq7PlP1uW
+QCkXfci5BYmlSxCy4WHgr081f0xL9SKoudvt5Lu0yXV7eJVAGl0xn+w+G19fa682
+xTz8bI3l1n/C9tYoEQv9MR57MsfiYpdGeIiq+icUqD4sS9DG27Zm23eVHTe9eQ4E
+HEreqs6HLO38T6OA0eqzbgRUy43cB8yVprUqM88ms3zwF3Ka/Is0wDmlWeFELyxN
+76D05MWA8CZ2V96JDHpYNyQBVhGq/VruI92NtnEm57xJd9v1xpyQ8pHRz/6lYeB7
+AgMBAAGjVTBTMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATAM
+BgNVHRMBAf8EAjAAMB4GA1UdEQQXMBWCE2FkbWluLmF1dGlzdGljaS5vcmcwDQYJ
+KoZIhvcNAQELBQADggEBACSym7npIK5Qh8bQBgux7aOgQVMxI/ELmro1JCtbLjf/
+bIqfGWOyAxcF9RaxnuSy8EAdqpKqjYdPhMTQknCHZQuspBMkvyCWw8QxmfzfzBiy
+OEdEa92EQuWcXIuPYqpmf8w6iWdV+llIuoNJPhS0U33HLNer8du45zt4tJTZn+z9
+BHgjTHyKCmTyUAJfDZssbnlTYidn65EUdHVVKJePnczV3VbnF1kWehEAUaah7/79
+3yPqdCxm2s+HbTEpnOxIO/3eIsmd3OAFZh5/3S+fjk4LSjBF7ZixNaMuw+yTZRB7
+syerzIZ0RiXCz34DnqkVAULHAGGfqpEhpsA4rGS/9nE=
+-----END CERTIFICATE-----
diff --git a/testdata/import/admin.autistici.org/privkey.pem b/testdata/import/admin.autistici.org/privkey.pem
new file mode 100644
index 00000000..7adb0dbe
--- /dev/null
+++ b/testdata/import/admin.autistici.org/privkey.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAvNSWIOQneUNHwRlJGZW0zfixeGGWznc4gB7LUcU5ZDyenTC+
+uM6xbQtgKXDl+jFNa7DjA3DSYB+SFquz5T9blkApF33IuQWJpUsQsuFh4K9PNX9M
+S/UiqLnb7eS7tMl1e3iVQBpdMZ/sPhtfX2uvNsU8/GyN5dZ/wvbWKBEL/TEeezLH
+4mKXRniIqvonFKg+LEvQxtu2Ztt3lR03vXkOBBxK3qrOhyzt/E+jgNHqs24EVMuN
+3AfMlaa1KjPPJrN88BdymvyLNMA5pVnhRC8sTe+g9OTFgPAmdlfeiQx6WDckAVYR
+qv1a7iPdjbZxJue8SXfb9cackPKR0c/+pWHgewIDAQABAoIBAGnhmjzfMfMg8r8n
++oFKHJPR+1FRB0rGNvxASCmVuVSnWRkzF/5wh7RpSIZ0M8BSnOLh0KlBPfWAPl5P
+8RN+r1BUsbXmZgK5RDFXIgnrrwJZysRomNOOpNyPOu7t7wK0ZBfTyXykS9VjowhA
+OP48V4cSmdvA0uApooIj/nmA9YQGbAbrtCLNxwmymSkh1yIYqRa/oXfgHw2ZlbC5
+j7DrDRwfDsRzLfXJry20/blQrDqkcfSYfuksnh8MBsyWA7xl66joGy48i7agxEyA
+utt+maDaInPYvNefQ5zATqaZ7fnD465OYR+0F7LqgHopsvQov/ZAFHUe/uN96NFv
+U+XZigECgYEA0d9H1lJufnXnRsqq0YnZ4lLV1azKaYtwpJF65YpUgGcapJiF2/xj
+DmMF3IsgedvxNbpPcVnVCOba2QceeY7KaCO2Uqjm9NZI9wEGO6881eVDu4J4T6a8
++sP5wXi1saqgXM6f14hffzGFAMESxf3HvFbPDBU+GuKVD+vM+Tyg+MECgYEA5lVd
+QRnKZbof0zJMC08epRWBhAIevSSWaSpCxHyTuOTl7l2Jqg6to5SwcADg22dvkRjd
+j3El3hoB9jcAYJVc+Sn7KhmnumehWDqR9VvselvwSHBsSdKJQJ+kgX+hLj/0CKOG
+VJCmHFX6CuJb9oYWFC9n5P2g6qktPgXVSNz/jDsCgYANCjqEboq/AMRl+aGzc0U+
+crcml53cAPX2w7NHH9kwQTe+klt+16ev9ScgFOTnrWrHU9QrztEVxIJEidSy6NRa
+tB1X9XakL8xIzJ9+hdzZzWzv8aNYD5weWJ7kDoP7jgZ4vb3gvnBwjrsc2OI5uxL1
++M7UiV1afkmUu6n4snOuAQKBgQCGl+teV0vGFfTy3LlDAoDNxrtgAD0mGXMrEN7j
+IkjQCjGyEEaZcOpWZFSVnm3Gx7KR/w24/E08c8YhshIuXhluH/H8r/ErI9NMUy1n
+Ssfs2DyYl9kvai9568+dPI9bD1PymFuunrfyZust4QgMDl5ofSsgzHRzq1gcnhv7
+7+gEcQKBgQCOlgisTmdLAgo4kHYsGHiuqYROE4g5FGubI1rvg9b97Ciq7rYxvlvI
+4IEz2VwBFxFwXCJAhs3zQaGk5WVOO28nxgu5v95s9M17L0sK6/W2SitTvg5qUmlU
+SZ1dAkLiKpwJ+9iMc4LhjTPH8/4VY0jKjqQDTE5lj+l5UQ6UUOnW1Q==
+-----END RSA PRIVATE KEY-----
diff --git a/testdata/import/alerts.autistici.org/cert.pem b/testdata/import/alerts.autistici.org/cert.pem
new file mode 100644
index 00000000..152a7c66
--- /dev/null
+++ b/testdata/import/alerts.autistici.org/cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHzCCAgegAwIBAgIRALpMZfci8mFAhsqRUtaMemowDQYJKoZIhvcNAQELBQAw
+HzEdMBsGA1UEAxMUYWxlcnRzLmF1dGlzdGljaS5vcmcwHhcNMjIwODI1MDkwMzAy
+WhcNMjMwODI1MDkwMzAyWjAfMR0wGwYDVQQDExRhbGVydHMuYXV0aXN0aWNpLm9y
+ZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMGzrj6juA8p5cSKhzF1
+WURXQYjYwPYUgKRoLHMAA4N2qGFpWx86MXFMUs+CPwyXhcw6Av1JIGrreeSS3Mu3
+K4Y1DzJg9atfurbcJam3Jwxu0HkmApXqHGE8rbxpGIQY0gQNlEr+TspVLvplmCa3
+ivcT0yimFr0Yvo3/2h6a2IbBlPvD2yA5RDP7LBSFta94ETrLOEG+/dXEmoVwfMtv
+GCBt2RamlixVm0yFfbUE7vjBbdPkdAQkw1D5cGKmJZmag73slV0LiUx8S99Tyubp
+1S7Yzu9Ay6ScnC+D2v4JjE6zIyTZQGlXeUSLG1t1Frdo63SIZdm8aZnlG8GUwFtI
+sZ8CAwEAAaNWMFQwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMB
+MAwGA1UdEwEB/wQCMAAwHwYDVR0RBBgwFoIUYWxlcnRzLmF1dGlzdGljaS5vcmcw
+DQYJKoZIhvcNAQELBQADggEBAFPJd4pnD67dELLpu8t3v+tD4V5eirEG0/9hAeyR
+vv9u9QJAVIso4kj3TqhLSj7Zp0iZ4xwyTPIoW0blT6K1ommTxASq6IW9zHiswZH2
+GT2Ui3mkH0N1VGgVQUmcUf9CH//63z54gUpkqA3UVg/UZ3flnUnoUbu+QhJovLOB
+aX5a9TiCt2yKiPgZ2TjmvwOsAOUn2xCfzao43TFykrQnNQZdgwAlVickhAbsfhyt
+o3MKcUGK1TMhCtTTibPa7mRTKdQihlh+Z38wDt5FZ/i6w9N+FhOcSCuHVaWOJp9L
+wtgKLaGx/Eb0mBAbsaHXbb0rIVpLdd/HrjQYfXjoLlmFMPo=
+-----END CERTIFICATE-----
diff --git a/testdata/import/alerts.autistici.org/fullchain.pem b/testdata/import/alerts.autistici.org/fullchain.pem
new file mode 100644
index 00000000..152a7c66
--- /dev/null
+++ b/testdata/import/alerts.autistici.org/fullchain.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHzCCAgegAwIBAgIRALpMZfci8mFAhsqRUtaMemowDQYJKoZIhvcNAQELBQAw
+HzEdMBsGA1UEAxMUYWxlcnRzLmF1dGlzdGljaS5vcmcwHhcNMjIwODI1MDkwMzAy
+WhcNMjMwODI1MDkwMzAyWjAfMR0wGwYDVQQDExRhbGVydHMuYXV0aXN0aWNpLm9y
+ZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMGzrj6juA8p5cSKhzF1
+WURXQYjYwPYUgKRoLHMAA4N2qGFpWx86MXFMUs+CPwyXhcw6Av1JIGrreeSS3Mu3
+K4Y1DzJg9atfurbcJam3Jwxu0HkmApXqHGE8rbxpGIQY0gQNlEr+TspVLvplmCa3
+ivcT0yimFr0Yvo3/2h6a2IbBlPvD2yA5RDP7LBSFta94ETrLOEG+/dXEmoVwfMtv
+GCBt2RamlixVm0yFfbUE7vjBbdPkdAQkw1D5cGKmJZmag73slV0LiUx8S99Tyubp
+1S7Yzu9Ay6ScnC+D2v4JjE6zIyTZQGlXeUSLG1t1Frdo63SIZdm8aZnlG8GUwFtI
+sZ8CAwEAAaNWMFQwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMB
+MAwGA1UdEwEB/wQCMAAwHwYDVR0RBBgwFoIUYWxlcnRzLmF1dGlzdGljaS5vcmcw
+DQYJKoZIhvcNAQELBQADggEBAFPJd4pnD67dELLpu8t3v+tD4V5eirEG0/9hAeyR
+vv9u9QJAVIso4kj3TqhLSj7Zp0iZ4xwyTPIoW0blT6K1ommTxASq6IW9zHiswZH2
+GT2Ui3mkH0N1VGgVQUmcUf9CH//63z54gUpkqA3UVg/UZ3flnUnoUbu+QhJovLOB
+aX5a9TiCt2yKiPgZ2TjmvwOsAOUn2xCfzao43TFykrQnNQZdgwAlVickhAbsfhyt
+o3MKcUGK1TMhCtTTibPa7mRTKdQihlh+Z38wDt5FZ/i6w9N+FhOcSCuHVaWOJp9L
+wtgKLaGx/Eb0mBAbsaHXbb0rIVpLdd/HrjQYfXjoLlmFMPo=
+-----END CERTIFICATE-----
diff --git a/testdata/import/alerts.autistici.org/privkey.pem b/testdata/import/alerts.autistici.org/privkey.pem
new file mode 100644
index 00000000..8b6edb9f
--- /dev/null
+++ b/testdata/import/alerts.autistici.org/privkey.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEAwbOuPqO4DynlxIqHMXVZRFdBiNjA9hSApGgscwADg3aoYWlb
+HzoxcUxSz4I/DJeFzDoC/Ukgaut55JLcy7crhjUPMmD1q1+6ttwlqbcnDG7QeSYC
+leocYTytvGkYhBjSBA2USv5OylUu+mWYJreK9xPTKKYWvRi+jf/aHprYhsGU+8Pb
+IDlEM/ssFIW1r3gROss4Qb791cSahXB8y28YIG3ZFqaWLFWbTIV9tQTu+MFt0+R0
+BCTDUPlwYqYlmZqDveyVXQuJTHxL31PK5unVLtjO70DLpJycL4Pa/gmMTrMjJNlA
+aVd5RIsbW3UWt2jrdIhl2bxpmeUbwZTAW0ixnwIDAQABAoIBAQCbFvuHadyAP6KZ
+d/UA851Sq65sHGh7XzUU7dfRCAzIIKR2wj1URmq59UtEnFtRAKz/NN+Z3wgWs414
+JuKkNXei0C7nvlxVJ6O+r4t0VJHuUXeX2Igvci/g5w2QEnGvqpPU7O50ESQSupuP
+nvJw5Xpdk8D5Beqk4wAHf/grkv2U/lsKV+9vAcURQHRSOpHH7xnaL2RGf4kcSpzV
+u9ypPcW1rrEOFv4kf9yT3a9SndFDqUfkzU+tJ3xLK3hKT1kbqmGYQLJOZqRYVW1W
+CYsF70BBVRXxdOZIk8Sa2cdUukD7TeHWIX6K2k+hswTEbBOZsQLtC69c/2cHiSOD
+II7FU5PxAoGBAOk+e+H7c9x+Z4+ek3H2UwBugoYAg77iseRmVvtcfADPUdlhSQuC
+AxuwDgIDwwt3ER4FFtaz4b4JlfrXhEaaMIWaccHD0LhQHy+3PXWD+C73YvpsGmWa
+SeMmj0PQ5sTkDnsAzp/mGh9d9+USoqRoNupBsP1KO+Krzw7tcNr/yiTpAoGBANSZ
+lrTuELUdWBBYlP0Xglju2631C+ymzQTBMiae63JHKOUA5u35RjQrFt/OhiWsj2C4
+zJtu1fhBIVr8I68VfN39YC+mPdKniLPpQUuJ+OIrCtWCgbvnMj1RYfrXPlXVNZPw
+Iqp7p2vnLUTz2vqwjTp9J9sv6PNcAx2a7+d0r61HAoGBAN7Vh1168lIxoZcglRVH
+lDzRV3nWfAeeZP4kl+1FimLrrvLTcwm/iGCnbnqWRX1qn7vieNNev8jjN5qS1Bjv
+rnO1/gB0p6+vPFxF4D2pey7DwpglN4LiSLzvVeggaJiqmPsC5mT1XPeiuvbrOt9/
+gReE8ybKCTShmAxW5H8V6XYhAoGAU1qQgwmf1VLW9zmz2HNCMNodmVutPDC9yzJw
+FkJSr7CEsRykbgNA6i1Gv6L6Z4T7hgGOfxZ6n+XxTPn0h63WEjdIYgY80P3MlDVM
+23AR+6LCkamkceaYhQAGeVGw5/g7FHCfNtZacuzli9ZMMePMy3TMjpx/KgMWWaIo
+k0flpPsCgYEA6SWVo4olHP9Cy/hRvmqu0JHmjStjmaPKiwgusnAzYp81j8d/naOH
+Zx2r3NLzpF8GVAk+0dm5hJ8+UH6tqeTWg47/OGzTUDKs7Fg3OsWMrt23mZ/QzobQ
+o5CEBGkaG7wyVJCafvxyjDWTbi3WqGqS1yWYkbuxn11N0T18lKxAKpE=
+-----END RSA PRIVATE KEY-----
diff --git a/testdata/import/assets.autistici.org/cert.pem b/testdata/import/assets.autistici.org/cert.pem
new file mode 100644
index 00000000..657f270e
--- /dev/null
+++ b/testdata/import/assets.autistici.org/cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHzCCAgegAwIBAgIRAM3uamQs6rKYxcP/dTJ9zdgwDQYJKoZIhvcNAQELBQAw
+HzEdMBsGA1UEAxMUYXNzZXRzLmF1dGlzdGljaS5vcmcwHhcNMjIwODI1MDkwMzAw
+WhcNMjMwODI1MDkwMzAwWjAfMR0wGwYDVQQDExRhc3NldHMuYXV0aXN0aWNpLm9y
+ZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJwOd4hzsZZiIyBMBUTo
+lgn2BYosz/rJ2f0ReGZqbfFUJBlkBeTFE0J7yeajGfaeIw0QaoVC+1R0Y5B9cWjF
+J+TcZ7tkIb2dzPD8vm0FRFKtO7404++JsDDBF+Nc+9EmM98ndCOyyaMIjoDM6Wth
+rX7gk45aHRAYJJ/dSx7wS7j9CD2lsxgTtBLn5WgN7ckDLy3WcKIZtlXk/gxh6PfP
+Gud0Ineqf0FwLP1Ov66PQVMRJM/fN8Ftmni6pRBlFfbhwHnDedRMO0Od+cCwtuH1
+qeMwXfQ0PcmyCtd7FAUZmtlg/OOKp8qxUq5KF0oD3ykEMRlBdA3Wv4S+ec5RT+B2
+t2UCAwEAAaNWMFQwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMB
+MAwGA1UdEwEB/wQCMAAwHwYDVR0RBBgwFoIUYXNzZXRzLmF1dGlzdGljaS5vcmcw
+DQYJKoZIhvcNAQELBQADggEBAFMyJjtS1ho+QYFnbQ47oGrP5y+5BMQAqN+FOnLP
+MxQLFODg9xoK/EUkGp7GdAWVeruBPoHu4QOkBJtTeoKzI4FA4u9H3CYoCXyQ/dBj
+6dt8czRiLrYfXIcfX/UetBEpdbTbiPw4HV82vRucnEYik6JBto8SrX0gS+BQEKcK
+ieaHafeLGg98Gf5lwGRU39BQxCgo2TlRuecrwmEcmo6z7RnK72EDlWPrOukNkbNl
+iMp2mv1lFsxGz2oUUFVwRvM4HnQ2k90rL8Q4qhMOs9GWFRLSPNIwdXwBoEUgqp8M
+98YNbrna2WVRZRbhJrCsApIZLpqgbjpGPSOAp2MfYpxuWw0=
+-----END CERTIFICATE-----
diff --git a/testdata/import/assets.autistici.org/fullchain.pem b/testdata/import/assets.autistici.org/fullchain.pem
new file mode 100644
index 00000000..657f270e
--- /dev/null
+++ b/testdata/import/assets.autistici.org/fullchain.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHzCCAgegAwIBAgIRAM3uamQs6rKYxcP/dTJ9zdgwDQYJKoZIhvcNAQELBQAw
+HzEdMBsGA1UEAxMUYXNzZXRzLmF1dGlzdGljaS5vcmcwHhcNMjIwODI1MDkwMzAw
+WhcNMjMwODI1MDkwMzAwWjAfMR0wGwYDVQQDExRhc3NldHMuYXV0aXN0aWNpLm9y
+ZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJwOd4hzsZZiIyBMBUTo
+lgn2BYosz/rJ2f0ReGZqbfFUJBlkBeTFE0J7yeajGfaeIw0QaoVC+1R0Y5B9cWjF
+J+TcZ7tkIb2dzPD8vm0FRFKtO7404++JsDDBF+Nc+9EmM98ndCOyyaMIjoDM6Wth
+rX7gk45aHRAYJJ/dSx7wS7j9CD2lsxgTtBLn5WgN7ckDLy3WcKIZtlXk/gxh6PfP
+Gud0Ineqf0FwLP1Ov66PQVMRJM/fN8Ftmni6pRBlFfbhwHnDedRMO0Od+cCwtuH1
+qeMwXfQ0PcmyCtd7FAUZmtlg/OOKp8qxUq5KF0oD3ykEMRlBdA3Wv4S+ec5RT+B2
+t2UCAwEAAaNWMFQwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMB
+MAwGA1UdEwEB/wQCMAAwHwYDVR0RBBgwFoIUYXNzZXRzLmF1dGlzdGljaS5vcmcw
+DQYJKoZIhvcNAQELBQADggEBAFMyJjtS1ho+QYFnbQ47oGrP5y+5BMQAqN+FOnLP
+MxQLFODg9xoK/EUkGp7GdAWVeruBPoHu4QOkBJtTeoKzI4FA4u9H3CYoCXyQ/dBj
+6dt8czRiLrYfXIcfX/UetBEpdbTbiPw4HV82vRucnEYik6JBto8SrX0gS+BQEKcK
+ieaHafeLGg98Gf5lwGRU39BQxCgo2TlRuecrwmEcmo6z7RnK72EDlWPrOukNkbNl
+iMp2mv1lFsxGz2oUUFVwRvM4HnQ2k90rL8Q4qhMOs9GWFRLSPNIwdXwBoEUgqp8M
+98YNbrna2WVRZRbhJrCsApIZLpqgbjpGPSOAp2MfYpxuWw0=
+-----END CERTIFICATE-----
diff --git a/testdata/import/assets.autistici.org/privkey.pem b/testdata/import/assets.autistici.org/privkey.pem
new file mode 100644
index 00000000..3cc87515
--- /dev/null
+++ b/testdata/import/assets.autistici.org/privkey.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAnA53iHOxlmIjIEwFROiWCfYFiizP+snZ/RF4Zmpt8VQkGWQF
+5MUTQnvJ5qMZ9p4jDRBqhUL7VHRjkH1xaMUn5Nxnu2QhvZ3M8Py+bQVEUq07vjTj
+74mwMMEX41z70SYz3yd0I7LJowiOgMzpa2GtfuCTjlodEBgkn91LHvBLuP0IPaWz
+GBO0EuflaA3tyQMvLdZwohm2VeT+DGHo988a53Qid6p/QXAs/U6/ro9BUxEkz983
+wW2aeLqlEGUV9uHAecN51Ew7Q535wLC24fWp4zBd9DQ9ybIK13sUBRma2WD844qn
+yrFSrkoXSgPfKQQxGUF0Dda/hL55zlFP4Ha3ZQIDAQABAoIBACY2CKC5q18mMnGj
+C5s+1BYmTl7x8A75Q84KbA+vubHgp2IsMqE8fCXIR7yYbTAMsoptXZe5ue6I/SQR
+AybjN4x7R08ECpryO486Y6owIvWXg0xkiPdLIIAfl6jNSL+CeisG7xIaofKwkqwJ
+AJKRfTjW5mpxuDILp1QZjF5L+k6WDH59lij/YarCfohINV3BJ8D1aqQ//+b2dIQG
+IlwVhBcmG9yP1U0V5ZjVukwSEfNQHM3rISIZUTxzPOn/TY2lE6YY+TkhuOtasAxR
+v3Oh3Fg3+y1UvkS+LyDeraWBUABJUz0i20auE7a8IB4ybBfhQy5AgpuIlC7qP/3j
++Ox6XxUCgYEAycWBKj/1R8TvpRGEl+tfs76h6pB5rqNf6AP3XE6kV7rzOp4XfiAy
+YQ4jgP2IOcAckcmCIll6K5AcPMqpwLpstwryTGgNX1HnLldUddFJpUru45KW4nWK
+CByBRPTfWdCNvvQQTVoZr+OnqNjI61WWmfC8gc9YQtDSjYGa6OM7t7MCgYEAxf+h
+pQYqunDXOUInCayXIFcU0LP0OnHjVF6+hUl3Va1GKoHifDGTAeRdDGkSrM9/HAeo
+KIOngcTcOEwsYlGNmZk+FZpmGmIwn00QEM0Ce/ycnKzyWHMM4F3QBPM9yZ2daF0K
+5ejXaQc6IdSHqpy0AbN54IT876tQ4EC3YN83yIcCgYEAugCPTOJ++7VJcfMPN3ks
+1Sc0XmbnNZ24KMuXOYnpqFJYLL5WS9fkeWIuSpft/8bgz2hixcWUgRotXcE58cgp
+tieqK9N0GF4JbsfkvWO8r1q/lByraYIPR+D3LhlK2KIQOWsGqDgaG/ISue12crOI
+zMqHDGeXJqkYoIhMxMnNYE8CgYBwJuZm1WFqjfQ7KK94GVzZIrpVpTkXyd7csXX1
+hbGkxCtUdJHXizUcA0B5gSLk4/u54tRrbnd5WArtkKgrYMp2UIkML5LaKvgjo8EP
+4zAfDu2fCY+UbkbC6CCmTSmYhEDfFqTPqd4qTb+t3faiu3Ry6jiE5o9nKoD7JW05
+3/Z3DQKBgAeiDbs0UtOFbiMAjADEPgKSX63x7k70cl0Yz+x9YKRvAipEnnySvR3V
+nnPvr9nbM3j/QmYI4JMLjoI+Ls/aI6+8OXeIFeTcDDouXaubxWs7ReO+w8dtPZaY
+dnLXxeNZZDB8a7OPg3iuSs2IW3jhH+x298scn9xZWFiZN1Kj4jhf
+-----END RSA PRIVATE KEY-----
diff --git a/testdata/import/backups.autistici.org/cert.pem b/testdata/import/backups.autistici.org/cert.pem
new file mode 100644
index 00000000..40338eff
--- /dev/null
+++ b/testdata/import/backups.autistici.org/cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDITCCAgmgAwIBAgIQNgn+VfLxcwjU52sIBdJNKzANBgkqhkiG9w0BAQsFADAg
+MR4wHAYDVQQDExViYWNrdXBzLmF1dGlzdGljaS5vcmcwHhcNMjIwODI1MDkwMzAw
+WhcNMjMwODI1MDkwMzAwWjAgMR4wHAYDVQQDExViYWNrdXBzLmF1dGlzdGljaS5v
+cmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDf2qGOW1yKadm8vvk5
+HB59CE99Abuf9dRyJgOAKlAuX60qmiJi0apvqGAFlWFQ5Nm/OGHyXkwp6teIhABg
+4K6lGQyacCk4s3tnFwErhUUFn0C0iDD2A4FscQN+SjRG70a/pz0FlUsx4oknmqzc
+C7QJAqVZ9rLqc6EN+NbrLwJCy/Dg0mxIGLxWulWnRoTeopRZ3/bTxf2Hd4uyREC8
+GPwmtCDl1Isg+K8KODqIwcnyQJhP7JC3Ry+sShQHnPCqczBiRLgdByPjLBFhqcuP
+jStYwFkcxqOuZel4b8+h1VDLqNwgutdX/IR6WWjcg6yYuUg6huYTLXr76K/Q4w4G
+Q0+hAgMBAAGjVzBVMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcD
+ATAMBgNVHRMBAf8EAjAAMCAGA1UdEQQZMBeCFWJhY2t1cHMuYXV0aXN0aWNpLm9y
+ZzANBgkqhkiG9w0BAQsFAAOCAQEAI5PWXY7dnjq80p4mJSfN/FfZwHgt5JqFspW+
+JFPedJBdleKDR8dOUENbV+O9TP2zUOiFLYF4cvHZqaJFktsKhD7cZPoEUz2ncSBC
+DKe+hjSfv7W9pwzL0B1q71ldDlCtFD/nhxkwJFzDEEYLgHvGPytfmOALSdaBrTWg
+OSbl0rHDfYCvfHN6hQM3AedivdfbDrUj1HV8tr2Rd6k4LoM0GBs3nqCYC75/XTJd
+n37e6YVc7xmB06HJZ9gvb63wQvpBWDblES1DC7HbueTFS3uC1qQzxBLYMzA2vZTr
+PcsrIhWIkWpAV7yZQiAEDAqT9sk8SlexcuZTePVx5gb8mDctJQ==
+-----END CERTIFICATE-----
diff --git a/testdata/import/backups.autistici.org/fullchain.pem b/testdata/import/backups.autistici.org/fullchain.pem
new file mode 100644
index 00000000..40338eff
--- /dev/null
+++ b/testdata/import/backups.autistici.org/fullchain.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDITCCAgmgAwIBAgIQNgn+VfLxcwjU52sIBdJNKzANBgkqhkiG9w0BAQsFADAg
+MR4wHAYDVQQDExViYWNrdXBzLmF1dGlzdGljaS5vcmcwHhcNMjIwODI1MDkwMzAw
+WhcNMjMwODI1MDkwMzAwWjAgMR4wHAYDVQQDExViYWNrdXBzLmF1dGlzdGljaS5v
+cmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDf2qGOW1yKadm8vvk5
+HB59CE99Abuf9dRyJgOAKlAuX60qmiJi0apvqGAFlWFQ5Nm/OGHyXkwp6teIhABg
+4K6lGQyacCk4s3tnFwErhUUFn0C0iDD2A4FscQN+SjRG70a/pz0FlUsx4oknmqzc
+C7QJAqVZ9rLqc6EN+NbrLwJCy/Dg0mxIGLxWulWnRoTeopRZ3/bTxf2Hd4uyREC8
+GPwmtCDl1Isg+K8KODqIwcnyQJhP7JC3Ry+sShQHnPCqczBiRLgdByPjLBFhqcuP
+jStYwFkcxqOuZel4b8+h1VDLqNwgutdX/IR6WWjcg6yYuUg6huYTLXr76K/Q4w4G
+Q0+hAgMBAAGjVzBVMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcD
+ATAMBgNVHRMBAf8EAjAAMCAGA1UdEQQZMBeCFWJhY2t1cHMuYXV0aXN0aWNpLm9y
+ZzANBgkqhkiG9w0BAQsFAAOCAQEAI5PWXY7dnjq80p4mJSfN/FfZwHgt5JqFspW+
+JFPedJBdleKDR8dOUENbV+O9TP2zUOiFLYF4cvHZqaJFktsKhD7cZPoEUz2ncSBC
+DKe+hjSfv7W9pwzL0B1q71ldDlCtFD/nhxkwJFzDEEYLgHvGPytfmOALSdaBrTWg
+OSbl0rHDfYCvfHN6hQM3AedivdfbDrUj1HV8tr2Rd6k4LoM0GBs3nqCYC75/XTJd
+n37e6YVc7xmB06HJZ9gvb63wQvpBWDblES1DC7HbueTFS3uC1qQzxBLYMzA2vZTr
+PcsrIhWIkWpAV7yZQiAEDAqT9sk8SlexcuZTePVx5gb8mDctJQ==
+-----END CERTIFICATE-----
diff --git a/testdata/import/backups.autistici.org/privkey.pem b/testdata/import/backups.autistici.org/privkey.pem
new file mode 100644
index 00000000..13cefa69
--- /dev/null
+++ b/testdata/import/backups.autistici.org/privkey.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA39qhjltcimnZvL75ORwefQhPfQG7n/XUciYDgCpQLl+tKpoi
+YtGqb6hgBZVhUOTZvzhh8l5MKerXiIQAYOCupRkMmnApOLN7ZxcBK4VFBZ9AtIgw
+9gOBbHEDfko0Ru9Gv6c9BZVLMeKJJ5qs3Au0CQKlWfay6nOhDfjW6y8CQsvw4NJs
+SBi8VrpVp0aE3qKUWd/208X9h3eLskRAvBj8JrQg5dSLIPivCjg6iMHJ8kCYT+yQ
+t0cvrEoUB5zwqnMwYkS4HQcj4ywRYanLj40rWMBZHMajrmXpeG/PodVQy6jcILrX
+V/yEello3IOsmLlIOobmEy16++iv0OMOBkNPoQIDAQABAoIBAQDaW7Hr40LJ4ab7
+bm49PyieXlTrY51Z07p//oS7qIq+MaYBQIj6S/stH9lKmQRsnE+rL5ybh1BUMzg7
+ekfEOGcjtOOOYopTPQDK9q21fjJt4YKEl7TqxVCiLztxHYEREKa6nK/xmkfXSVrw
+vviPTPs5LtOMhhDQkiowLuO2VgymieYjtjhZcJTEDBK6IAGU4kpGsiOjY/AEBUNH
+OqBPeqTpz6nPfIpLdlQrHiiRxGVfumojvyT2EfySaYFx7mLJywq0WctNky8nLMBY
+rlsXbLinG5PSmaDCr4TyzUCFa5Zl/BBTRrlex2Uri8FOjWGl3xo+QvG41Z6swbfz
+IaS+kSKBAoGBAOkQt8tOEZzb0skNd77LBghrW9k2/ElAPjQpinUqg7ZR/+MV606A
+8l/dpLCpfW7CtohDhKY9ZhPAyHtU2NJjeH7rSKPK8tUf1b7coxdb60qsG30MAhOt
+33g2VlVwSTF3gLF8OKfyE89WripMFKoRwW49K9gXRPr+5YEQq7Tw6z59AoGBAPXh
+3dcWP3MTXFNT3LHPbF4l8+l/7NWII9EHX2QuETwS9rvYaECpYG2aQib6KEvKuQUh
+rDbD2tHFW+gaFT8lQQa/RaKw+eQbCUcPnoolsok7kmUnrw2ItZGjKO/6t5yt8a4W
+0SKoYXe25yHh+HnIzJo3ao9k/TJ7zzu88TCGYCr1AoGBAIyU6/gy+AbAxe1GNWOd
+fN8ZOwbT1uusV5kJMZ4o7dfMsGfesdfhidFvlUaGhcWIp6eb6miIAomJsKRL8pk9
+LNKJw4AhC8aodDqzRaDybgPVuqL72kjaiUAurnbMyymOZs8oGdib+X0IUPfsek53
+dVC0jzzCFHa9x9vJHXmAFLXVAoGATBoIS1X7dWZ8oN3H5NDKPamJgK+zYbOtQjkB
+8UPA2dMUwrcMLVc/HS0tZFrwteSct1Xgs2KTC3IGLCRGhzDEm43/w4b7EPq9OF1B
+OIjKZWcmvGA38fxqFhVlykDTK486yiMNv6hV4nOdvgLuoOLhUKnuNUuQgluHnHmy
+PM6aXTECgYBBwpB7GTp7nxalW1dm6IaH7pd1uHcJ4FOfiF5NLhJ3F/iK4Pg8z+ll
+wfXzl7045ST93pc7V4FKru/KL52tGjN0no9PqzxzOlxpMOAEpbYwNz4jL+Nrsgf0
+YUMonbRmVfDDZ41HPJWLPgTxlZcHHGc6p8B1jygcioaWlrs6tIhK+Q==
+-----END RSA PRIVATE KEY-----
diff --git a/testdata/import/grafana.autistici.org/cert.pem b/testdata/import/grafana.autistici.org/cert.pem
new file mode 100644
index 00000000..66818516
--- /dev/null
+++ b/testdata/import/grafana.autistici.org/cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDIjCCAgqgAwIBAgIRAJugR9gsLtQFy05zhZQPsF0wDQYJKoZIhvcNAQELBQAw
+IDEeMBwGA1UEAxMVZ3JhZmFuYS5hdXRpc3RpY2kub3JnMB4XDTIyMDgyNTA5MDMw
+MloXDTIzMDgyNTA5MDMwMlowIDEeMBwGA1UEAxMVZ3JhZmFuYS5hdXRpc3RpY2ku
+b3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2xct8BIZo8BocfWO
+r0PYlogNo27vRd7KQtUf5T9XtY4eHa0jHeiCaKsiE77AMisf6V1x0ijYH5OgSHeQ
+nRGWT3Tsrb9C9672e3W1nVL4CvcBLPT6oOXLJNUGFhxYVEwPkFuYf1J34vKGratL
+6scNQFAw7C8EVasAmjE48sUTMVHOk2nN5Bjozu0+Didew+KGXG7unLFmJdesbWjO
+Uf3zuhLS/BCBuMSvPEdMxEB7Tfvn5RbTFbFKOvxwSkxM3gOdd/d1sY8Fp8otwDCC
+Voz9mOCAZfK8tLFvuOLoLQ6UYWZ76CHWH832brw7CePTDYm5XmCgeqLa46cweJXc
+318q5QIDAQABo1cwVTAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUH
+AwEwDAYDVR0TAQH/BAIwADAgBgNVHREEGTAXghVncmFmYW5hLmF1dGlzdGljaS5v
+cmcwDQYJKoZIhvcNAQELBQADggEBAHaOh7CbOc3OnfasBoSXw262HmnNr8vD7krz
+kvW4ChKntmzoqQZzls5vvi7+ucuX5C9uyymrqbwp79ShE8Ul/R5vzn2DTy1X1f4I
+uDVYvTuHTOLNR/AnD2lCt2ArBjsowZvLTsyz1KsW80PrLezJXua0qDeLA61nOLv0
+WNVjVuMEiml3Vro50frkBEl0qeSbqiyIrGzAe6i9nc+f3xyo5L0I6KIaXwtdxlfM
+0HO0jtEc59U+TxrKIU7+OIicHGXIIHydgf3jEmuYR4ivXdkAFpl478NTuuUGVEjW
+RRP3eU3jufN/raqRhKOw1X0PSoT8NzkOv8Ax99rOq6+GL/lX6Fk=
+-----END CERTIFICATE-----
diff --git a/testdata/import/grafana.autistici.org/fullchain.pem b/testdata/import/grafana.autistici.org/fullchain.pem
new file mode 100644
index 00000000..66818516
--- /dev/null
+++ b/testdata/import/grafana.autistici.org/fullchain.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDIjCCAgqgAwIBAgIRAJugR9gsLtQFy05zhZQPsF0wDQYJKoZIhvcNAQELBQAw
+IDEeMBwGA1UEAxMVZ3JhZmFuYS5hdXRpc3RpY2kub3JnMB4XDTIyMDgyNTA5MDMw
+MloXDTIzMDgyNTA5MDMwMlowIDEeMBwGA1UEAxMVZ3JhZmFuYS5hdXRpc3RpY2ku
+b3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2xct8BIZo8BocfWO
+r0PYlogNo27vRd7KQtUf5T9XtY4eHa0jHeiCaKsiE77AMisf6V1x0ijYH5OgSHeQ
+nRGWT3Tsrb9C9672e3W1nVL4CvcBLPT6oOXLJNUGFhxYVEwPkFuYf1J34vKGratL
+6scNQFAw7C8EVasAmjE48sUTMVHOk2nN5Bjozu0+Didew+KGXG7unLFmJdesbWjO
+Uf3zuhLS/BCBuMSvPEdMxEB7Tfvn5RbTFbFKOvxwSkxM3gOdd/d1sY8Fp8otwDCC
+Voz9mOCAZfK8tLFvuOLoLQ6UYWZ76CHWH832brw7CePTDYm5XmCgeqLa46cweJXc
+318q5QIDAQABo1cwVTAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUH
+AwEwDAYDVR0TAQH/BAIwADAgBgNVHREEGTAXghVncmFmYW5hLmF1dGlzdGljaS5v
+cmcwDQYJKoZIhvcNAQELBQADggEBAHaOh7CbOc3OnfasBoSXw262HmnNr8vD7krz
+kvW4ChKntmzoqQZzls5vvi7+ucuX5C9uyymrqbwp79ShE8Ul/R5vzn2DTy1X1f4I
+uDVYvTuHTOLNR/AnD2lCt2ArBjsowZvLTsyz1KsW80PrLezJXua0qDeLA61nOLv0
+WNVjVuMEiml3Vro50frkBEl0qeSbqiyIrGzAe6i9nc+f3xyo5L0I6KIaXwtdxlfM
+0HO0jtEc59U+TxrKIU7+OIicHGXIIHydgf3jEmuYR4ivXdkAFpl478NTuuUGVEjW
+RRP3eU3jufN/raqRhKOw1X0PSoT8NzkOv8Ax99rOq6+GL/lX6Fk=
+-----END CERTIFICATE-----
diff --git a/testdata/import/grafana.autistici.org/privkey.pem b/testdata/import/grafana.autistici.org/privkey.pem
new file mode 100644
index 00000000..91b63493
--- /dev/null
+++ b/testdata/import/grafana.autistici.org/privkey.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA2xct8BIZo8BocfWOr0PYlogNo27vRd7KQtUf5T9XtY4eHa0j
+HeiCaKsiE77AMisf6V1x0ijYH5OgSHeQnRGWT3Tsrb9C9672e3W1nVL4CvcBLPT6
+oOXLJNUGFhxYVEwPkFuYf1J34vKGratL6scNQFAw7C8EVasAmjE48sUTMVHOk2nN
+5Bjozu0+Didew+KGXG7unLFmJdesbWjOUf3zuhLS/BCBuMSvPEdMxEB7Tfvn5RbT
+FbFKOvxwSkxM3gOdd/d1sY8Fp8otwDCCVoz9mOCAZfK8tLFvuOLoLQ6UYWZ76CHW
+H832brw7CePTDYm5XmCgeqLa46cweJXc318q5QIDAQABAoIBAG/Oi3X5drY3idWm
+UBOnVw+zDOq7wUXx5Vxn8SgHxFX12e6hHcu0jWCm/PQAo4q2sUlhk6WBjJHIv8Jb
+qiROBlw0OliMw7sswcLNlkpcPhMybJopxwF3Y80/HSUJgBY83rB33gIT7vSVeh+E
+zHBa9qs8gUr2z4p8gwktV3elFR9UQzI+5V2SHgZ3KBkPv++z3PSZGdoQEM0ZHnKZ
+UVov+ErqCDDCg/AUZNKdVVujEI6nAxR8OWL2NjQ0IKVlWbC7u5zUD93xG9lX3fp6
+WToJgV+hzTBCc1els51809NBXDnzErYUJTJfdDieJQjeATLbNdY0hoTD2y+5gB9t
+bR8VsCECgYEA39yZR5dwvnS745Kj5TBJ531AlHd4H96n9xAlvOuDBAse8EZCJXOD
+oZU2mJQScQnMJ4ZpbA1xl1hFz7Gy+Fvw+BKkXiXShAs/WKAn+y/VnaBjv2BQ/pBN
+YYp0Tvdr0YjHvdE2xyyYoobeIAK3QzkXAPpfn7ni7/UcxG2Gi8B8xU0CgYEA+os6
+3Kb/Yd1q7tCKEq7mmxZG1cEe5Bgomd1Gd4VZCAxRa8k4RLieORFr3hK1E6kRDUbq
+eEyNPmno5v09DGsw0xaIec4H6ytWJxu6o7L0L4R785/esJXloFj4uA5SU0/pNOwZ
+zyG2Ectj/ZjQMEQrAkpQKKUtAgsjyTKBladiz/kCgYEAo1qnY02IEQK/5IWqIWFG
+XKzShkRTnPXuHWRkFWLKPRYcYnryY7/SSwPGTjqm5mOe3SzBGv8IZ8cbUj66eW8l
+/PAljyoMXxySAgsEgdBbEeXrQ8h18fy86rOMwd7ch49HKiVT0q2hR+AgkL1Km7w6
+JBWP189eYXm8nz3JE4vHwHUCgYEAhxhQEu6RQinsIP2dldltSy7F3ziU+ZT1P5IL
+DyN+EFLkDGShzJxn5Im6SjJ4JTPCmBAnGdrdRkxBY1wlcO7Tkt7RlCw+JNZnTAst
+pls2Q/ECDSmlID6TZNTUuBow1e9+5iRw/jlFPW8YaYPerkfzkPaVJnV6QWHUWAa9
+zP5Gz7ECgYBpLFjzjXtVpZ9pYmVgt307QcFjFoRwA14F1t0nzs1/PXRijTJadC09
+y/qGmNTnr+nqxUmyyFamcrHt4Gba1Lm4fQduSCjjoIbWNRtfusRLi66S+YD9hN56
+UpwvpKl7FkyUmfz376GguAfL1mKQDUIXQLnNyHlCIN2E4r0RgnBtuw==
+-----END RSA PRIVATE KEY-----
diff --git a/testdata/import/lists.autistici.org/cert.pem b/testdata/import/lists.autistici.org/cert.pem
new file mode 100644
index 00000000..db20d56b
--- /dev/null
+++ b/testdata/import/lists.autistici.org/cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDGzCCAgOgAwIBAgIQCtz2rvIreXvgaASYl5J+GzANBgkqhkiG9w0BAQsFADAe
+MRwwGgYDVQQDExNsaXN0cy5hdXRpc3RpY2kub3JnMB4XDTIyMDgyNTA5MDMwMVoX
+DTIzMDgyNTA5MDMwMVowHjEcMBoGA1UEAxMTbGlzdHMuYXV0aXN0aWNpLm9yZzCC
+ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMEXHBw6AvMBFhqaxEov0+cw
+h9+yCHduIy3fZPJoIR6LPsxgPvHUI3LOimJeAkom1dXEEFgfcrTyJOp4fJxIqD98
+Vfenf2s687p6AmOljlnsI+TwWwtmVv3mUdQIUUS9+DShcFqyVUcaIadqiP5Uysdh
+BHdL2RUTBAOZ80TexebV3YVn/HTXglggnbMvc1QeMOWQTSCQBz7+i/59SBUik/zG
+GT4nzZ4jKwknqdtBnGhBA6HpASzP4G3QqWDEM81yp8gojxgugQVuzSR+i7IqpM2O
+uawcfYOybdi5w1TxAPoiraqdr3hreWjEBp4CBjqvpR/fNHFJjJ2wAMhEHH66NhUC
+AwEAAaNVMFMwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwG
+A1UdEwEB/wQCMAAwHgYDVR0RBBcwFYITbGlzdHMuYXV0aXN0aWNpLm9yZzANBgkq
+hkiG9w0BAQsFAAOCAQEAmSSPAh/yBZ2UmmdrfwUPT5OOgB6Vrr6+BSGQtb79LiIk
++G/orwGXyUUZ9eki5EKTk1kGjNn3GdSn+MUO0OT/uf9vFO+rfqqI1wW8R3UlDWIx
+nVZfh+LM11qIwCFL8BqpxIBx98G7Dk1Z0I3oaXC4heCQZQVdtMIO8stPorch7x5C
+cj826TH+ItD5qx6An8ztmtrCw0smruyhbo22/SYKW86i/j6bKZRDjH3dQsnVefvy
+lLhmzlDNSeqPNhGVmg78vPERrT2D+XUZmGPKi1N9SLzpSQaMaSOVYaz9K3+ZHGdZ
+rcM2aYqq7N/49arrlP9+ZXQi6MLsXpOGPhvvR/AnXg==
+-----END CERTIFICATE-----
diff --git a/testdata/import/lists.autistici.org/fullchain.pem b/testdata/import/lists.autistici.org/fullchain.pem
new file mode 100644
index 00000000..db20d56b
--- /dev/null
+++ b/testdata/import/lists.autistici.org/fullchain.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDGzCCAgOgAwIBAgIQCtz2rvIreXvgaASYl5J+GzANBgkqhkiG9w0BAQsFADAe
+MRwwGgYDVQQDExNsaXN0cy5hdXRpc3RpY2kub3JnMB4XDTIyMDgyNTA5MDMwMVoX
+DTIzMDgyNTA5MDMwMVowHjEcMBoGA1UEAxMTbGlzdHMuYXV0aXN0aWNpLm9yZzCC
+ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMEXHBw6AvMBFhqaxEov0+cw
+h9+yCHduIy3fZPJoIR6LPsxgPvHUI3LOimJeAkom1dXEEFgfcrTyJOp4fJxIqD98
+Vfenf2s687p6AmOljlnsI+TwWwtmVv3mUdQIUUS9+DShcFqyVUcaIadqiP5Uysdh
+BHdL2RUTBAOZ80TexebV3YVn/HTXglggnbMvc1QeMOWQTSCQBz7+i/59SBUik/zG
+GT4nzZ4jKwknqdtBnGhBA6HpASzP4G3QqWDEM81yp8gojxgugQVuzSR+i7IqpM2O
+uawcfYOybdi5w1TxAPoiraqdr3hreWjEBp4CBjqvpR/fNHFJjJ2wAMhEHH66NhUC
+AwEAAaNVMFMwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwG
+A1UdEwEB/wQCMAAwHgYDVR0RBBcwFYITbGlzdHMuYXV0aXN0aWNpLm9yZzANBgkq
+hkiG9w0BAQsFAAOCAQEAmSSPAh/yBZ2UmmdrfwUPT5OOgB6Vrr6+BSGQtb79LiIk
++G/orwGXyUUZ9eki5EKTk1kGjNn3GdSn+MUO0OT/uf9vFO+rfqqI1wW8R3UlDWIx
+nVZfh+LM11qIwCFL8BqpxIBx98G7Dk1Z0I3oaXC4heCQZQVdtMIO8stPorch7x5C
+cj826TH+ItD5qx6An8ztmtrCw0smruyhbo22/SYKW86i/j6bKZRDjH3dQsnVefvy
+lLhmzlDNSeqPNhGVmg78vPERrT2D+XUZmGPKi1N9SLzpSQaMaSOVYaz9K3+ZHGdZ
+rcM2aYqq7N/49arrlP9+ZXQi6MLsXpOGPhvvR/AnXg==
+-----END CERTIFICATE-----
diff --git a/testdata/import/lists.autistici.org/privkey.pem b/testdata/import/lists.autistici.org/privkey.pem
new file mode 100644
index 00000000..7c690f64
--- /dev/null
+++ b/testdata/import/lists.autistici.org/privkey.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAwRccHDoC8wEWGprESi/T5zCH37IId24jLd9k8mghHos+zGA+
+8dQjcs6KYl4CSibV1cQQWB9ytPIk6nh8nEioP3xV96d/azrzunoCY6WOWewj5PBb
+C2ZW/eZR1AhRRL34NKFwWrJVRxohp2qI/lTKx2EEd0vZFRMEA5nzRN7F5tXdhWf8
+dNeCWCCdsy9zVB4w5ZBNIJAHPv6L/n1IFSKT/MYZPifNniMrCSep20GcaEEDoekB
+LM/gbdCpYMQzzXKnyCiPGC6BBW7NJH6LsiqkzY65rBx9g7Jt2LnDVPEA+iKtqp2v
+eGt5aMQGngIGOq+lH980cUmMnbAAyEQcfro2FQIDAQABAoIBAFYm8oLhm1lDPdKH
+BVe4eUqoy6cSb1aiTI32I7tInKZdKJrVwp/xsBNUdkgne8wMAdLMFSLMfUig1bnM
+IoU0oeUZm93Q3ThB6GdmctR1FMuoYeePGoA+nEfS0OXTBDewryMEePXupcAAt81T
+6mXX/VXyQqTbsbMGdf8JXJJ490HjmJAspWnBR8MDbpjT2x3QcsvMYUdDtRQNO/RM
+y79j/QGOoh6W3lLTNkbdai0Kf8Objj4rSwgZCj3oom+VmPKagQ3PiK71X30+d7wS
+eb6m8w1/tCFYFrRs7wRJjtdUZovxlrM0eJsySM8j/wJpu4cqmXSubgD03iObs+hz
+oKNtmS0CgYEAxVdPJvWg0A0IMUCD2B57sTyRe+Stvs9eXkoXAhlaXapz1XlPInRz
+pTZYhHJf/+hX3NAbBte9LzH005zoVQM/tmhGLqKmqdNSMZd1URjYYidfyaIoqfzI
+yOqRZws6NVysgpXE+o2/zbUXwo9G8YbonJPQo8UjZwdesqDxlH5jm6cCgYEA+nxW
+S+cKNWlwHYwQayy1aZrEacW/EWn+XOow/bX0s2Wle3KcwpNwdffB2x73lDL6sFoB
+758qHgWUcfMHlBeEiDntTGo8hNVqfaPDAgzjROmosb9yaXUttY3FTGhc0rQXdi6j
+AZgqgHv59HR9jWxqbZfH1m108ICiHdoS5n3hZ+MCgYBB/TcnpP14dNMnfRZZf6AH
+HWTc1BRReRBqYUYeWpIAPk5rtRwEPoKpXWnuKDz32x7+ZMyU1jGaBY+D77U1MFfu
+zCGj2Jn4sgSLNsVxL+izsczzJPmKFlCh9uZWWcfGLk41gJWU3y+jPwpJ8hVcEKva
+KAedq/9xpWJCQ1VlPXpbgwKBgH8+Ymg0ec8gPMAFwuFu2MlIwbYpqiIiggCZR0Hb
+CO+JF0NauZ9uXpbOwT+av5IdQ5QKZSlg5XhSxVINbzAfskexhYZoAU5JS8t/QTTt
+bxY2zGdfJbPEvu+XkT57VYwDyFdU4aGRtyWF1TgFZTJOfNInZHDdMaZq8e/QNAG6
+uKKZAoGAFZm/nRPzy4UOd6sG3D/c/trWJsInVQiGHR0zqtnljojORPfkL04GP61z
+ljfceRadsonSIhRsiAvSVHK4fk/vHDoNfycrQMqdjGad7rTO82k206yKM7BRYPaw
+8A4BD/boLx++L9e6FY1MVp9IIw3SPmcXWTAvtiVbDy+/3EFyA+Q=
+-----END RSA PRIVATE KEY-----
diff --git a/testdata/import/mail.autistici.org/cert.pem b/testdata/import/mail.autistici.org/cert.pem
new file mode 100644
index 00000000..5e1a2c16
--- /dev/null
+++ b/testdata/import/mail.autistici.org/cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDGTCCAgGgAwIBAgIRAMF+9tJ45PRlQBBoueORircwDQYJKoZIhvcNAQELBQAw
+HTEbMBkGA1UEAxMSbWFpbC5hdXRpc3RpY2kub3JnMB4XDTIyMDgyNTA5MDMwMVoX
+DTIzMDgyNTA5MDMwMVowHTEbMBkGA1UEAxMSbWFpbC5hdXRpc3RpY2kub3JnMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw+eLGbe1FuRKDr8JU68rNGCU
+rM22LNwhQSNpC1SpYxE6z9kHmLATDgWUX7HiKqzcv4i8xUiqBFkv0mPePrkR+3/I
+nU+61Zi8sZngJcVvQ46X1NrjCgHEJrbDaBf3XWfqEfct7bHXmRo38NhV4Cnxj7Fd
+R4M70SI4usw3KP2bvQ3UL7zi7wmvZwAjJ95wc/ZAtKjvUX+1/Vzfhs44Z0rwp9+s
+jnPMZWmK8yR7mM/5EhpTUi03AAl2CElFnBP3gpAz0XrKk1dGRXaPqWo4sug+9a5j
+iHDF2ET0f2S1xGu44LIjGQe249r9v9UayGakLJ7HMFI6B0HTr2ohYlqLzD+K7QID
+AQABo1QwUjAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYD
+VR0TAQH/BAIwADAdBgNVHREEFjAUghJtYWlsLmF1dGlzdGljaS5vcmcwDQYJKoZI
+hvcNAQELBQADggEBAIZIpOiYx/2IE9tbpLsqSLC1VbO9yOrILu1t2YtlJh4j67bX
+DJqU96uRq/ekDC3cCNnC9uw4nAyDQUVmJeNppNjtu86K6J/f/uVv9vJmWVyyqHKD
+yHfJwJDp0PTMwl91LFY7PghshXaraxb2S3MI63KX7QzwCtWIUTzRTs8cuDvXzQcR
+ZT5hIcaH62jmyCx/BwvDYewxY3gYSZ5bjYZ2exmTJ9aXjG5mEfT1oddbna9RTUYU
+7CjcNJNqrEtlEBAr6gq1ylPHhYaoHIpjwAqwwJwzYC8vyKyZ/TY65TKB83SX07+c
+ge/dJatwdodr4OH598E3O/MXKngoYG8R0rnAcTA=
+-----END CERTIFICATE-----
diff --git a/testdata/import/mail.autistici.org/fullchain.pem b/testdata/import/mail.autistici.org/fullchain.pem
new file mode 100644
index 00000000..5e1a2c16
--- /dev/null
+++ b/testdata/import/mail.autistici.org/fullchain.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDGTCCAgGgAwIBAgIRAMF+9tJ45PRlQBBoueORircwDQYJKoZIhvcNAQELBQAw
+HTEbMBkGA1UEAxMSbWFpbC5hdXRpc3RpY2kub3JnMB4XDTIyMDgyNTA5MDMwMVoX
+DTIzMDgyNTA5MDMwMVowHTEbMBkGA1UEAxMSbWFpbC5hdXRpc3RpY2kub3JnMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAw+eLGbe1FuRKDr8JU68rNGCU
+rM22LNwhQSNpC1SpYxE6z9kHmLATDgWUX7HiKqzcv4i8xUiqBFkv0mPePrkR+3/I
+nU+61Zi8sZngJcVvQ46X1NrjCgHEJrbDaBf3XWfqEfct7bHXmRo38NhV4Cnxj7Fd
+R4M70SI4usw3KP2bvQ3UL7zi7wmvZwAjJ95wc/ZAtKjvUX+1/Vzfhs44Z0rwp9+s
+jnPMZWmK8yR7mM/5EhpTUi03AAl2CElFnBP3gpAz0XrKk1dGRXaPqWo4sug+9a5j
+iHDF2ET0f2S1xGu44LIjGQe249r9v9UayGakLJ7HMFI6B0HTr2ohYlqLzD+K7QID
+AQABo1QwUjAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYD
+VR0TAQH/BAIwADAdBgNVHREEFjAUghJtYWlsLmF1dGlzdGljaS5vcmcwDQYJKoZI
+hvcNAQELBQADggEBAIZIpOiYx/2IE9tbpLsqSLC1VbO9yOrILu1t2YtlJh4j67bX
+DJqU96uRq/ekDC3cCNnC9uw4nAyDQUVmJeNppNjtu86K6J/f/uVv9vJmWVyyqHKD
+yHfJwJDp0PTMwl91LFY7PghshXaraxb2S3MI63KX7QzwCtWIUTzRTs8cuDvXzQcR
+ZT5hIcaH62jmyCx/BwvDYewxY3gYSZ5bjYZ2exmTJ9aXjG5mEfT1oddbna9RTUYU
+7CjcNJNqrEtlEBAr6gq1ylPHhYaoHIpjwAqwwJwzYC8vyKyZ/TY65TKB83SX07+c
+ge/dJatwdodr4OH598E3O/MXKngoYG8R0rnAcTA=
+-----END CERTIFICATE-----
diff --git a/testdata/import/mail.autistici.org/privkey.pem b/testdata/import/mail.autistici.org/privkey.pem
new file mode 100644
index 00000000..c9d68aba
--- /dev/null
+++ b/testdata/import/mail.autistici.org/privkey.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEAw+eLGbe1FuRKDr8JU68rNGCUrM22LNwhQSNpC1SpYxE6z9kH
+mLATDgWUX7HiKqzcv4i8xUiqBFkv0mPePrkR+3/InU+61Zi8sZngJcVvQ46X1Nrj
+CgHEJrbDaBf3XWfqEfct7bHXmRo38NhV4Cnxj7FdR4M70SI4usw3KP2bvQ3UL7zi
+7wmvZwAjJ95wc/ZAtKjvUX+1/Vzfhs44Z0rwp9+sjnPMZWmK8yR7mM/5EhpTUi03
+AAl2CElFnBP3gpAz0XrKk1dGRXaPqWo4sug+9a5jiHDF2ET0f2S1xGu44LIjGQe2
+49r9v9UayGakLJ7HMFI6B0HTr2ohYlqLzD+K7QIDAQABAoIBAQCgaFEPh8qxqc+x
+716VtRKtWn+iVvZaGQzppzL14iaoRY1gbxa3QUT48DyjdqX3x38hHbdj2rHLybNM
+lBWASk2dHu6HUKdtChlHWvWcN+N476MG2QuaQAP6px+tdiJHyzRv7XMf88TTShKQ
+FLOMTwvKWrJEQgKSpXDKEzj/SVliHCeIYpy0cyqp303WaiZOGurSXPzVLVKXU2WX
+/EXddfUDRod2TWW3V6IoGuAsTH9eOKEbbAY7g0C5Cu855Am3vpho+RYSO/d7XeQy
+t6pD/2vldFyd+4adpW1vW/VRG8zHcXcu8sWNgLwE3GBcH2bCn/o3B2bWxdP0DEau
+5fwIyGgBAoGBANX0hBqI6nkZSzy7YHiliGaja5oIxNVyimWTeZHmAuRTqpnlgCY8
+lmlCi90ck4Cdnle6kONKH+osJVK+pazOtRzFgUr0e2DofYV2433uUGCBoIX4v/FM
+Mxk6qPkyWAiKHs1zJB5umaGYjlhsi33AW0HcBTcfP6w1FweH9YF0NNkBAoGBAOpm
+8s1sDDDyKyLUmxCyuaSI+4p02QdGxj17N5Q+kficZfLudwCxQeq5ZdR2vL55dv71
+xHMLzf1+hhaVUmoOVWNvihmZhncjCDZhOLm9NbgX8TGeHcm2WLuiugQWHL50By5M
+UQL791dzmDdXUztAUD6pkBYtvevuiQ2RZrv6daXtAoGBAMEIMoeeL63CXFcW1xsC
+feXcWyBF+RB6ntK2E2DGnKVTtv/SR6Q31ZxyJa4AMjRAALQ15rKqFdE4sxPMXb1j
+YZPrvZLasNXl7BmWAfmEIZBwY1t76fDSRufkPL9X5IUP2GqUD2pFbuaUtRiiFdgG
+zE7HKw1obBH6dCSfTajWJzgBAoGBAIse2QLlGwM96C4/A13QxfAKwUi2+0yfS++X
+nzAkRtzIbpXVxNmrWxUmPnLwmituyhqe4Z7qpbLLkmf67jQ2F6XxwIRbI3y4VRp5
+b4KF80k1JJkvYDylNlr/ROpB+YNsOArZjsd3pqDH4dMOyD6FeFsIppUTRwKty7g6
+0T/GcmctAoGAcHhCD/cNYrPHTnLa4m+GJpa5VefcTzPLdNhBZRY87f7pQlH47IYV
+jIfUr3EtGGYzag2v0h4C37w/wHtOHzbNh9194Xm7IP52vnnPCgihMLf7UeRBkAdr
+D/x3Gkg9jJ0kYQKgJcJ0lxbGLOwXcdCEGFTtvhQjmfvBF1gDUIRc+A8=
+-----END RSA PRIVATE KEY-----
diff --git a/testdata/import/monitor.autistici.org/cert.pem b/testdata/import/monitor.autistici.org/cert.pem
new file mode 100644
index 00000000..69033f1d
--- /dev/null
+++ b/testdata/import/monitor.autistici.org/cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDITCCAgmgAwIBAgIQMea9zIQBleEwZv6jCOmC/TANBgkqhkiG9w0BAQsFADAg
+MR4wHAYDVQQDExVtb25pdG9yLmF1dGlzdGljaS5vcmcwHhcNMjIwODI1MDkwMzAy
+WhcNMjMwODI1MDkwMzAyWjAgMR4wHAYDVQQDExVtb25pdG9yLmF1dGlzdGljaS5v
+cmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDF4t4QLjSjeECkQb/2
+2lpUGnjclckomcOqjh15wde0hZTgS7dYHb0RW5Btpc2i13PUyePd6m3Iu0h2GMoJ
+/bjjwidIxnnzMSZt4G6KdPRJ33d27JjRQob++Aclk/A1DmOXQZWlewPLfy0pUhKb
+nvngF5zwpKJXbbwbBjVR4SJp+2bnQSWDISkCOatnyzmtRAAD4pOeC/Z61ZGxRXEq
+mFUc4SHKdfZoM+F4vU7uOHxGib+veaY5wqfg8snI/jcMpSxnnlWXWUOShSKLkcxm
+wLEm5M9yY+cvvzXQunwSuligLzPSuU4QHaDjHIgApvMehPUUXzxJR5u4H4wHddCq
+/NvBAgMBAAGjVzBVMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcD
+ATAMBgNVHRMBAf8EAjAAMCAGA1UdEQQZMBeCFW1vbml0b3IuYXV0aXN0aWNpLm9y
+ZzANBgkqhkiG9w0BAQsFAAOCAQEAZpAeAU7gjzB02W7MT3cU0m8X5gNuMdQMuevd
+fb27AkhC47Oci5EkdQx5LPqwjSqIQZyGOlgm9HRyjQ+hcaDZ6M2aMyBEFdKJZz95
+zizwAEE3bQgZkRk0ZgjYWKHuw1KnnLUDCCQgU9iG9hinKYp02fz22fWCHsWYael4
++dx7nl8aWAAR47oSUi/oQCf9ZPXKg8C3PvVwdh/iHthIYuYYctXD+CiUoPBYPuFC
+e+VwvrzEury7MfNFgPTXbPIXoZhIkQsNKZZLFldFaIL0Z8Q7g107KEyQcOqaoLZi
+kldIN960UAXCmg6zV3hIAs0o5WTkmFbWkcAU34LRDTBv4pL3jw==
+-----END CERTIFICATE-----
diff --git a/testdata/import/monitor.autistici.org/fullchain.pem b/testdata/import/monitor.autistici.org/fullchain.pem
new file mode 100644
index 00000000..69033f1d
--- /dev/null
+++ b/testdata/import/monitor.autistici.org/fullchain.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDITCCAgmgAwIBAgIQMea9zIQBleEwZv6jCOmC/TANBgkqhkiG9w0BAQsFADAg
+MR4wHAYDVQQDExVtb25pdG9yLmF1dGlzdGljaS5vcmcwHhcNMjIwODI1MDkwMzAy
+WhcNMjMwODI1MDkwMzAyWjAgMR4wHAYDVQQDExVtb25pdG9yLmF1dGlzdGljaS5v
+cmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDF4t4QLjSjeECkQb/2
+2lpUGnjclckomcOqjh15wde0hZTgS7dYHb0RW5Btpc2i13PUyePd6m3Iu0h2GMoJ
+/bjjwidIxnnzMSZt4G6KdPRJ33d27JjRQob++Aclk/A1DmOXQZWlewPLfy0pUhKb
+nvngF5zwpKJXbbwbBjVR4SJp+2bnQSWDISkCOatnyzmtRAAD4pOeC/Z61ZGxRXEq
+mFUc4SHKdfZoM+F4vU7uOHxGib+veaY5wqfg8snI/jcMpSxnnlWXWUOShSKLkcxm
+wLEm5M9yY+cvvzXQunwSuligLzPSuU4QHaDjHIgApvMehPUUXzxJR5u4H4wHddCq
+/NvBAgMBAAGjVzBVMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcD
+ATAMBgNVHRMBAf8EAjAAMCAGA1UdEQQZMBeCFW1vbml0b3IuYXV0aXN0aWNpLm9y
+ZzANBgkqhkiG9w0BAQsFAAOCAQEAZpAeAU7gjzB02W7MT3cU0m8X5gNuMdQMuevd
+fb27AkhC47Oci5EkdQx5LPqwjSqIQZyGOlgm9HRyjQ+hcaDZ6M2aMyBEFdKJZz95
+zizwAEE3bQgZkRk0ZgjYWKHuw1KnnLUDCCQgU9iG9hinKYp02fz22fWCHsWYael4
++dx7nl8aWAAR47oSUi/oQCf9ZPXKg8C3PvVwdh/iHthIYuYYctXD+CiUoPBYPuFC
+e+VwvrzEury7MfNFgPTXbPIXoZhIkQsNKZZLFldFaIL0Z8Q7g107KEyQcOqaoLZi
+kldIN960UAXCmg6zV3hIAs0o5WTkmFbWkcAU34LRDTBv4pL3jw==
+-----END CERTIFICATE-----
diff --git a/testdata/import/monitor.autistici.org/privkey.pem b/testdata/import/monitor.autistici.org/privkey.pem
new file mode 100644
index 00000000..f19a8516
--- /dev/null
+++ b/testdata/import/monitor.autistici.org/privkey.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAxeLeEC40o3hApEG/9tpaVBp43JXJKJnDqo4decHXtIWU4Eu3
+WB29EVuQbaXNotdz1Mnj3eptyLtIdhjKCf2448InSMZ58zEmbeBuinT0Sd93duyY
+0UKG/vgHJZPwNQ5jl0GVpXsDy38tKVISm5754Bec8KSiV228GwY1UeEiaftm50El
+gyEpAjmrZ8s5rUQAA+KTngv2etWRsUVxKphVHOEhynX2aDPheL1O7jh8Rom/r3mm
+OcKn4PLJyP43DKUsZ55Vl1lDkoUii5HMZsCxJuTPcmPnL7810Lp8ErpYoC8z0rlO
+EB2g4xyIAKbzHoT1FF88SUebuB+MB3XQqvzbwQIDAQABAoIBAEq81IHn8ZmL4V4o
+NwhUj6kUFnG5YLAk4vGXmypRO9bCXOgZsLVnBfF4U6QV6wSFuwmy9ozp7WzSzxtL
+YYNEgIAE5DFoRMbE2W7GRPmmEl2UONqBoXXnWnUR2CEpLp/bP2HxM0O8okJJVhU1
+GjIMaPNp96wrgeTjXRgX0CuMjCLaViQ37dzOJfnr8Y3GX1wcilVjgfc6e5NHlAso
+mzkayXOkv9o10UfCxNDITtIQutGkUQVpiQCaodGDwf3YuuruTyzRfmPymYkeigMQ
+0/fu49gJuc62icXPROlfQpW9JKy+3LiA2oB7kI3YbpMN9d/Ddh2e1rn+BUQkE3YE
+dKRZAAECgYEA0dZrYhli0vrIKBsSSDe+y9NVaxgdoNWURMvr6ADhrAanxJ2aYow8
+ad4m90O8BrkGI9hhpktFkTzEqKMF7dm7gVrSJ1+S2jyXU2EnOrE9OgMKDLMzWrrr
+7Nc9sBsKNL+PMmPlbF3gXm8MyvcO+imLXfV2l1PBKc4jkAGzFzMcw4ECgYEA8Wtf
+loTCHQUbWAIsep4bEEKceNYPkT32QvBOYe0VyTTbVZLmtiOUDFXG1SjA1daqeCk6
+mg45N0fzBCwls+WJlZZ4qXIDqmoIwBcbuilSMhAFmfN6bmBgqTIJ/o2d/rURGNrp
+h1RZNxpxaNqh+XoXUPIDE5MuFeJGibxe1C9rOEECgYBwVFNZyXMwtBUv+wX3aPgX
+TsN8OAWsUokzpuJbaRVlrI5k32WE+cNhfQ3o8faWtreFutDTX5oXHKu+Br8my5bn
+sUGs2CnZ7/OmK+z/K2A2TOg1UjzchhHD0JlyS5dUE3PJqtZnA34Xr2TuxgDbhB/a
+JYvdiHVwNqys62v+sFrmgQKBgQCP2kYz/1lXR3K6p1jvm4RO1RI9onfWONCjpGNg
+UJBZu0gynn0g7ORL0VTQMR9ujCI5xX9oyq7nNNUmmFPC9TRdUTMG7KwogFREecCI
+1hRnRkrAKGe8m2sdhnaUGriZTDUq1U6v19/tSLFpJFW9vTVlgz+4C0w7uAAGFJuA
+3dyKwQKBgC5FGbb5WOjhBUiW4GNOGGYxJvPkKowfnhOKYJVqjekzHofLgfAxqBto
+wKruBv/MpHt9c/bI8SM4wpniJiF7PIIbmk/XYUnjfdL202rkqfarl0c6Hdx81keA
+bSMyXbOp2Nd0knJfTvCq6mKeHPBf0wIR0Vze5MO/xHxVUjtYQW5T
+-----END RSA PRIVATE KEY-----
diff --git a/testdata/import/prober.autistici.org/cert.pem b/testdata/import/prober.autistici.org/cert.pem
new file mode 100644
index 00000000..ba2663f5
--- /dev/null
+++ b/testdata/import/prober.autistici.org/cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHzCCAgegAwIBAgIRAOMva8dvgIFO1PNvtPJ3L3YwDQYJKoZIhvcNAQELBQAw
+HzEdMBsGA1UEAxMUcHJvYmVyLmF1dGlzdGljaS5vcmcwHhcNMjIwODI1MDkwMzAy
+WhcNMjMwODI1MDkwMzAyWjAfMR0wGwYDVQQDExRwcm9iZXIuYXV0aXN0aWNpLm9y
+ZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANKBXomGWM1JblZjOoks
+SZiASd5vo/T98nnJSb7J1cyKZzneEV7oqwDJO/4q/LFK82k+g9gjC5TIAD28hO2i
+v5lsR1SNnlX3QZcT77hhqzulGsGL/I/4nx85SSd+ig+rKcVHRT1t27eBxFhC7MJp
+fFNUprFL1m2rmRi07FxqWyxCcIawUodi16oh8xFtE2mDkaisPDXIl/7meUIwtgqb
+Qe6NgjzVvhVGnbwYr8/ZVNziFmhDzYhbKeop0Il1+KZF+fw04g9GhBnexFKr0/5U
+DnU0ta6szfOQS8EXUSFA33yogExjM5eeTPELowbJB+U2s40KzjWJxX4eBoauHq8x
+wDcCAwEAAaNWMFQwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMB
+MAwGA1UdEwEB/wQCMAAwHwYDVR0RBBgwFoIUcHJvYmVyLmF1dGlzdGljaS5vcmcw
+DQYJKoZIhvcNAQELBQADggEBAJH4Q+gduWRH7AWzEj3zWN7D5sa5Yw2u3nU8Fy8/
+coK9bBo9DZW3DTuCuMXA75ideSdZLVUeyzHM6dWZVMdrzG2pbeQY0jZXMLAG7LV/
+dbRIOmyW49kOfsMdY61K9bq0dSEYC4oJq607G9PrbrVOGadgEGi6C0xULLH5Z5mZ
+V4Zkas3OK227HgxLNnj6ds7N4PMTLW0kM8quPvwC7fS032NgQRvN9yJ2rUG1LmzG
+0c7dMBqagN68bqUZPhbKWoprYZ+Fwznwr/OXo/dQ2u6eBMgZ5JUWg+5u9LEyFwP/
+U0N6mtS73Nv9PRKpPBa6an7wNxWC5Tzf1AlZLiutFSRYEtQ=
+-----END CERTIFICATE-----
diff --git a/testdata/import/prober.autistici.org/fullchain.pem b/testdata/import/prober.autistici.org/fullchain.pem
new file mode 100644
index 00000000..ba2663f5
--- /dev/null
+++ b/testdata/import/prober.autistici.org/fullchain.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHzCCAgegAwIBAgIRAOMva8dvgIFO1PNvtPJ3L3YwDQYJKoZIhvcNAQELBQAw
+HzEdMBsGA1UEAxMUcHJvYmVyLmF1dGlzdGljaS5vcmcwHhcNMjIwODI1MDkwMzAy
+WhcNMjMwODI1MDkwMzAyWjAfMR0wGwYDVQQDExRwcm9iZXIuYXV0aXN0aWNpLm9y
+ZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANKBXomGWM1JblZjOoks
+SZiASd5vo/T98nnJSb7J1cyKZzneEV7oqwDJO/4q/LFK82k+g9gjC5TIAD28hO2i
+v5lsR1SNnlX3QZcT77hhqzulGsGL/I/4nx85SSd+ig+rKcVHRT1t27eBxFhC7MJp
+fFNUprFL1m2rmRi07FxqWyxCcIawUodi16oh8xFtE2mDkaisPDXIl/7meUIwtgqb
+Qe6NgjzVvhVGnbwYr8/ZVNziFmhDzYhbKeop0Il1+KZF+fw04g9GhBnexFKr0/5U
+DnU0ta6szfOQS8EXUSFA33yogExjM5eeTPELowbJB+U2s40KzjWJxX4eBoauHq8x
+wDcCAwEAAaNWMFQwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMB
+MAwGA1UdEwEB/wQCMAAwHwYDVR0RBBgwFoIUcHJvYmVyLmF1dGlzdGljaS5vcmcw
+DQYJKoZIhvcNAQELBQADggEBAJH4Q+gduWRH7AWzEj3zWN7D5sa5Yw2u3nU8Fy8/
+coK9bBo9DZW3DTuCuMXA75ideSdZLVUeyzHM6dWZVMdrzG2pbeQY0jZXMLAG7LV/
+dbRIOmyW49kOfsMdY61K9bq0dSEYC4oJq607G9PrbrVOGadgEGi6C0xULLH5Z5mZ
+V4Zkas3OK227HgxLNnj6ds7N4PMTLW0kM8quPvwC7fS032NgQRvN9yJ2rUG1LmzG
+0c7dMBqagN68bqUZPhbKWoprYZ+Fwznwr/OXo/dQ2u6eBMgZ5JUWg+5u9LEyFwP/
+U0N6mtS73Nv9PRKpPBa6an7wNxWC5Tzf1AlZLiutFSRYEtQ=
+-----END CERTIFICATE-----
diff --git a/testdata/import/prober.autistici.org/privkey.pem b/testdata/import/prober.autistici.org/privkey.pem
new file mode 100644
index 00000000..f2e40c0c
--- /dev/null
+++ b/testdata/import/prober.autistici.org/privkey.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA0oFeiYZYzUluVmM6iSxJmIBJ3m+j9P3yeclJvsnVzIpnOd4R
+XuirAMk7/ir8sUrzaT6D2CMLlMgAPbyE7aK/mWxHVI2eVfdBlxPvuGGrO6UawYv8
+j/ifHzlJJ36KD6spxUdFPW3bt4HEWELswml8U1SmsUvWbauZGLTsXGpbLEJwhrBS
+h2LXqiHzEW0TaYORqKw8NciX/uZ5QjC2CptB7o2CPNW+FUadvBivz9lU3OIWaEPN
+iFsp6inQiXX4pkX5/DTiD0aEGd7EUqvT/lQOdTS1rqzN85BLwRdRIUDffKiATGMz
+l55M8QujBskH5TazjQrONYnFfh4Ghq4erzHANwIDAQABAoIBAFFrACmo8MfV0vtO
+veE4Y+68D9Yc0Al9/z7jf8ZvQmYuUear4YQX8awIKVARSFM6bhMSvuPmCiCtiopW
+VAOlov2wR5aIf+k4DQJmD539CMG1Bjg98J2ux7zH52eDJdsRlMJc4obz6rt7UXnh
+yLkZG6EPaLnCX1ljuX842wKTkFf5VtoXDwkmSmwPb6qgjXVKFp9erDtskYpaq8yG
+zac6pOwPrnaFyFq/ptp3uv416oTUyXB4Hrl4ikF19v13H2Gr/qTmyjoABeaHiC6V
+N2uGreJvceycAhD1hvUzEWsGUAA+huy/TzZ6tS4eqRzQjL7Z/tqHjNJPafvSUPkZ
+KtN12+ECgYEA8e/hJDxBdAq73EqFTC7P/A2qma4JKiqR1/EGpFrqiWmewKGNrj6h
+5ioKjSZr1+GX176l26N6y8ysgFdnPSarNO71lYW/UhR+wfV45u8/pbEJnxot1FbP
+4IsNm32Y/+l+bqKnzrpaABIEWcP9ALseEryOG1vS1Puv0M1PTySvLEMCgYEA3r3G
+FHouKL1Qj02CGLFaoS9VYh1+a2BEFfQFfLg2EmrKDLlVukVVPRqSvwWGclRLB6DV
+vgHjrtmULH1guyrQ8Jdkje7c9eyEGOzMarqBlwKmaywWwy/X+LJSMmgSaW0f5a1i
+uLY5J2oJ1sd8jM5TTwtSfVhb4VPjraFaPxzx1v0CgYEA2vx9lQbVV+u/Tw5EyUg2
+Cbd1C9Vzhi8yA3uuv2VrlSV9BG82htk6QdMb6dc1FhqkDiwKhFvc8ks4eJKkZ71w
+imEg+U4udmUGtxkxbhZwTdGJG/HeOay6TEyQ7IUqz7jRL+MacQxMGnQ0a+vyPY8h
+EWSKXEko9I87/ItZlhFmXXECgYEAqNWYKzVhf6fgRH8vuZ/VXpAOXEyW4rUZZrqM
+3jiTY+pmNLbW8FZVgfd+fCSoAEk8hg+3dEriVkq+MernQ3f04CkhIySHgDFC/9Ge
+MNgmGlz74AiPSGwffHdOLr8MFbWNk/6U8gXN1L5frtmO4vwR0bx3TmOHYYjA7396
+dJRwC9ECgYAwbURpbknVOCzQlOdkgqN7Jxua8ZXznciXlNV0cW2RLmwl4gKL48b7
+pMB2q3VHZV0ojxxSXaE8p2KYlphnSXezuvZKoVSQYIpDwvCdYlxURigrkjccI0Wg
+Ordaovbxr7GV9Gw01z08XX8jodXBbA6QqsvRhX6/Hnf/JptFqwuawQ==
+-----END RSA PRIVATE KEY-----
diff --git a/testdata/import/smtp.autistici.org/cert.pem b/testdata/import/smtp.autistici.org/cert.pem
new file mode 100644
index 00000000..cc997b7e
--- /dev/null
+++ b/testdata/import/smtp.autistici.org/cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDGTCCAgGgAwIBAgIRAJcW8Nuy/L1iNU4llCLQTC4wDQYJKoZIhvcNAQELBQAw
+HTEbMBkGA1UEAxMSc210cC5hdXRpc3RpY2kub3JnMB4XDTIyMDgyNTA5MDMwMVoX
+DTIzMDgyNTA5MDMwMVowHTEbMBkGA1UEAxMSc210cC5hdXRpc3RpY2kub3JnMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArIwRYxzqn/g81nwcgjPnkIrY
+UyTAKP+gMlRjoZmN0o0l5g3I1D0PGQy+9Vct3OzljBeQz5L9FaMXChIuZRvWzBQs
+gGv8uyRZinhbr3QfAxRPlgoZhVNcmp633Id2IPV+9qCLuIJXBph7p28a0+H6WDPY
+Zhn82bpA7U1aM3TJHEOL2s5M7bo3nKR9WvQITIMuwS40fI43Z34+P8Y8UbAmJctD
+K81SstClH80++2i8+w/0193v4TXZl2KKXIewW1G3U2Aj+52sX+Li/f7uXjYdOMWJ
+9UpIt1x2pWiJ+jLnzT2Vn8FYQPHEcDZ6RvTECDhGr5HF6wGorfztsqzDyGkHTQID
+AQABo1QwUjAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYD
+VR0TAQH/BAIwADAdBgNVHREEFjAUghJzbXRwLmF1dGlzdGljaS5vcmcwDQYJKoZI
+hvcNAQELBQADggEBAB2NsDOaMMIbzdRqVVv5YXwsIbjUPZuPdmG5q9+YlO82/wx0
+buNRkimTnDcTR1SnN8fQXttydG9jLr8Me/byXXA/OIWYZM8aoTRL8TFNXLt5VrYV
+AJaZOlq9KZWAPNVntr+NyKMVuqr0cvy2V70sqOWywqgDRBy3MlWcRgsSCv/fu0xM
+ZY/ow2WezC0O0CcVgtHHB0+XJMu+vchZ/YxZzRCJ9XHr6+9k4NRmd/iGhUuB7ZT6
+xb2zNaqeKCeLytO/zHEF3rC8S0m5aQiFUfIZXqqcJh2d/aQLnvuWx3iCN79QYspQ
+Ab8Jn7ZyUV+Sb/L1WzMXpAunnSRu/jHeCQyCG24=
+-----END CERTIFICATE-----
diff --git a/testdata/import/smtp.autistici.org/fullchain.pem b/testdata/import/smtp.autistici.org/fullchain.pem
new file mode 100644
index 00000000..cc997b7e
--- /dev/null
+++ b/testdata/import/smtp.autistici.org/fullchain.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDGTCCAgGgAwIBAgIRAJcW8Nuy/L1iNU4llCLQTC4wDQYJKoZIhvcNAQELBQAw
+HTEbMBkGA1UEAxMSc210cC5hdXRpc3RpY2kub3JnMB4XDTIyMDgyNTA5MDMwMVoX
+DTIzMDgyNTA5MDMwMVowHTEbMBkGA1UEAxMSc210cC5hdXRpc3RpY2kub3JnMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArIwRYxzqn/g81nwcgjPnkIrY
+UyTAKP+gMlRjoZmN0o0l5g3I1D0PGQy+9Vct3OzljBeQz5L9FaMXChIuZRvWzBQs
+gGv8uyRZinhbr3QfAxRPlgoZhVNcmp633Id2IPV+9qCLuIJXBph7p28a0+H6WDPY
+Zhn82bpA7U1aM3TJHEOL2s5M7bo3nKR9WvQITIMuwS40fI43Z34+P8Y8UbAmJctD
+K81SstClH80++2i8+w/0193v4TXZl2KKXIewW1G3U2Aj+52sX+Li/f7uXjYdOMWJ
+9UpIt1x2pWiJ+jLnzT2Vn8FYQPHEcDZ6RvTECDhGr5HF6wGorfztsqzDyGkHTQID
+AQABo1QwUjAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYD
+VR0TAQH/BAIwADAdBgNVHREEFjAUghJzbXRwLmF1dGlzdGljaS5vcmcwDQYJKoZI
+hvcNAQELBQADggEBAB2NsDOaMMIbzdRqVVv5YXwsIbjUPZuPdmG5q9+YlO82/wx0
+buNRkimTnDcTR1SnN8fQXttydG9jLr8Me/byXXA/OIWYZM8aoTRL8TFNXLt5VrYV
+AJaZOlq9KZWAPNVntr+NyKMVuqr0cvy2V70sqOWywqgDRBy3MlWcRgsSCv/fu0xM
+ZY/ow2WezC0O0CcVgtHHB0+XJMu+vchZ/YxZzRCJ9XHr6+9k4NRmd/iGhUuB7ZT6
+xb2zNaqeKCeLytO/zHEF3rC8S0m5aQiFUfIZXqqcJh2d/aQLnvuWx3iCN79QYspQ
+Ab8Jn7ZyUV+Sb/L1WzMXpAunnSRu/jHeCQyCG24=
+-----END CERTIFICATE-----
diff --git a/testdata/import/smtp.autistici.org/privkey.pem b/testdata/import/smtp.autistici.org/privkey.pem
new file mode 100644
index 00000000..03469c2d
--- /dev/null
+++ b/testdata/import/smtp.autistici.org/privkey.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEArIwRYxzqn/g81nwcgjPnkIrYUyTAKP+gMlRjoZmN0o0l5g3I
+1D0PGQy+9Vct3OzljBeQz5L9FaMXChIuZRvWzBQsgGv8uyRZinhbr3QfAxRPlgoZ
+hVNcmp633Id2IPV+9qCLuIJXBph7p28a0+H6WDPYZhn82bpA7U1aM3TJHEOL2s5M
+7bo3nKR9WvQITIMuwS40fI43Z34+P8Y8UbAmJctDK81SstClH80++2i8+w/0193v
+4TXZl2KKXIewW1G3U2Aj+52sX+Li/f7uXjYdOMWJ9UpIt1x2pWiJ+jLnzT2Vn8FY
+QPHEcDZ6RvTECDhGr5HF6wGorfztsqzDyGkHTQIDAQABAoIBAAabEitWNxFKwqF/
+fOPrMslQ13lPZd/r8Wkb2/ia1VgjOTk2e+LMN6DA4SqpXMMheqRDXrLjsLa8WKx1
+B7utNir39sAv4iwT9y2a7/+mlW1bwLRAzLtS9q+gte9GbFpUq7iras30h3pkKg+9
+7CwCccAgs/8srYZ0X/n1xSdFKFHhn/neFcoGCI35OgtCDa5lJDyNrl0VqBXDmXuq
+jlzZbgk6NwvqC0ynYKg2NDtghDNRgj9Qy92oGLevdosmeueiEbEOja4chn5Q+te/
+iGiRNB5CKPn1kLocWP5iIt+u3LcoAHHkE0TuTv+KoFP6Pb1nrNhFgOpZpip8BUhK
+FyclzAECgYEA5I/sqhF91hWv/PERfFE0rlqPWZ9fZ+iNiJ0Yf6h/bQC7LpO2gbWH
+tcc8LpsBbmnTu1Yng12nE5HhWKcRDJnv9hY7jJIL6PLOiFJbaIkhcp9rKizk0swq
+bJmObareT/2ysw99nZ1EIfiZ9iiBJxVH6LjVSxwjQaXE2Ej/I8AL5QECgYEAwUK2
+aMrojZrFkJZce2gXcQZpgBQtKSPBV1SEg0b0OsLhusrKRSIm0wXVExJ564Ksm1+7
+M25w1vsuUf2ZHv/UGbA19r6+5vMaJ2AywrxTjPZg4VolUGdck4MDTjjpAoj2+nM7
+9U66ZV1tY+rD1vCMwfYWqFRfN5LCqvYJgu3XJk0CgYAr6VbPqcKiUdJY5zuzxao4
+YR+vWUYsIWqnt37Qb9/jYkOUBD8JFoY3L0rprVAOFXjfQgMfYuvJf7pqnNZStHR8
+s/LG4/wzygpR+HilUctCSUx+cteXhDAWYz4Q5cYGWjht3GkG0gMX/7ocp9WAW79P
+/8rCvmorn5x87TFPfgMLAQKBgQCVo0oJqdKWkhgW89V32AkOgqB9gyHUkNLLA632
+16GTPgH517r1/JODISjyM4QjTnLHe0A/emv0iVg8M9ezoPJhUYb3HopB2EmP43Rq
+Q9eNddddpabuspmDvhV5I6eHRvW0xjTOo76/ba+dhdFNQ8Q5CopQm8N+DCyeoxKK
+Rg4ooQKBgGbEPrB/burG1VvN6Q86xaXB9rRsyvyhlYFNYhptF1wxFxEbgmclXEl9
+Kr1QFyakQrGmRu/hkAkMVSA0O61LoAQ+imCoivo+TttHSHtpMuzakA9LYgPIc5A4
+BbwdEby5oDiv6p4jjWNvodMC9OhXgb4Lcl58ZDqutagJuREECODN
+-----END RSA PRIVATE KEY-----
diff --git a/testdata/import/trace.autistici.org/cert.pem b/testdata/import/trace.autistici.org/cert.pem
new file mode 100644
index 00000000..60cd7df6
--- /dev/null
+++ b/testdata/import/trace.autistici.org/cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDGzCCAgOgAwIBAgIQMzgMGQy3nT5UQJcFq2TJwTANBgkqhkiG9w0BAQsFADAe
+MRwwGgYDVQQDExN0cmFjZS5hdXRpc3RpY2kub3JnMB4XDTIyMDgyNTA5MDMwM1oX
+DTIzMDgyNTA5MDMwM1owHjEcMBoGA1UEAxMTdHJhY2UuYXV0aXN0aWNpLm9yZzCC
+ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMzYVRftoB4/0TuSuTQDOCGR
+SAE4RpT5r2hYNfiF+i6fTqMgI+ved1OB/qXO1v6/J1+xKyMILgEb0uPpMgLbnjWw
+JRa19FQ2Gm0RQSONQmTD+YkwsIG6OAFcq/mSqorph0jKBDx4bX99rGPhf8jYL7mp
+DBF01Oyn1FNmrr7BwWxeiY1a11rne172jTZ5Jz/sWpqCWeJtmO8npM+dO8bDrKyh
+KuruQUeYcpmDw+AafyC6tybKM2is5U1vli+0Mwx+JAL8a2059PlnAWO2mA+7tuXA
+FhWyK0MvvxCxUSAseNVCb8WOIKWWOAhCoi1zzF1n3ONe6nB2u0NGNIilWwtY9l8C
+AwEAAaNVMFMwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwG
+A1UdEwEB/wQCMAAwHgYDVR0RBBcwFYITdHJhY2UuYXV0aXN0aWNpLm9yZzANBgkq
+hkiG9w0BAQsFAAOCAQEAI/0WTyCWR7jHtqoIpEW2134MgM3g5dGDN4Kx2rgVxGAc
+aLTLTfQts9qRL+5GmT64T4Eed2UOxoMLc/+mq858nzjcBsYVvVHHmtydiPyYBxrR
+gjGvJYQnVtQm1unWlJegOzArWbApcREFikDUF8ioE9jClpNJNvqEnWirkeP/ctFe
+d5HIwCxQtWldFSL81Mdz+wkkBXYKQXt80GKgmX1AdKZh/QnOx5dqf18cAdzbaIKN
+C1A2CI6m+vE3zp0Gn9LE9ltanZcwx2htHiaAJOzlyYuewHvD2EADEbfZlQuLkAhU
+zZRvvTzmkv7SmU4mf1h66IbhLnIqLeXNpS1yim9ceA==
+-----END CERTIFICATE-----
diff --git a/testdata/import/trace.autistici.org/fullchain.pem b/testdata/import/trace.autistici.org/fullchain.pem
new file mode 100644
index 00000000..60cd7df6
--- /dev/null
+++ b/testdata/import/trace.autistici.org/fullchain.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDGzCCAgOgAwIBAgIQMzgMGQy3nT5UQJcFq2TJwTANBgkqhkiG9w0BAQsFADAe
+MRwwGgYDVQQDExN0cmFjZS5hdXRpc3RpY2kub3JnMB4XDTIyMDgyNTA5MDMwM1oX
+DTIzMDgyNTA5MDMwM1owHjEcMBoGA1UEAxMTdHJhY2UuYXV0aXN0aWNpLm9yZzCC
+ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMzYVRftoB4/0TuSuTQDOCGR
+SAE4RpT5r2hYNfiF+i6fTqMgI+ved1OB/qXO1v6/J1+xKyMILgEb0uPpMgLbnjWw
+JRa19FQ2Gm0RQSONQmTD+YkwsIG6OAFcq/mSqorph0jKBDx4bX99rGPhf8jYL7mp
+DBF01Oyn1FNmrr7BwWxeiY1a11rne172jTZ5Jz/sWpqCWeJtmO8npM+dO8bDrKyh
+KuruQUeYcpmDw+AafyC6tybKM2is5U1vli+0Mwx+JAL8a2059PlnAWO2mA+7tuXA
+FhWyK0MvvxCxUSAseNVCb8WOIKWWOAhCoi1zzF1n3ONe6nB2u0NGNIilWwtY9l8C
+AwEAAaNVMFMwDgYDVR0PAQH/BAQDAgWgMBMGA1UdJQQMMAoGCCsGAQUFBwMBMAwG
+A1UdEwEB/wQCMAAwHgYDVR0RBBcwFYITdHJhY2UuYXV0aXN0aWNpLm9yZzANBgkq
+hkiG9w0BAQsFAAOCAQEAI/0WTyCWR7jHtqoIpEW2134MgM3g5dGDN4Kx2rgVxGAc
+aLTLTfQts9qRL+5GmT64T4Eed2UOxoMLc/+mq858nzjcBsYVvVHHmtydiPyYBxrR
+gjGvJYQnVtQm1unWlJegOzArWbApcREFikDUF8ioE9jClpNJNvqEnWirkeP/ctFe
+d5HIwCxQtWldFSL81Mdz+wkkBXYKQXt80GKgmX1AdKZh/QnOx5dqf18cAdzbaIKN
+C1A2CI6m+vE3zp0Gn9LE9ltanZcwx2htHiaAJOzlyYuewHvD2EADEbfZlQuLkAhU
+zZRvvTzmkv7SmU4mf1h66IbhLnIqLeXNpS1yim9ceA==
+-----END CERTIFICATE-----
diff --git a/testdata/import/trace.autistici.org/privkey.pem b/testdata/import/trace.autistici.org/privkey.pem
new file mode 100644
index 00000000..674e0fae
--- /dev/null
+++ b/testdata/import/trace.autistici.org/privkey.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAzNhVF+2gHj/RO5K5NAM4IZFIAThGlPmvaFg1+IX6Lp9OoyAj
+6953U4H+pc7W/r8nX7ErIwguARvS4+kyAtueNbAlFrX0VDYabRFBI41CZMP5iTCw
+gbo4AVyr+ZKqiumHSMoEPHhtf32sY+F/yNgvuakMEXTU7KfUU2auvsHBbF6JjVrX
+Wud7XvaNNnknP+xamoJZ4m2Y7yekz507xsOsrKEq6u5BR5hymYPD4Bp/ILq3Jsoz
+aKzlTW+WL7QzDH4kAvxrbTn0+WcBY7aYD7u25cAWFbIrQy+/ELFRICx41UJvxY4g
+pZY4CEKiLXPMXWfc417qcHa7Q0Y0iKVbC1j2XwIDAQABAoIBAA5L8vRuk0RJM/My
+dFaa+uns19et888l9gYUUf/8ac+jMrvT3G3z4uQjKICuBPdWpArbtKUHRx6wsHFT
+rzff2BTrLEt/e4P9Gq8OCzvN+hpKSzo1+bu0IYCG1UHf6KM1VUnRP6ZwyUOio8t8
+y4xa+km88KivPMUfmYQQoJWTtEo51uuI1ftnctYB+jUy1IefUBzzlEzuxc3N7Sa2
+lDsH5Q5MTBh0glecizgf1cVqT9SVxsaQitYe6P2kybgXWaSnYINPCJiYN/U6Cq1Z
+EUfgi4TDVur6dTwEQU/w2rSp1XKLVqfTeQJU8Rpw10sGfwRUvcLXgqtradBLigzl
+2K9J2hECgYEA6OoxAPPgI+ap41VRFQfLbBf4PtjNrzKseZM7D2Di7sje1dpY6MOx
+cVwC/A2h6LDRl3uB0XzMyaf4DfSZpbxO9PA3tuzXEPp0i/oUHgeS4lIM1gSPSPLT
+oOV/M3PlAHz/MoxWAyaNvM6iGOJYP8+NoCdK8/T05jAc31ijcdk5BnsCgYEA4SXr
+Z1j0289XSRmgay8SDxGcuqGTORE7iQDZHBO1h3o9Gu1mvD6QUa+CBIizLEVI3Ztt
+BPn+YXbNMxwJjWzIeOnTJnlmjbk12aFkw6Ix0bSPyLlpncgpOVvEYaVPE1s9r+TP
+vj+t9LYarKxH361SATUfaGHecnmCFFn9WEknXG0CgYAxAHRm2ughJXpAlcaFQiqO
+B0z3C+3aGjMnMM6QIbYcvq4DF/j0n5dJXi5SkRurNOgjkpxxUmxTUL9jUD6zG7cQ
+2Skflxx4OfkjKe3pk3ZXnyOMM1Fh65SSmTX1wUBApguauuOtrgLL4j1ANv6SDjyh
+oSKnyLVtaFnqs19PqH/S2QKBgAlfMrVSlp6vuspoLq1HgFDwQr3iC+bMONYzGtZ4
+W44QIIsGZshfMlK0I41cw1iMacQPvDIA1f3onBIMkLk6Vf3yfb+UPhiYJQ2yEiey
+kLBR+8o3LZZLxquEjmAiKXB808yGkb8xl2QgUGvvn+5HoKo8M9p8eG37cNa4CDus
+j97hAoGARZppv8VC+pReAjD7ZVk0MQmKGrbxvlBk6M7Ogn6H/yiJdRHCV2jnatxr
+fd+0505OwJouRsDASpcZN7LGnw6fweXFLgL0FWqvrTY0KuQ6g4DYePNm/Py0LE4g
+wVTfsjGPfHM3PgnIAp+oWelAT9fGv1XAVYfXzIHirecJv09vsj0=
+-----END RSA PRIVATE KEY-----
diff --git a/testdata/import/webmail.autistici.org/cert.pem b/testdata/import/webmail.autistici.org/cert.pem
new file mode 100644
index 00000000..05d38d1c
--- /dev/null
+++ b/testdata/import/webmail.autistici.org/cert.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDVjCCAj6gAwIBAgIRAKmLc668KGzg4WTAqLN1fK0wDQYJKoZIhvcNAQELBQAw
+IDEeMBwGA1UEAxMVd2VibWFpbC5hdXRpc3RpY2kub3JnMB4XDTIyMDgyNTA5MDMw
+MVoXDTIzMDgyNTA5MDMwMVowIDEeMBwGA1UEAxMVd2VibWFpbC5hdXRpc3RpY2ku
+b3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArh6VjM4RV7bj8GnU
+OqDJGbOL5HE5YAmhqQB71F6HhsILMjWxjQX1xVia8C1/yrS/v+CVfrQC2YqDa5uH
+hj+/lRaO432jHGDR1USP77Lde4zCKR4V52r9nPnJxjWwSw4I2tJE0LnoM7OTV8Zn
+3nbzs/3bXQJw7kPPtVMoz9hHRhdQJIes+Jru4G8NDrnvhVltgWV006O1yTniYi1D
+zabkzOiF+hdLQBsS/4koKlZOMysnaIe+WFt55xxXPv+LrlAUDzWrOonZZmnmN6we
+H8iaE0GxK8lihCao5z9Dxc3bBpB0rDISare6Rq9K1bSpFIvQksejG+Pl2ila3E4m
+lGKmhwIDAQABo4GKMIGHMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEF
+BQcDATAMBgNVHRMBAf8EAjAAMFIGA1UdEQRLMEmCFXdlYm1haWwuYXV0aXN0aWNp
+Lm9yZ4IXMS53ZWJtYWlsLmF1dGlzdGljaS5vcmeCFzIud2VibWFpbC5hdXRpc3Rp
+Y2kub3JnMA0GCSqGSIb3DQEBCwUAA4IBAQBGzrU/3gHW9mlGfm1gEFGeJM8pCMXq
+HBVmnAxRILFgzOJfJE4svg3ksp8LHXZb25Uoo6mjyZGgDQmc+sDAHIsOXwa/Ig/h
+SwBjB+Z1GrZpRvivWXdcM0NC2Ir/n4g0qpr3UrERNDLlF/deWDZzBmIb/hCgguUK
+rMuUoJ3o5ahtLs0Y6w4YU7lWdp62osJ4rE98yDrGblbG4BQnazsJVxK2JcwVOR3Y
+ewlEEuqe0pm4w6KesAgNDfab1Kp/RqY1LLQU0r2zcGt2qEgo9uxD9BydPgxOrEC3
+qLnsnVT8Vu++W0ofF5rd1IeL9ciTwluU6p69B8CmgZGNua2l4i/NywYj
+-----END CERTIFICATE-----
diff --git a/testdata/import/webmail.autistici.org/fullchain.pem b/testdata/import/webmail.autistici.org/fullchain.pem
new file mode 100644
index 00000000..05d38d1c
--- /dev/null
+++ b/testdata/import/webmail.autistici.org/fullchain.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDVjCCAj6gAwIBAgIRAKmLc668KGzg4WTAqLN1fK0wDQYJKoZIhvcNAQELBQAw
+IDEeMBwGA1UEAxMVd2VibWFpbC5hdXRpc3RpY2kub3JnMB4XDTIyMDgyNTA5MDMw
+MVoXDTIzMDgyNTA5MDMwMVowIDEeMBwGA1UEAxMVd2VibWFpbC5hdXRpc3RpY2ku
+b3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArh6VjM4RV7bj8GnU
+OqDJGbOL5HE5YAmhqQB71F6HhsILMjWxjQX1xVia8C1/yrS/v+CVfrQC2YqDa5uH
+hj+/lRaO432jHGDR1USP77Lde4zCKR4V52r9nPnJxjWwSw4I2tJE0LnoM7OTV8Zn
+3nbzs/3bXQJw7kPPtVMoz9hHRhdQJIes+Jru4G8NDrnvhVltgWV006O1yTniYi1D
+zabkzOiF+hdLQBsS/4koKlZOMysnaIe+WFt55xxXPv+LrlAUDzWrOonZZmnmN6we
+H8iaE0GxK8lihCao5z9Dxc3bBpB0rDISare6Rq9K1bSpFIvQksejG+Pl2ila3E4m
+lGKmhwIDAQABo4GKMIGHMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEF
+BQcDATAMBgNVHRMBAf8EAjAAMFIGA1UdEQRLMEmCFXdlYm1haWwuYXV0aXN0aWNp
+Lm9yZ4IXMS53ZWJtYWlsLmF1dGlzdGljaS5vcmeCFzIud2VibWFpbC5hdXRpc3Rp
+Y2kub3JnMA0GCSqGSIb3DQEBCwUAA4IBAQBGzrU/3gHW9mlGfm1gEFGeJM8pCMXq
+HBVmnAxRILFgzOJfJE4svg3ksp8LHXZb25Uoo6mjyZGgDQmc+sDAHIsOXwa/Ig/h
+SwBjB+Z1GrZpRvivWXdcM0NC2Ir/n4g0qpr3UrERNDLlF/deWDZzBmIb/hCgguUK
+rMuUoJ3o5ahtLs0Y6w4YU7lWdp62osJ4rE98yDrGblbG4BQnazsJVxK2JcwVOR3Y
+ewlEEuqe0pm4w6KesAgNDfab1Kp/RqY1LLQU0r2zcGt2qEgo9uxD9BydPgxOrEC3
+qLnsnVT8Vu++W0ofF5rd1IeL9ciTwluU6p69B8CmgZGNua2l4i/NywYj
+-----END CERTIFICATE-----
diff --git a/testdata/import/webmail.autistici.org/privkey.pem b/testdata/import/webmail.autistici.org/privkey.pem
new file mode 100644
index 00000000..00bb9b4c
--- /dev/null
+++ b/testdata/import/webmail.autistici.org/privkey.pem
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEArh6VjM4RV7bj8GnUOqDJGbOL5HE5YAmhqQB71F6HhsILMjWx
+jQX1xVia8C1/yrS/v+CVfrQC2YqDa5uHhj+/lRaO432jHGDR1USP77Lde4zCKR4V
+52r9nPnJxjWwSw4I2tJE0LnoM7OTV8Zn3nbzs/3bXQJw7kPPtVMoz9hHRhdQJIes
++Jru4G8NDrnvhVltgWV006O1yTniYi1DzabkzOiF+hdLQBsS/4koKlZOMysnaIe+
+WFt55xxXPv+LrlAUDzWrOonZZmnmN6weH8iaE0GxK8lihCao5z9Dxc3bBpB0rDIS
+are6Rq9K1bSpFIvQksejG+Pl2ila3E4mlGKmhwIDAQABAoIBAQCVZzTXjvIem5XW
+clhivhgQb5l3uReMKneGdh3KyhnsLZBB0wS4hwauASthLwlaO+HUmLZt87QGSe4e
+ZWPSc9zF5odQ+Dr/XKxwHNaMzKNzIgJwGZqd6ZYYHdVeuC6/GBnM+WOG3h46Trn9
+NQdnQhY2uXqCwld4esM9SU/ZmDEAGBFqWh53gZYTnyT2o61N5wcMBtd48jst9APP
+5s+q2LBLGv3bjICkO8lqKAL6WgUj0IVzvt86+S7ubtY3sev2JU0VGYUHbsjQCNBy
+5RTtSP5lCZJ2wsU2iiafjYr6ovl8s9Pf9YTl2uQI/jESXDjM5d3epp3c8Xv6slWM
+Rmc86PoxAoGBAOUcFgVEDbbAjKWwP3MHnrFUoSHUipMAim4WuGPpoJlU2/8Osur0
+VHIOHBl+ANafKxMGXVTKdBor39qhK7Zbc+FymL9TKrnaPcJMAJ244sY+zF2pJ89O
+2rYa7/A2xpp6zM1jHhM158b+J86AicPr7aOwTVU2INJ9WUIGssaNNgX9AoGBAMKO
+PcWrwZLk3tv+WyXVXJrtq8n8/LpxIx0gK1+WVzjnuQNoOf4NPfnluEavMC9qBQI3
+2FP3rn8iuP37/3cSAoqk9Ze5uIyxOw+XQugDVkX6D7NYlFre6cHAdrhyxGdvH6er
+OL1x8WbjPLd3RwO+6HCV8MPAkA6i5nm78NEfPMPTAoGBAMfhcPS3Ip/F7O7AjRp2
+E2TySIGTRDJrzlmtSMEiGhKrjiM/V6jT8LhwxuMWovSbeKDodJR87D3I+UtsQQ6f
+SsHtkIWasTPjyu2wEPmJS49ZSkKMSUb7rKQjbCFOKIati+/EAYERDlz1bxUh3w8m
+zfR4437r8kaxRSXtYhsHJ5KhAoGAJSJ7PeibqQsQUpNJGH6SD2w/9uzX3EpN9ng2
+bydF/LGWCcVyFEC4zKMBOlSj8Njend7E5roGz3N0Nd5Mk5CXdZko203yoMAPUtl/
+RsOkSvUA3C2TG4kiuv2ea3RUcp2uQBWZXeeKuSW0aBxXLKrauOqVqvdtR63mKeQq
+ge/hbEsCgYAUbIk/RdVtok10ym+y8WPI9/f2Dq0sj23sK6LxrOIlEWeKjT20pv3r
+XXc7v9OjiTYNMEXdTr1wq1mTaneZhtCMq3CgzC2wrJzBr7Ah/7iv06TSS1QLcdDx
+dHRPnsw6eCV6+D0Q0kPpwkOns3CWD8Ge1+5avMrfThTjqnwoklRLYg==
+-----END RSA PRIVATE KEY-----
diff --git a/util.go b/util.go
index acb3195a..841e3f7b 100644
--- a/util.go
+++ b/util.go
@@ -1,17 +1,12 @@
 package acmeserver
 
 import (
-	"bytes"
 	"context"
-	"crypto"
-	"crypto/ecdsa"
-	"crypto/rsa"
-	"crypto/x509"
-	"encoding/pem"
-	"errors"
 	"path/filepath"
 	"sync"
 	"time"
+
+	"git.autistici.org/ai3/tools/acmeserver/common"
 )
 
 // Simple channel-based semaphore.
@@ -109,68 +104,30 @@ func runWithUpdates(ctx context.Context, fn func(context.Context, *configSnapsho
 	}
 }
 
-func encodeCerts(der [][]byte) ([]byte, error) {
-	var buf bytes.Buffer
-	for _, b := range der {
-		pb := &pem.Block{Type: "CERTIFICATE", Bytes: b}
-		if err := pem.Encode(&buf, pb); err != nil {
-			return nil, err
-		}
-	}
-	return buf.Bytes(), nil
-}
-
-func encodePrivateKey(key crypto.Signer) ([]byte, error) {
-	var pb *pem.Block
-	switch priv := key.(type) {
-	case *rsa.PrivateKey:
-		pb = &pem.Block{
-			Type:  "RSA PRIVATE KEY",
-			Bytes: x509.MarshalPKCS1PrivateKey(priv),
-		}
-	case *ecdsa.PrivateKey:
-		b, err := x509.MarshalECPrivateKey(priv)
-		if err != nil {
-			return nil, err
-		}
-		pb = &pem.Block{
-			Type:  "EC PRIVATE KEY",
-			Bytes: b,
-		}
-	default:
-		return nil, errors.New("unknown private key type")
-	}
-	var buf bytes.Buffer
-	if err := pem.Encode(&buf, pb); err != nil {
-		return nil, err
-	}
-	return buf.Bytes(), nil
-}
-
 func (c *Certificate) dump(prefix string) (*Batch, error) {
 	b := new(Batch)
 
-	data, err := encodeCerts(c.DER)
+	data, err := common.EncodeCerts(c.DER)
 	if err != nil {
 		return nil, err
 	}
 	b.Put(filepath.Join(prefix, "fullchain.pem"), data)
 
-	data, err = encodeCerts(c.DER[:1])
+	data, err = common.EncodeCerts(c.DER[:1])
 	if err != nil {
 		return nil, err
 	}
 	b.Put(filepath.Join(prefix, "cert.pem"), data)
 
 	if len(c.DER) > 1 {
-		data, err = encodeCerts(c.DER[1:])
+		data, err = common.EncodeCerts(c.DER[1:])
 		if err != nil {
 			return nil, err
 		}
 		b.Put(filepath.Join(prefix, "chain.pem"), data)
 	}
 
-	data, err = encodePrivateKey(c.Key)
+	data, err = common.EncodePrivateKey(c.Key)
 	if err != nil {
 		return nil, err
 	}
-- 
GitLab