A certificate is issued once and kept

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. Every machine carrying a certificate then
stood eternally "waiting" — pushed seconds ago and already behind — and
the forge test, the first to wait for settledness on such a machine,
failed four runs in a row wearing three other faults' clothes.

Found live on a kept mesh, which is what settled it: two plans seconds
apart, identical to the byte but for one serial, in the certificate
file. Deduction had four theories; the diff had one line.

The keeping columns had existed since the serving key's migration —
"and what was issued for it" — and were written by nothing, the same
shape ReleasePorts was found in this morning.

Kept beside the serving key it certifies, and it stands while the name,
the key and the clock agree: a node rejoining with a new key or renamed
gets a fresh signing, exactly as if nothing were kept, and so does one
whose certificate is into its last stretch of life. The port's rule and
the secret's, applied to the third thing composed fresh each time.
This commit is contained in:
2026-09-01 22:26:50 +02:00
parent b70f0d626a
commit 38d4e77cec
2 changed files with 92 additions and 1 deletions
+47 -1
View File
@@ -117,6 +117,25 @@ func (i *Identity) Certify(ctx context.Context, node, name, servingKey string) (
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
@@ -147,7 +166,34 @@ func (i *Identity) Certify(ctx context.Context, node, name, servingKey string) (
if err != nil {
return "", err
}
return string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})), nil
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) {
+45
View File
@@ -204,3 +204,48 @@ func TestTwoProcessesStartingTogetherAgreeOnOneAuthority(t *testing.T) {
t.Errorf("%d authorities exist; exactly one may", count)
}
}
// Asking twice gives the same certificate, to the byte.
//
// Every signing carries a fresh random serial, so a mesh that signed per composition composed a
// different declaration each 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 but for one serial.
func TestACertificateIsIssuedOnceAndKept(t *testing.T) {
ident := fresh(t)
ctx := context.Background()
public, _ := aServingKey(t)
if _, err := ident.RecordNodeKey(ctx, "1b7e0000-0000-4000-8000-000000000001", make(ed25519.PublicKey, ed25519.PublicKeySize)); err != nil {
t.Fatal(err)
}
if err := ident.RecordServingKey(ctx, "1b7e0000-0000-4000-8000-000000000001", public); err != nil {
t.Fatal(err)
}
first, err := ident.Certify(ctx, "a", "a.internal", public)
if err != nil {
t.Fatal(err)
}
second, err := ident.Certify(ctx, "a", "a.internal", public)
if err != nil {
t.Fatal(err)
}
if first != second {
t.Fatal("two askings gave two certificates; every composition then differs by a serial " +
"and a machine carrying one is eternally behind")
}
// And a new key is a new world: the kept answer must not outlive what it certifies.
fresh2, _ := aServingKey(t)
if err := ident.RecordServingKey(ctx, "1b7e0000-0000-4000-8000-000000000001", fresh2); err != nil {
t.Fatal(err)
}
third, err := ident.Certify(ctx, "a", "a.internal", fresh2)
if err != nil {
t.Fatal(err)
}
if third == first {
t.Fatal("the node rejoined with a new key and was handed the certificate of its old one")
}
}