blob: 95b7e0d6234c72047b65929fde22023d95be58ff [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
64// VerifyInCluster ensures that the given certificate has been signed by a CA
65// certificate and are both certificates emitted for ed25519 keypairs.
66//
67// The subject certificate's public key is returned if verification is
68// successful, and error is returned otherwise.
69func VerifyInCluster(cert, ca *x509.Certificate) (ed25519.PublicKey, error) {
70 // Ensure ca certificate uses ED25519 keypair.
71 if _, ok := ca.PublicKey.(ed25519.PublicKey); !ok {
72 return nil, fmt.Errorf("ca certificate not issued for ed25519 keypair")
73 }
74
75 // Ensure subject cert is signed by ca.
76 if err := cert.CheckSignatureFrom(ca); err != nil {
77 return nil, fmt.Errorf("signature veritifcation failed: %w", err)
78 }
79
80 // Ensure subject certificate is _not_ CA. CAs (cluster or possibly
81 // intermediaries) are not supposed to either directly serve traffic or perform
82 // client actions on the cluster.
83 if cert.IsCA {
84 return nil, fmt.Errorf("subject certificate is a CA")
85 }
86
87 // Extract subject ED25519 public key.
88 pubkey, ok := cert.PublicKey.(ed25519.PublicKey)
89 if !ok {
90 return nil, fmt.Errorf("certificate not issued for ed25519 keypair")
91 }
92
93 return pubkey, nil
94}
95
96// VerifyNodeInCluster ensures that a given certificate is a Metropolis node
97// certificate emitted by a given Metropolis CA.
98//
99// The node's public key is returned if verification is successful, and error is
100// returned otherwise.
101func VerifyNodeInCluster(node, ca *x509.Certificate) (ed25519.PublicKey, error) {
102 pk, err := VerifyInCluster(node, ca)
103 if err != nil {
104 return nil, err
105 }
106
107 // Ensure certificate has ServerAuth bit, thereby marking it as a node certificate.
108 found := false
109 for _, ku := range node.ExtKeyUsage {
110 if ku == x509.ExtKeyUsageServerAuth {
111 found = true
112 break
113 }
114 }
115 if !found {
116 return nil, fmt.Errorf("not a node certificate (missing ServerAuth key usage)")
117 }
118
119 id := NodeID(pk)
120
121 // Ensure node ID is present in Subject.CommonName and at least one DNS name.
122 if node.Subject.CommonName != id {
123 return nil, fmt.Errorf("node ID not found in CommonName")
124 }
125
126 found = false
127 for _, n := range node.DNSNames {
128 if n == id {
129 found = true
130 break
131 }
132 }
133 if !found {
134 return nil, fmt.Errorf("node ID not found in DNSNames")
135 }
136
137 return pk, nil
138}
139
140// VerifyUserInCluster ensures that a given certificate is a Metropolis user
141// certificate emitted by a given Metropolis CA.
142//
143// The user certificate's identity is returned if verification is successful,
144// and error is returned otherwise.
145func VerifyUserInCluster(user, ca *x509.Certificate) (string, error) {
146 _, err := VerifyInCluster(user, ca)
147 if err != nil {
148 return "", err
149 }
150
151 // Ensure certificate has ClientAuth bit, thereby marking it as a user certificate.
152 found := false
153 for _, ku := range user.ExtKeyUsage {
154 if ku == x509.ExtKeyUsageClientAuth {
155 found = true
156 break
157 }
158 }
159 if !found {
160 return "", fmt.Errorf("not a user certificate (missing ClientAuth key usage)")
161 }
162
163 // Extract identity from CommonName, ensure set.
164 identity := user.Subject.CommonName
165 if identity == "" {
166 return "", fmt.Errorf("CommonName not set")
167 }
168 return identity, nil
169}