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