package identity import ( "context" "crypto/ed25519" "crypto/rand" "crypto/x509" "crypto/x509/pkix" "encoding/base64" "encoding/pem" "errors" "fmt" "math/big" "time" "github.com/jackc/pgx/v5" ) // The authority that certifies names inside the mesh. // // novox/hq 08-connectivity keeps two authorities apart on purpose: a public one issues for names // the outside world reaches, and this one for names only the mesh knows. **It certifies a public // key a node generated**, which is the whole of what a certificate authority does — so nothing // secret travels, nothing is sealed, and a copy of this context's store certifies nothing it did // not already certify. // // It is not a bootstrap concern. A joining node verifies the control plane against the fingerprint // in its token (ADR 0004), so nothing needs this before membership. // forever is how long an internal certificate lasts. // // Long, and that is a choice rather than laziness. A short life needs something that renews it, // and a renewal that fails silently is a mesh that stops trusting itself on a date nobody wrote // down. What makes an internal certificate replaceable is that the mesh can reissue it on demand // and the node is told in the ordinary way — not that it expires. const forever = 10 * 365 * 24 * time.Hour // Authority is the mesh's own certificate authority. type Authority struct { Certificate string private ed25519.PrivateKey } // EstablishAuthority makes the mesh's authority if it has none, and returns it either way. // // Idempotent like the signing key beside it: two authorities and nothing says which certificate to // believe, so the row is written once and read forever after. func (i *Identity) EstablishAuthority(ctx context.Context) (Authority, error) { held, err := i.authority(ctx) if err == nil { return held, nil } if !errors.Is(err, pgx.ErrNoRows) { return Authority{}, err } public, private, err := ed25519.GenerateKey(rand.Reader) if err != nil { return Authority{}, err } serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) if err != nil { return Authority{}, err } template := &x509.Certificate{ SerialNumber: serial, Subject: pkix.Name{CommonName: "the mesh"}, NotBefore: time.Now().Add(-time.Hour), NotAfter: time.Now().Add(forever), IsCA: true, KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, // No BasicConstraintsValid path length: this signs leaves and nothing else, and an // authority that could sign another authority is one that can be delegated without // anybody deciding to. BasicConstraintsValid: true, MaxPathLen: 0, MaxPathLenZero: true, } der, err := x509.CreateCertificate(rand.Reader, template, template, public, private) if err != nil { return Authority{}, err } certificate := string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})) // Written once. A second insert loses to the first, and both callers then read the same // authority — which is what must happen when two control planes start together. if _, err := i.store.Pool().Exec(ctx, `insert into authority (singleton, certificate, private) values (true, $1, $2) on conflict (singleton) do nothing`, certificate, base64.StdEncoding.EncodeToString(private)); err != nil { return Authority{}, err } return i.authority(ctx) } func (i *Identity) authority(ctx context.Context) (Authority, error) { var certificate, private string if err := i.store.Pool().QueryRow(ctx, `select certificate, private from authority where singleton`).Scan(&certificate, &private); err != nil { return Authority{}, err } raw, err := base64.StdEncoding.DecodeString(private) if err != nil || len(raw) != ed25519.PrivateKeySize { return Authority{}, fmt.Errorf("the mesh's authority key is unusable") } return Authority{Certificate: certificate, private: ed25519.PrivateKey(raw)}, nil } // Certify issues a certificate for a node's internal name, binding the key that node generated. // // **The public key is given, never made here.** A certificate authority's whole job is to say // *this name belongs to the holder of this key*, and an authority that made the key would be // saying something about a key it also holds. func (i *Identity) Certify(ctx context.Context, node, name, servingKey string) (string, error) { public, err := base64.StdEncoding.DecodeString(servingKey) if err != nil || len(public) != ed25519.PublicKeySize { return "", fmt.Errorf("%s presented something that is not a serving key", node) } authority, err := i.EstablishAuthority(ctx) if err != nil { return "", err } parent, err := parse(authority.Certificate) if err != nil { return "", err } serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) if err != nil { return "", err } template := &x509.Certificate{ SerialNumber: serial, Subject: pkix.Name{CommonName: name}, // The name is in the subject alternative names, which is the only place anything has // looked for a decade — a certificate carrying it only in the common name is a // certificate every modern client refuses. DNSNames: []string{name}, NotBefore: time.Now().Add(-time.Hour), NotAfter: time.Now().Add(forever), KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, } der, err := x509.CreateCertificate(rand.Reader, template, parent, ed25519.PublicKey(public), authority.private) if err != nil { return "", err } return string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})), nil } func parse(certificate string) (*x509.Certificate, error) { block, _ := pem.Decode([]byte(certificate)) if block == nil { return nil, fmt.Errorf("the mesh's authority is not a certificate") } return x509.ParseCertificate(block.Bytes) } // RecordServingKey keeps the public half a node generated for serving TLS. func (i *Identity) RecordServingKey(ctx context.Context, node, key string) error { if key == "" { return nil } _, err := i.store.Pool().Exec(ctx, `update node_key set serving_key = $2 where node = $1 and revoked is null`, node, key) return err } // ServingKeyOf is what a node serves TLS with, empty if it has said nothing. func (i *Identity) ServingKeyOf(ctx context.Context, node string) (string, error) { var key *string err := i.store.Pool().QueryRow(ctx, `select serving_key from node_key where node = $1 and revoked is null`, node).Scan(&key) if errors.Is(err, pgx.ErrNoRows) { return "", nil } if err != nil { return "", err } if key == nil { return "", nil } return *key, nil }