m/n/core/localstorage: add helper functions for PKIDirectory

Change-Id: I2798b9d6fcaedcf7a5e8e01e322797ebb8a1389d
Reviewed-on: https://review.monogon.dev/c/monogon/+/1376
Tested-by: Jenkins CI
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
diff --git a/metropolis/node/core/identity/identity.go b/metropolis/node/core/identity/identity.go
index 8749b6d..a08bd6f 100644
--- a/metropolis/node/core/identity/identity.go
+++ b/metropolis/node/core/identity/identity.go
@@ -112,44 +112,19 @@
 
 // Save stores the given node credentials in local storage.
 func (n *NodeCredentials) Save(d *localstorage.PKIDirectory) error {
-	if err := d.CACertificate.Write(n.ca.Raw, 0400); err != nil {
-		return fmt.Errorf("when writing CA certificate: %w", err)
-	}
-	if err := d.Certificate.Write(n.node.Raw, 0400); err != nil {
-		return fmt.Errorf("when writing node certificate: %w", err)
-	}
-	if err := d.Key.Write(n.private, 0400); err != nil {
-		return fmt.Errorf("when writing node private key: %w", err)
-	}
-	return nil
+	return d.WriteAll(n.node.Raw, n.private, n.ca.Raw)
 }
 
 // Read initializes NodeCredentials' contents with the data stored in the
 // PKIDirectory d. It may return an I/O error, or a parsing error.
 func (n *NodeCredentials) Read(d *localstorage.PKIDirectory) error {
-	if car, err := d.CACertificate.Read(); err != nil {
-		return fmt.Errorf("while reading CA certificate: %w", err)
-	} else {
-		cert, err := x509.ParseCertificate(car)
-		if err != nil {
-			return fmt.Errorf("while parsing CA certificate: %w", err)
-		}
-		n.ca = cert
+	ca, cert, key, err := d.ReadAll()
+	if err != nil {
+		return err
 	}
-	if nr, err := d.Certificate.Read(); err != nil {
-		return fmt.Errorf("while reading node certificate: %w", err)
-	} else {
-		cert, err := x509.ParseCertificate(nr)
-		if err != nil {
-			return fmt.Errorf("while parsing node certificate: %w", err)
-		}
-		n.node = cert
-	}
-	if npr, err := d.Key.Read(); err != nil {
-		return fmt.Errorf("while reading node private key: %w", err)
-	} else {
-		n.private = npr
-	}
+	n.ca = ca
+	n.node = cert
+	n.private = key
 	return nil
 }
 
diff --git a/metropolis/node/core/localstorage/directory_pki.go b/metropolis/node/core/localstorage/directory_pki.go
index 028b5ef..37fcdb6 100644
--- a/metropolis/node/core/localstorage/directory_pki.go
+++ b/metropolis/node/core/localstorage/directory_pki.go
@@ -20,6 +20,7 @@
 	"crypto/ed25519"
 	"crypto/x509"
 	"encoding/pem"
+	"errors"
 	"fmt"
 	"time"
 
@@ -63,12 +64,9 @@
 	return true, nil
 }
 
-// WriteAll (over)writes the PKI data in this directory with the given private
-// key, certificate and CA certificate.
-//
-// For ease of use, the accepted certificates are expected to already be in
-// DER-encoded form (eg. from the Raw field in a x509.Certificate).
-func (p *PKIDirectory) WriteAll(cert []byte, key ed25519.PrivateKey, ca []byte) error {
+// WritePrivateKey serializes the given private key (PKCS8 + PEM) and writes it
+// to the PKIDirectory, overwriting whatever might already be present there.
+func (p *PKIDirectory) WritePrivateKey(key ed25519.PrivateKey) error {
 	if err := p.MkdirAll(0700); err != nil {
 		return fmt.Errorf("failed to make PKI directory: %w", err)
 	}
@@ -79,6 +77,46 @@
 	if err := p.Key.Write(pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes}), 0600); err != nil {
 		return fmt.Errorf("failed to write key: %w", err)
 	}
+	return nil
+}
+
+// ReadPrivateKey loads an ED25519 private key from the PKIDirectory and
+// deserializes it (PEM + PKCS).
+func (p *PKIDirectory) ReadPrivateKey() (ed25519.PrivateKey, error) {
+	raw, err := p.Key.Read()
+	if err != nil {
+		return nil, fmt.Errorf("failed to read key: %w", err)
+	}
+	block, _ := pem.Decode(raw)
+	if block == nil {
+		return nil, errors.New("not PEM")
+	}
+	keyType := "PRIVATE KEY"
+	if block.Type != keyType {
+		return nil, fmt.Errorf("contains a PEM block that's not a %v", keyType)
+	}
+
+	key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse key: %w", err)
+	}
+	switch k := key.(type) {
+	case ed25519.PrivateKey:
+		return k, nil
+	default:
+		return nil, fmt.Errorf("PCKS8 contains invalid key type")
+	}
+}
+
+// WriteCertificates serializes (PEM) and saves the given certificates into the
+// PKIDirectory, overwriting whatever might already be present there.
+//
+// For ease of use, the accepted certificates are expected to already be in
+// DER-encoded form (eg. from the Raw field in a x509.Certificate).
+func (p *PKIDirectory) WriteCertificates(ca, cert []byte) error {
+	if err := p.MkdirAll(0700); err != nil {
+		return fmt.Errorf("failed to make PKI directory: %w", err)
+	}
 	if err := p.Certificate.Write(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: cert}), 0600); err != nil {
 		return fmt.Errorf("failed to write certificate: %w", err)
 	}
@@ -87,3 +125,60 @@
 	}
 	return nil
 }
+
+// readPEMX509 reads a file and parses it as a PEM-encoded X509 certificate.
+func readPEMX509(p *declarative.File) (*x509.Certificate, error) {
+	bytes, err := p.Read()
+	if err != nil {
+		return nil, fmt.Errorf("couldn't read: %w", err)
+	}
+	block, _ := pem.Decode(bytes)
+	if block == nil || block.Type != "CERTIFICATE" {
+		return nil, errors.New("invalid PEM armoring")
+	}
+	cert, err := x509.ParseCertificate(block.Bytes)
+	if err != nil {
+		return nil, fmt.Errorf("invalid X509: %w", err)
+	}
+	return cert, nil
+}
+
+// ReadCertificates reads and parses (PEM + X509) the certificates from a
+// PKIDirectory.
+func (p *PKIDirectory) ReadCertificates() (ca, cert *x509.Certificate, err error) {
+	ca, err = readPEMX509(&p.CACertificate)
+	if err != nil {
+		return nil, nil, fmt.Errorf("when loading CA certificate: %w", err)
+	}
+	cert, err = readPEMX509(&p.Certificate)
+	if err != nil {
+		return nil, nil, fmt.Errorf("when loading certificate: %w", err)
+	}
+	return ca, cert, nil
+}
+
+// WriteAll (over)writes the PKI data in this directory with the given private
+// key, certificate and CA certificate.
+//
+// For ease of use, the accepted certificates are expected to already be in
+// DER-encoded form (eg. from the Raw field in a x509.Certificate).
+func (p *PKIDirectory) WriteAll(cert []byte, key ed25519.PrivateKey, ca []byte) error {
+	if err := p.WritePrivateKey(key); err != nil {
+		return err
+	}
+	if err := p.WriteCertificates(ca, cert); err != nil {
+		return err
+	}
+	return nil
+}
+
+// ReadAll reads and parses (PEM + PKCS8/X509) the stored certificates and key of
+// this PKIDirectory.
+func (p *PKIDirectory) ReadAll() (ca, cert *x509.Certificate, key ed25519.PrivateKey, err error) {
+	ca, cert, err = p.ReadCertificates()
+	if err != nil {
+		return
+	}
+	key, err = p.ReadPrivateKey()
+	return
+}