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