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) } // **Issued once and kept** — the port's rule and the secret's, applied to the certificate. // Every signing carries a fresh random serial, so a mesh that signed per composition // composed a different declaration every time it was asked what a machine should be — and // every machine carrying a certificate stood eternally "waiting", pushed seconds ago and // already behind. Found live on a kept mesh: two plans seconds apart, identical to the byte // but for one serial. The columns for keeping it had existed since the serving key's // migration — "and what was issued for it" — and were written by nothing, which is the same // shape ReleasePorts was found in. var kept *string err = i.store.Pool().QueryRow(ctx, `select certificate from node_key where serving_key = $1 and revoked is null`, servingKey).Scan(&kept) if err != nil && !errors.Is(err, pgx.ErrNoRows) { return "", err } if kept != nil && stillStands(*kept, name, public) { return *kept, nil } 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 } issued := string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})) // Kept beside the key it certifies. A serving key nothing recorded keeps nothing, and is // certified fresh each time — which only a test does. if _, err := i.store.Pool().Exec(ctx, `update node_key set certificate = $2, certified_at = now() where serving_key = $1 and revoked is null`, servingKey, issued); err != nil { return "", err } return issued, nil } // stillStands says whether a kept certificate is still the one Certify would issue: same name, // same key, and enough life left that nothing downstream will meet its expiry. Any mismatch means // the world moved — the node rejoined with a new key, or its name changed — and the answer is a // fresh signing, exactly as if nothing were kept. func stillStands(kept, name string, public []byte) bool { parsed, err := parse(kept) if err != nil { return false } if len(parsed.DNSNames) != 1 || parsed.DNSNames[0] != name { return false } held, ok := parsed.PublicKey.(ed25519.PublicKey) if !ok || !held.Equal(ed25519.PublicKey(public)) { return false } return time.Until(parsed.NotAfter) > forever/10 } 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 }