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