blob: 0d4daacd8e31b65ab9ff41368080a04c1098a45c [file] [log] [blame]
Serge Bazanski439b95e2021-06-30 23:16:13 +02001package cluster
2
3import (
4 "crypto/ed25519"
5 "crypto/subtle"
6 "crypto/x509"
7 "fmt"
8
9 "source.monogon.dev/metropolis/node/core/curator"
10 "source.monogon.dev/metropolis/node/core/localstorage"
11)
12
13// NodeCertificate is the public part of the credentials of a node. They are
14// emitted for a node by the cluster CA contained within the curator.
15type NodeCertificate struct {
16 node *x509.Certificate
17 ca *x509.Certificate
18}
19
20// NodeCredentials are the public and private part of the credentials of a node.
21//
22// It represents all the data necessary for a node to authenticate over mTLS to
23// other nodes and the rest of the cluster.
24//
25// It must never be made available to any node other than the node it has been
26// emitted for.
27type NodeCredentials struct {
28 NodeCertificate
29 private ed25519.PrivateKey
30}
31
32// NewNodeCertificate wraps a pair CA and node DER-encoded certificates into
33// NodeCertificate, ensuring the given certificate data is valid and compatible
34// Metropolis assumptions.
35//
36// It does _not_ verify that the given CA is a known/trusted Metropolis CA for a
37// running cluster.
38func NewNodeCertificate(cert, ca []byte) (*NodeCertificate, error) {
39 certParsed, err := x509.ParseCertificate(cert)
40 if err != nil {
41 return nil, fmt.Errorf("could not parse node certificate: %w", err)
42 }
43 caCertParsed, err := x509.ParseCertificate(ca)
44 if err != nil {
45 return nil, fmt.Errorf("could not parse ca certificate: %w", err)
46 }
47
48 // Ensure both CA and node certs use ED25519.
49 if certParsed.PublicKeyAlgorithm != x509.Ed25519 {
50 return nil, fmt.Errorf("node certificate must use ED25519, is %s", certParsed.PublicKeyAlgorithm.String())
51 }
52 if pub, ok := certParsed.PublicKey.(ed25519.PublicKey); !ok || len(pub) != ed25519.PublicKeySize {
53 return nil, fmt.Errorf("node certificate ED25519 key invalid")
54 }
55 if caCertParsed.PublicKeyAlgorithm != x509.Ed25519 {
56 return nil, fmt.Errorf("CA certificate must use ED25519, is %s", caCertParsed.PublicKeyAlgorithm.String())
57 }
58 if pub, ok := caCertParsed.PublicKey.(ed25519.PublicKey); !ok || len(pub) != ed25519.PublicKeySize {
59 return nil, fmt.Errorf("CA certificate ED25519 key invalid")
60 }
61
62 // Ensure that the certificate is signed by the CA certificate.
63 if err := certParsed.CheckSignatureFrom(caCertParsed); err != nil {
64 return nil, fmt.Errorf("certificate not signed by given CA: %w", err)
65 }
66
67 // Ensure that the certificate has the node's calculated ID in its DNS names.
68 found := false
69 nid := curator.NodeID(certParsed.PublicKey.(ed25519.PublicKey))
70 for _, n := range certParsed.DNSNames {
71 if n == nid {
72 found = true
73 break
74 }
75 }
76 if !found {
77 return nil, fmt.Errorf("calculated node ID %q not found in node certificate's DNS names (%v)", nid, certParsed.DNSNames)
78 }
79
80 return &NodeCertificate{
81 node: certParsed,
82 ca: caCertParsed,
83 }, nil
84}
85
86// NewNodeCredentials wraps a pair of CA and node DER-encoded certificates plus
87// a private key into NodeCredentials, ensuring that the given data is valid and
88// compatible with Metropolis assumptions.
89//
90// It does _not_ verify that the given CA is a known/trusted Metropolis CA for a
91// running cluster.
92func NewNodeCredentials(priv, cert, ca []byte) (*NodeCredentials, error) {
93 nc, err := NewNodeCertificate(cert, ca)
94 if err != nil {
95 return nil, err
96 }
97
98 // Ensure that the private key is a valid length.
99 if want, got := ed25519.PrivateKeySize, len(priv); want != got {
100 return nil, fmt.Errorf("private key is not the correct length, wanted %d, got %d", want, got)
101 }
102
103 // Ensure that the given private key matches the given public key.
104 if want, got := ed25519.PrivateKey(priv).Public().(ed25519.PublicKey), nc.PublicKey(); subtle.ConstantTimeCompare(want, got) != 1 {
105 return nil, fmt.Errorf("public key does not match private key")
106 }
107
108 return &NodeCredentials{
109 NodeCertificate: *nc,
110 private: ed25519.PrivateKey(priv),
111 }, nil
112}
113
114// Save stores the given node credentials in local storage.
115func (c *NodeCredentials) Save(d *localstorage.PKIDirectory) error {
116 if err := d.CACertificate.Write(c.ca.Raw, 0400); err != nil {
117 return fmt.Errorf("when writing CA certificate: %w", err)
118 }
119 if err := d.Certificate.Write(c.node.Raw, 0400); err != nil {
120 return fmt.Errorf("when writing node certificate: %w", err)
121 }
122 if err := d.Key.Write(c.private, 0400); err != nil {
123 return fmt.Errorf("when writing node private key: %w", err)
124 }
125 return nil
126}
127
128// PublicKey returns the ED25519 public key corresponding to this node's
129// certificate/credentials.
130func (nc *NodeCertificate) PublicKey() ed25519.PublicKey {
131 // Safe: we have ensured that the given certificate has an ed25519 public key on
132 // NewNodeCertificate.
133 return nc.node.PublicKey.(ed25519.PublicKey)
134}
135
136// ID returns the canonical ID/name of the node for which this
137// certificate/credentials were emitted.
138func (nc *NodeCertificate) ID() string {
139 return curator.NodeID(nc.PublicKey())
140}