blob: af5b6547058b6d958ef60381ecf99347350e2de1 [file] [log] [blame]
package cluster
import (
"crypto/ed25519"
"crypto/subtle"
"crypto/tls"
"crypto/x509"
"fmt"
"source.monogon.dev/metropolis/node/core/curator"
"source.monogon.dev/metropolis/node/core/localstorage"
)
// NodeCertificate is the public part of the credentials of a node. They are
// emitted for a node by the cluster CA contained within the curator.
type NodeCertificate struct {
node *x509.Certificate
ca *x509.Certificate
}
// ClusterCA returns the CA certificate of the cluster for which this
// NodeCertificate is emitted.
func (n *NodeCertificate) ClusterCA() *x509.Certificate {
return n.ca
}
// NodeCredentials are the public and private part of the credentials of a node.
//
// It represents all the data necessary for a node to authenticate over mTLS to
// other nodes and the rest of the cluster.
//
// It must never be made available to any node other than the node it has been
// emitted for.
type NodeCredentials struct {
NodeCertificate
private ed25519.PrivateKey
}
// NewNodeCertificate wraps a pair CA and node DER-encoded certificates into
// NodeCertificate, ensuring the given certificate data is valid and compatible
// Metropolis assumptions.
//
// It does _not_ verify that the given CA is a known/trusted Metropolis CA for a
// running cluster.
func NewNodeCertificate(cert, ca []byte) (*NodeCertificate, error) {
certParsed, err := x509.ParseCertificate(cert)
if err != nil {
return nil, fmt.Errorf("could not parse node certificate: %w", err)
}
caCertParsed, err := x509.ParseCertificate(ca)
if err != nil {
return nil, fmt.Errorf("could not parse ca certificate: %w", err)
}
// Ensure both CA and node certs use ED25519.
if certParsed.PublicKeyAlgorithm != x509.Ed25519 {
return nil, fmt.Errorf("node certificate must use ED25519, is %s", certParsed.PublicKeyAlgorithm.String())
}
if pub, ok := certParsed.PublicKey.(ed25519.PublicKey); !ok || len(pub) != ed25519.PublicKeySize {
return nil, fmt.Errorf("node certificate ED25519 key invalid")
}
if caCertParsed.PublicKeyAlgorithm != x509.Ed25519 {
return nil, fmt.Errorf("CA certificate must use ED25519, is %s", caCertParsed.PublicKeyAlgorithm.String())
}
if pub, ok := caCertParsed.PublicKey.(ed25519.PublicKey); !ok || len(pub) != ed25519.PublicKeySize {
return nil, fmt.Errorf("CA certificate ED25519 key invalid")
}
// Ensure that the certificate is signed by the CA certificate.
if err := certParsed.CheckSignatureFrom(caCertParsed); err != nil {
return nil, fmt.Errorf("certificate not signed by given CA: %w", err)
}
// Ensure that the certificate has the node's calculated ID in its DNS names.
found := false
nid := curator.NodeID(certParsed.PublicKey.(ed25519.PublicKey))
for _, n := range certParsed.DNSNames {
if n == nid {
found = true
break
}
}
if !found {
return nil, fmt.Errorf("calculated node ID %q not found in node certificate's DNS names (%v)", nid, certParsed.DNSNames)
}
return &NodeCertificate{
node: certParsed,
ca: caCertParsed,
}, nil
}
// NewNodeCredentials wraps a pair of CA and node DER-encoded certificates plus
// a private key into NodeCredentials, ensuring that the given data is valid and
// compatible with Metropolis assumptions.
//
// It does _not_ verify that the given CA is a known/trusted Metropolis CA for a
// running cluster.
func NewNodeCredentials(priv, cert, ca []byte) (*NodeCredentials, error) {
nc, err := NewNodeCertificate(cert, ca)
if err != nil {
return nil, err
}
// Ensure that the private key is a valid length.
if want, got := ed25519.PrivateKeySize, len(priv); want != got {
return nil, fmt.Errorf("private key is not the correct length, wanted %d, got %d", want, got)
}
// Ensure that the given private key matches the given public key.
if want, got := ed25519.PrivateKey(priv).Public().(ed25519.PublicKey), nc.PublicKey(); subtle.ConstantTimeCompare(want, got) != 1 {
return nil, fmt.Errorf("public key does not match private key")
}
return &NodeCredentials{
NodeCertificate: *nc,
private: ed25519.PrivateKey(priv),
}, nil
}
// Save stores the given node credentials in local storage.
func (c *NodeCredentials) Save(d *localstorage.PKIDirectory) error {
if err := d.CACertificate.Write(c.ca.Raw, 0400); err != nil {
return fmt.Errorf("when writing CA certificate: %w", err)
}
if err := d.Certificate.Write(c.node.Raw, 0400); err != nil {
return fmt.Errorf("when writing node certificate: %w", err)
}
if err := d.Key.Write(c.private, 0400); err != nil {
return fmt.Errorf("when writing node private key: %w", err)
}
return nil
}
// PublicKey returns the ED25519 public key corresponding to this node's
// certificate/credentials.
func (nc *NodeCertificate) PublicKey() ed25519.PublicKey {
// Safe: we have ensured that the given certificate has an ed25519 public key on
// NewNodeCertificate.
return nc.node.PublicKey.(ed25519.PublicKey)
}
// ID returns the canonical ID/name of the node for which this
// certificate/credentials were emitted.
func (nc *NodeCertificate) ID() string {
return curator.NodeID(nc.PublicKey())
}
func (nc *NodeCredentials) TLSCredentials() tls.Certificate {
return tls.Certificate{
Certificate: [][]byte{nc.node.Raw},
PrivateKey: nc.private,
}
}