blob: 95b7e0d6234c72047b65929fde22023d95be58ff [file] [log] [blame]
package identity
import (
"crypto/ed25519"
"crypto/x509"
"crypto/x509/pkix"
"fmt"
"math/big"
)
// UserCertificate makes a Metropolis-compatible user certificate template.
func UserCertificate(identity string) x509.Certificate {
return x509.Certificate{
Subject: pkix.Name{
CommonName: identity,
},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth,
},
}
}
// NodeCertificate makes a Metropolis-compatible node certificate template.
func NodeCertificate(pubkey ed25519.PublicKey) x509.Certificate {
return x509.Certificate{
Subject: pkix.Name{
CommonName: NodeID(pubkey),
},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{
// Note: node certificates are also effectively being used to perform client
// authentication to other node certificates, but they don't have the ClientAuth
// bit set. Instead, Metropolis uses the ClientAuth and ServerAuth bits
// exclusively to distinguish Metropolis nodes from Metropolis users.
x509.ExtKeyUsageServerAuth,
},
// We populate the Node's ID (metropolis-xxxx) as the DNS name for this
// certificate for ease of use within Metropolis, where the local DNS setup
// allows each node's IP address to be resolvable through the Node's ID.
DNSNames: []string{
NodeID(pubkey),
},
}
}
// CA makes a Metropolis-compatible CA certificate template.
//
// cn is a human-readable string that can be used to distinguish Metropolis
// clusters, if needed. It is not machine-parsed, instead only signature
// verification and CA pinning is performed.
func CACertificate(cn string) x509.Certificate {
return x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{
CommonName: cn,
},
IsCA: true,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageOCSPSigning},
}
}
// VerifyInCluster ensures that the given certificate has been signed by a CA
// certificate and are both certificates emitted for ed25519 keypairs.
//
// The subject certificate's public key is returned if verification is
// successful, and error is returned otherwise.
func VerifyInCluster(cert, ca *x509.Certificate) (ed25519.PublicKey, error) {
// Ensure ca certificate uses ED25519 keypair.
if _, ok := ca.PublicKey.(ed25519.PublicKey); !ok {
return nil, fmt.Errorf("ca certificate not issued for ed25519 keypair")
}
// Ensure subject cert is signed by ca.
if err := cert.CheckSignatureFrom(ca); err != nil {
return nil, fmt.Errorf("signature veritifcation failed: %w", err)
}
// Ensure subject certificate is _not_ CA. CAs (cluster or possibly
// intermediaries) are not supposed to either directly serve traffic or perform
// client actions on the cluster.
if cert.IsCA {
return nil, fmt.Errorf("subject certificate is a CA")
}
// Extract subject ED25519 public key.
pubkey, ok := cert.PublicKey.(ed25519.PublicKey)
if !ok {
return nil, fmt.Errorf("certificate not issued for ed25519 keypair")
}
return pubkey, nil
}
// VerifyNodeInCluster ensures that a given certificate is a Metropolis node
// certificate emitted by a given Metropolis CA.
//
// The node's public key is returned if verification is successful, and error is
// returned otherwise.
func VerifyNodeInCluster(node, ca *x509.Certificate) (ed25519.PublicKey, error) {
pk, err := VerifyInCluster(node, ca)
if err != nil {
return nil, err
}
// Ensure certificate has ServerAuth bit, thereby marking it as a node certificate.
found := false
for _, ku := range node.ExtKeyUsage {
if ku == x509.ExtKeyUsageServerAuth {
found = true
break
}
}
if !found {
return nil, fmt.Errorf("not a node certificate (missing ServerAuth key usage)")
}
id := NodeID(pk)
// Ensure node ID is present in Subject.CommonName and at least one DNS name.
if node.Subject.CommonName != id {
return nil, fmt.Errorf("node ID not found in CommonName")
}
found = false
for _, n := range node.DNSNames {
if n == id {
found = true
break
}
}
if !found {
return nil, fmt.Errorf("node ID not found in DNSNames")
}
return pk, nil
}
// VerifyUserInCluster ensures that a given certificate is a Metropolis user
// certificate emitted by a given Metropolis CA.
//
// The user certificate's identity is returned if verification is successful,
// and error is returned otherwise.
func VerifyUserInCluster(user, ca *x509.Certificate) (string, error) {
_, err := VerifyInCluster(user, ca)
if err != nil {
return "", err
}
// Ensure certificate has ClientAuth bit, thereby marking it as a user certificate.
found := false
for _, ku := range user.ExtKeyUsage {
if ku == x509.ExtKeyUsageClientAuth {
found = true
break
}
}
if !found {
return "", fmt.Errorf("not a user certificate (missing ClientAuth key usage)")
}
// Extract identity from CommonName, ensure set.
identity := user.Subject.CommonName
if identity == "" {
return "", fmt.Errorf("CommonName not set")
}
return identity, nil
}