blob: c15f91344a653d18d7c5831930ef90bf63a2487f [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 {
70 // Ensure ca certificate uses ED25519 keypair.
71 if _, ok := ca.PublicKey.(ed25519.PublicKey); !ok {
72 return fmt.Errorf("not issued for ed25519 keypair")
73 }
74 // Ensure CA certificate has the X.509 basic constraints extension. Everything
75 // else is legacy, we might as well weed that out early.
76 if !ca.BasicConstraintsValid {
77 return fmt.Errorf("does not have basic constraints")
78 }
79 // Ensure CA certificate can act as CA per BasicConstraints.
80 if !ca.IsCA {
81 return fmt.Errorf("not permitted to act as CA")
82 }
83 if ca.KeyUsage != 0 && ca.KeyUsage&x509.KeyUsageCertSign == 0 {
84 return fmt.Errorf("not permitted to sign certificates")
85 }
86 return nil
87}
88
Serge Bazanski3379a5d2021-09-09 12:56:40 +020089// VerifyInCluster ensures that the given certificate has been signed by a CA
90// certificate and are both certificates emitted for ed25519 keypairs.
91//
92// The subject certificate's public key is returned if verification is
93// successful, and error is returned otherwise.
94func VerifyInCluster(cert, ca *x509.Certificate) (ed25519.PublicKey, error) {
Serge Bazanskie78a0892021-10-07 17:03:49 +020095 if err := VerifyCAInsecure(ca); err != nil {
96 return nil, fmt.Errorf("ca certificate invalid: %w", err)
Serge Bazanski3379a5d2021-09-09 12:56:40 +020097 }
98
99 // Ensure subject cert is signed by ca.
100 if err := cert.CheckSignatureFrom(ca); err != nil {
101 return nil, fmt.Errorf("signature veritifcation failed: %w", err)
102 }
103
104 // Ensure subject certificate is _not_ CA. CAs (cluster or possibly
105 // intermediaries) are not supposed to either directly serve traffic or perform
106 // client actions on the cluster.
107 if cert.IsCA {
108 return nil, fmt.Errorf("subject certificate is a CA")
109 }
110
111 // Extract subject ED25519 public key.
112 pubkey, ok := cert.PublicKey.(ed25519.PublicKey)
113 if !ok {
114 return nil, fmt.Errorf("certificate not issued for ed25519 keypair")
115 }
116
117 return pubkey, nil
118}
119
120// VerifyNodeInCluster ensures that a given certificate is a Metropolis node
121// certificate emitted by a given Metropolis CA.
122//
123// The node's public key is returned if verification is successful, and error is
124// returned otherwise.
125func VerifyNodeInCluster(node, ca *x509.Certificate) (ed25519.PublicKey, error) {
126 pk, err := VerifyInCluster(node, ca)
127 if err != nil {
128 return nil, err
129 }
130
131 // Ensure certificate has ServerAuth bit, thereby marking it as a node certificate.
132 found := false
133 for _, ku := range node.ExtKeyUsage {
134 if ku == x509.ExtKeyUsageServerAuth {
135 found = true
136 break
137 }
138 }
139 if !found {
140 return nil, fmt.Errorf("not a node certificate (missing ServerAuth key usage)")
141 }
142
143 id := NodeID(pk)
144
145 // Ensure node ID is present in Subject.CommonName and at least one DNS name.
146 if node.Subject.CommonName != id {
147 return nil, fmt.Errorf("node ID not found in CommonName")
148 }
149
150 found = false
151 for _, n := range node.DNSNames {
152 if n == id {
153 found = true
154 break
155 }
156 }
157 if !found {
158 return nil, fmt.Errorf("node ID not found in DNSNames")
159 }
160
161 return pk, nil
162}
163
164// VerifyUserInCluster ensures that a given certificate is a Metropolis user
165// certificate emitted by a given Metropolis CA.
166//
167// The user certificate's identity is returned if verification is successful,
168// and error is returned otherwise.
169func VerifyUserInCluster(user, ca *x509.Certificate) (string, error) {
170 _, err := VerifyInCluster(user, ca)
171 if err != nil {
172 return "", err
173 }
174
175 // Ensure certificate has ClientAuth bit, thereby marking it as a user certificate.
176 found := false
177 for _, ku := range user.ExtKeyUsage {
178 if ku == x509.ExtKeyUsageClientAuth {
179 found = true
180 break
181 }
182 }
183 if !found {
184 return "", fmt.Errorf("not a user certificate (missing ClientAuth key usage)")
185 }
186
187 // Extract identity from CommonName, ensure set.
188 identity := user.Subject.CommonName
189 if identity == "" {
190 return "", fmt.Errorf("CommonName not set")
191 }
192 return identity, nil
193}