blob: 6663771be33474442c0d382e2f665eb19e9cbdf6 [file] [log] [blame]
Serge Bazanski3379a5d2021-09-09 12:56:40 +02001package identity
2
3import (
4 "crypto/ed25519"
5 "crypto/subtle"
6 "crypto/tls"
7 "crypto/x509"
8 "encoding/hex"
9 "fmt"
Serge Bazanski3379a5d2021-09-09 12:56:40 +020010)
11
12// Node is the public part of the credentials of a node. They are
13// emitted for a node by the cluster CA contained within the curator.
14type Node struct {
15 node *x509.Certificate
16 ca *x509.Certificate
17}
18
19// NewNode wraps a pair CA and node DER-encoded certificates into
20// Node, ensuring the given certificate data is valid and compatible
21// with Metropolis assumptions.
22func NewNode(cert, ca []byte) (*Node, error) {
23 certParsed, err := x509.ParseCertificate(cert)
24 if err != nil {
25 return nil, fmt.Errorf("could not parse node certificate: %w", err)
26 }
27 caCertParsed, err := x509.ParseCertificate(ca)
28 if err != nil {
29 return nil, fmt.Errorf("could not parse ca certificate: %w", err)
30 }
31
32 if _, err := VerifyNodeInCluster(certParsed, caCertParsed); err != nil {
33 return nil, fmt.Errorf("could not node certificate within cluster CA: %w", err)
34 }
35
36 return &Node{
37 node: certParsed,
38 ca: caCertParsed,
39 }, nil
40}
41
42// PublicKey returns the Ed25519 public key corresponding to this node's
43// certificate/credentials.
44func (n *Node) PublicKey() ed25519.PublicKey {
45 // Safe: we have ensured that the given certificate has an Ed25519 public key on
46 // NewNode.
47 return n.node.PublicKey.(ed25519.PublicKey)
48}
49
50// ClusterCA returns the CA certificate of the cluster for which this
51// Node is emitted.
52func (n *Node) ClusterCA() *x509.Certificate {
53 return n.ca
54}
55
56// ID returns the canonical ID/name of the node for which this
57// certificate/credentials were emitted.
58func (n *Node) ID() string {
59 return NodeID(n.PublicKey())
60}
61
62func (n *Node) Certificate() *x509.Certificate {
63 return n.node
64}
65
66// NodeCredentials are the public and private part of the credentials of a node.
67//
68// It represents all the data necessary for a node to authenticate over mTLS to
69// other nodes and the rest of the cluster.
70//
71// It must never be made available to any node other than the node it has been
72// emitted for.
73type NodeCredentials struct {
74 Node
75 private ed25519.PrivateKey
76}
77
78// NewNodeCredentials wraps a pair of CA and node DER-encoded certificates plus
79// a private key into NodeCredentials, ensuring that the given data is valid and
80// compatible with Metropolis assumptions.
81func NewNodeCredentials(priv, cert, ca []byte) (*NodeCredentials, error) {
82 nc, err := NewNode(cert, ca)
83 if err != nil {
84 return nil, err
85 }
86
87 // Ensure that the private key is a valid length.
88 if want, got := ed25519.PrivateKeySize, len(priv); want != got {
89 return nil, fmt.Errorf("private key is not the correct length, wanted %d, got %d", want, got)
90 }
91
92 // Ensure that the given private key matches the given public key.
93 if want, got := ed25519.PrivateKey(priv).Public().(ed25519.PublicKey), nc.PublicKey(); subtle.ConstantTimeCompare(want, got) != 1 {
94 return nil, fmt.Errorf("public key does not match private key")
95 }
96
97 return &NodeCredentials{
98 Node: *nc,
99 private: priv,
100 }, nil
101}
102
103func (n *NodeCredentials) TLSCredentials() tls.Certificate {
104 return tls.Certificate{
105 Leaf: n.node,
106 Certificate: [][]byte{n.node.Raw},
107 PrivateKey: n.private,
108 }
109}
110
Serge Bazanski4f28b9e2023-07-19 17:11:05 +0200111type PKIDirectory interface {
112 ReadAll() (ca, cert *x509.Certificate, key ed25519.PrivateKey, err error)
113 WriteAll(cert []byte, key ed25519.PrivateKey, ca []byte) error
114}
115
Serge Bazanski3379a5d2021-09-09 12:56:40 +0200116// Save stores the given node credentials in local storage.
Serge Bazanski4f28b9e2023-07-19 17:11:05 +0200117func (n *NodeCredentials) Save(d PKIDirectory) error {
Serge Bazanskie013f1b2023-03-21 11:49:54 +0100118 return d.WriteAll(n.node.Raw, n.private, n.ca.Raw)
Serge Bazanski3379a5d2021-09-09 12:56:40 +0200119}
120
Mateusz Zalega2930e992022-04-25 12:52:35 +0200121// Read initializes NodeCredentials' contents with the data stored in the
122// PKIDirectory d. It may return an I/O error, or a parsing error.
Serge Bazanski4f28b9e2023-07-19 17:11:05 +0200123func (n *NodeCredentials) Read(d PKIDirectory) error {
Serge Bazanskie013f1b2023-03-21 11:49:54 +0100124 ca, cert, key, err := d.ReadAll()
125 if err != nil {
126 return err
Mateusz Zalega2930e992022-04-25 12:52:35 +0200127 }
Serge Bazanskie013f1b2023-03-21 11:49:54 +0100128 n.ca = ca
129 n.node = cert
130 n.private = key
Mateusz Zalega2930e992022-04-25 12:52:35 +0200131 return nil
132}
133
Serge Bazanski3379a5d2021-09-09 12:56:40 +0200134// NodeIDBare returns the `{pubkeyHash}` part of the node ID.
135func NodeIDBare(pub []byte) string {
136 return hex.EncodeToString(pub[:16])
137}
138
139// NodeID returns the name of this node, which is `metropolis-{pubkeyHash}`.
140// This name should be the primary way to refer to Metropoils nodes within a
141// cluster, and is guaranteed to be unique by relying on cryptographic
142// randomness.
143func NodeID(pub []byte) string {
144 return fmt.Sprintf("metropolis-%s", NodeIDBare(pub))
145}