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
+}