blob: 862e794c3f99bf513b72b41b21e0f989d76843d9 [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 {
115 if err := d.CACertificate.Write(n.ca.Raw, 0400); err != nil {
116 return fmt.Errorf("when writing CA certificate: %w", err)
117 }
118 if err := d.Certificate.Write(n.node.Raw, 0400); err != nil {
119 return fmt.Errorf("when writing node certificate: %w", err)
120 }
121 if err := d.Key.Write(n.private, 0400); err != nil {
122 return fmt.Errorf("when writing node private key: %w", err)
123 }
124 return nil
125}
126
127// NodeIDBare returns the `{pubkeyHash}` part of the node ID.
128func NodeIDBare(pub []byte) string {
129 return hex.EncodeToString(pub[:16])
130}
131
132// NodeID returns the name of this node, which is `metropolis-{pubkeyHash}`.
133// This name should be the primary way to refer to Metropoils nodes within a
134// cluster, and is guaranteed to be unique by relying on cryptographic
135// randomness.
136func NodeID(pub []byte) string {
137 return fmt.Sprintf("metropolis-%s", NodeIDBare(pub))
138}