blob: a8f2e7b76bb0175164f7c51d15098832394d4751 [file] [log] [blame]
Serge Bazanski3379a5d2021-09-09 12:56:40 +02001package identity
2
3import (
4 "crypto/ed25519"
5 "crypto/x509"
6 "crypto/x509/pkix"
7 "fmt"
8 "math/big"
9)
10
11// UserCertificate makes a Metropolis-compatible user certificate template.
12func UserCertificate(identity string) x509.Certificate {
13 return x509.Certificate{
14 Subject: pkix.Name{
15 CommonName: identity,
16 },
17 KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
18 ExtKeyUsage: []x509.ExtKeyUsage{
19 x509.ExtKeyUsageClientAuth,
20 },
21 }
22}
23
24// NodeCertificate makes a Metropolis-compatible node certificate template.
25func NodeCertificate(pubkey ed25519.PublicKey) x509.Certificate {
26 return x509.Certificate{
27 Subject: pkix.Name{
28 CommonName: NodeID(pubkey),
29 },
30 KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
31 ExtKeyUsage: []x509.ExtKeyUsage{
32 // Note: node certificates are also effectively being used to perform client
33 // authentication to other node certificates, but they don't have the ClientAuth
34 // bit set. Instead, Metropolis uses the ClientAuth and ServerAuth bits
35 // exclusively to distinguish Metropolis nodes from Metropolis users.
36 x509.ExtKeyUsageServerAuth,
37 },
38 // We populate the Node's ID (metropolis-xxxx) as the DNS name for this
39 // certificate for ease of use within Metropolis, where the local DNS setup
40 // allows each node's IP address to be resolvable through the Node's ID.
41 DNSNames: []string{
42 NodeID(pubkey),
43 },
44 }
45}
46
47// CA makes a Metropolis-compatible CA certificate template.
48//
49// cn is a human-readable string that can be used to distinguish Metropolis
50// clusters, if needed. It is not machine-parsed, instead only signature
51// verification and CA pinning is performed.
52func CACertificate(cn string) x509.Certificate {
53 return x509.Certificate{
54 SerialNumber: big.NewInt(1),
55 Subject: pkix.Name{
56 CommonName: cn,
57 },
58 IsCA: true,
59 KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageDigitalSignature,
60 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageOCSPSigning},
61 }
62}
63
Serge Bazanskie78a0892021-10-07 17:03:49 +020064// VerifyCAInsecure ensures that the given certificate is a valid certificate
65// that is allowed to act as a CA and which is emitted for an Ed25519 keypair.
66//
67// It does _not_ ensure that the certificate is the local node's CA, and should
68// not be used for security checks, just for data validation checks.
69func VerifyCAInsecure(ca *x509.Certificate) error {
Serge Bazanski3ac3a2e2023-03-29 14:20:06 +020070 if ca == nil {
71 return fmt.Errorf("ca must be set")
72 }
Serge Bazanskie78a0892021-10-07 17:03:49 +020073 // Ensure ca certificate uses ED25519 keypair.
74 if _, ok := ca.PublicKey.(ed25519.PublicKey); !ok {
75 return fmt.Errorf("not issued for ed25519 keypair")
76 }
77 // Ensure CA certificate has the X.509 basic constraints extension. Everything
78 // else is legacy, we might as well weed that out early.
79 if !ca.BasicConstraintsValid {
80 return fmt.Errorf("does not have basic constraints")
81 }
82 // Ensure CA certificate can act as CA per BasicConstraints.
83 if !ca.IsCA {
84 return fmt.Errorf("not permitted to act as CA")
85 }
86 if ca.KeyUsage != 0 && ca.KeyUsage&x509.KeyUsageCertSign == 0 {
87 return fmt.Errorf("not permitted to sign certificates")
88 }
89 return nil
90}
91
Serge Bazanski3379a5d2021-09-09 12:56:40 +020092// VerifyInCluster ensures that the given certificate has been signed by a CA
93// certificate and are both certificates emitted for ed25519 keypairs.
94//
95// The subject certificate's public key is returned if verification is
96// successful, and error is returned otherwise.
97func VerifyInCluster(cert, ca *x509.Certificate) (ed25519.PublicKey, error) {
Serge Bazanskie78a0892021-10-07 17:03:49 +020098 if err := VerifyCAInsecure(ca); err != nil {
99 return nil, fmt.Errorf("ca certificate invalid: %w", err)
Serge Bazanski3379a5d2021-09-09 12:56:40 +0200100 }
101
102 // Ensure subject cert is signed by ca.
103 if err := cert.CheckSignatureFrom(ca); err != nil {
104 return nil, fmt.Errorf("signature veritifcation failed: %w", err)
105 }
106
107 // Ensure subject certificate is _not_ CA. CAs (cluster or possibly
108 // intermediaries) are not supposed to either directly serve traffic or perform
109 // client actions on the cluster.
110 if cert.IsCA {
111 return nil, fmt.Errorf("subject certificate is a CA")
112 }
113
114 // Extract subject ED25519 public key.
115 pubkey, ok := cert.PublicKey.(ed25519.PublicKey)
116 if !ok {
117 return nil, fmt.Errorf("certificate not issued for ed25519 keypair")
118 }
119
120 return pubkey, nil
121}
122
123// VerifyNodeInCluster ensures that a given certificate is a Metropolis node
124// certificate emitted by a given Metropolis CA.
125//
126// The node's public key is returned if verification is successful, and error is
127// returned otherwise.
128func VerifyNodeInCluster(node, ca *x509.Certificate) (ed25519.PublicKey, error) {
129 pk, err := VerifyInCluster(node, ca)
130 if err != nil {
131 return nil, err
132 }
133
134 // Ensure certificate has ServerAuth bit, thereby marking it as a node certificate.
135 found := false
136 for _, ku := range node.ExtKeyUsage {
137 if ku == x509.ExtKeyUsageServerAuth {
138 found = true
139 break
140 }
141 }
142 if !found {
143 return nil, fmt.Errorf("not a node certificate (missing ServerAuth key usage)")
144 }
145
146 id := NodeID(pk)
147
148 // Ensure node ID is present in Subject.CommonName and at least one DNS name.
149 if node.Subject.CommonName != id {
150 return nil, fmt.Errorf("node ID not found in CommonName")
151 }
152
153 found = false
154 for _, n := range node.DNSNames {
155 if n == id {
156 found = true
157 break
158 }
159 }
160 if !found {
161 return nil, fmt.Errorf("node ID not found in DNSNames")
162 }
163
164 return pk, nil
165}
166
167// VerifyUserInCluster ensures that a given certificate is a Metropolis user
168// certificate emitted by a given Metropolis CA.
169//
170// The user certificate's identity is returned if verification is successful,
171// and error is returned otherwise.
172func VerifyUserInCluster(user, ca *x509.Certificate) (string, error) {
173 _, err := VerifyInCluster(user, ca)
174 if err != nil {
175 return "", err
176 }
177
178 // Ensure certificate has ClientAuth bit, thereby marking it as a user certificate.
179 found := false
180 for _, ku := range user.ExtKeyUsage {
181 if ku == x509.ExtKeyUsageClientAuth {
182 found = true
183 break
184 }
185 }
186 if !found {
187 return "", fmt.Errorf("not a user certificate (missing ClientAuth key usage)")
188 }
189
190 // Extract identity from CommonName, ensure set.
191 identity := user.Subject.CommonName
192 if identity == "" {
193 return "", fmt.Errorf("CommonName not set")
194 }
195 return identity, nil
196}