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