blob: 6663771be33474442c0d382e2f665eb19e9cbdf6 [file] [log] [blame]
package identity
import (
"crypto/ed25519"
"crypto/subtle"
"crypto/tls"
"crypto/x509"
"encoding/hex"
"fmt"
)
// Node 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 Node struct {
node *x509.Certificate
ca *x509.Certificate
}
// NewNode wraps a pair CA and node DER-encoded certificates into
// Node, ensuring the given certificate data is valid and compatible
// with Metropolis assumptions.
func NewNode(cert, ca []byte) (*Node, 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)
}
if _, err := VerifyNodeInCluster(certParsed, caCertParsed); err != nil {
return nil, fmt.Errorf("could not node certificate within cluster CA: %w", err)
}
return &Node{
node: certParsed,
ca: caCertParsed,
}, nil
}
// PublicKey returns the Ed25519 public key corresponding to this node's
// certificate/credentials.
func (n *Node) PublicKey() ed25519.PublicKey {
// Safe: we have ensured that the given certificate has an Ed25519 public key on
// NewNode.
return n.node.PublicKey.(ed25519.PublicKey)
}
// ClusterCA returns the CA certificate of the cluster for which this
// Node is emitted.
func (n *Node) ClusterCA() *x509.Certificate {
return n.ca
}
// ID returns the canonical ID/name of the node for which this
// certificate/credentials were emitted.
func (n *Node) ID() string {
return NodeID(n.PublicKey())
}
func (n *Node) Certificate() *x509.Certificate {
return n.node
}
// 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 {
Node
private ed25519.PrivateKey
}
// 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.
func NewNodeCredentials(priv, cert, ca []byte) (*NodeCredentials, error) {
nc, err := NewNode(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{
Node: *nc,
private: priv,
}, nil
}
func (n *NodeCredentials) TLSCredentials() tls.Certificate {
return tls.Certificate{
Leaf: n.node,
Certificate: [][]byte{n.node.Raw},
PrivateKey: n.private,
}
}
type PKIDirectory interface {
ReadAll() (ca, cert *x509.Certificate, key ed25519.PrivateKey, err error)
WriteAll(cert []byte, key ed25519.PrivateKey, ca []byte) error
}
// Save stores the given node credentials in local storage.
func (n *NodeCredentials) Save(d PKIDirectory) error {
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 PKIDirectory) error {
ca, cert, key, err := d.ReadAll()
if err != nil {
return err
}
n.ca = ca
n.node = cert
n.private = key
return nil
}
// NodeIDBare returns the `{pubkeyHash}` part of the node ID.
func NodeIDBare(pub []byte) string {
return hex.EncodeToString(pub[:16])
}
// NodeID returns the name of this node, which is `metropolis-{pubkeyHash}`.
// This name should be the primary way to refer to Metropoils nodes within a
// cluster, and is guaranteed to be unique by relying on cryptographic
// randomness.
func NodeID(pub []byte) string {
return fmt.Sprintf("metropolis-%s", NodeIDBare(pub))
}