C2: a key pair the manager cannot read is made anew in its place (issue 1409)
mesh/delivery merged: source.main moved to novox/mesh-catalog@6181d9afb3e5
mesh/check checking 8291502cbbaf on 6181d9afb3e5

Moved to its own account, the manager finds manager-key.json still owned by the tool runner's account, which the
node-engine does not hand over; it skipped making a pair because the file existed, and every verb answered the read
error. Now a pair it cannot read is replaced by a new one, renamed over it, and said in its journal; every use passes
the public key per ask, so nothing depends on the old pair.

Builds: design 39 bus-and-switch C2

Claude-Session: https://claude.ai/code/session_01NcbgfHb9d4T9nq5Q6WqdiJ
This commit is contained in:
2026-11-12 09:38:03 +01:00
parent 8d2497b65e
commit 8291502cbb
2 changed files with 74 additions and 13 deletions
@@ -60,7 +60,7 @@ func manager() (*Manager, error) {
if err != nil {
return nil, err
}
keys, err := keypair(dir)
keys, err := keypair(dir, say)
if err != nil {
return nil, err
}
@@ -131,28 +131,60 @@ func configLevels(node string) (map[string]string, error) {
}
// keypair is this module's own, made once in its state directory; the private half never leaves it.
// Written whole, then linked into place, so a second process reads the first's key and never half of it.
func keypair(dir string) (KeyPair, error) {
// Written whole, then linked into place, so a second process reads the first's key and never half of it. A pair it
// finds and cannot read (another account's, left before the module ran as its own) is made anew in its place and said
// in its journal (design 39 bus-and-switch C2): every use passes the public key per ask, so nothing depends on the old.
func keypair(dir string, log func(string, ...any)) (KeyPair, error) {
file := filepath.Join(dir, "manager-key.json")
if _, err := os.Stat(file); errors.Is(err, os.ErrNotExist) {
k, err := GenerateKeyPair()
if err != nil {
if err := writeKeyPair(dir, file, false); err != nil {
return KeyPair{}, err
}
raw, _ := json.Marshal(k)
tmp := filepath.Join(dir, fmt.Sprintf(".manager-key.%d.json", os.Getpid()))
if err := os.WriteFile(tmp, raw, 0o600); err != nil {
return KeyPair{}, err
}
_ = os.Link(tmp, file) // fails when another made it first, which is right
_ = os.Remove(tmp)
}
k, err := readKeyPair(file)
if err == nil {
return k, nil
}
log("the key pair %s cannot be read (%v); a new one is made in its place", file, err)
if err := writeKeyPair(dir, file, true); err != nil {
return KeyPair{}, err
}
return readKeyPair(file)
}
func readKeyPair(file string) (KeyPair, error) {
raw, err := os.ReadFile(file)
if err != nil {
return KeyPair{}, err
}
var k KeyPair
return k, json.Unmarshal(raw, &k)
if err := json.Unmarshal(raw, &k); err != nil {
return KeyPair{}, err
}
if k.PublicKey == "" || k.PrivateKey == "" {
return KeyPair{}, errors.New("it holds no whole key pair")
}
return k, nil
}
// writeKeyPair writes a new pair whole beside file, then links it into place, or, replacing, renames it over the one
// that cannot be read.
func writeKeyPair(dir, file string, replace bool) error {
k, err := GenerateKeyPair()
if err != nil {
return err
}
raw, _ := json.Marshal(k)
tmp := filepath.Join(dir, fmt.Sprintf(".manager-key.%d.json", os.Getpid()))
if err := os.WriteFile(tmp, raw, 0o600); err != nil {
return err
}
if replace {
return os.Rename(tmp, file)
}
_ = os.Link(tmp, file) // fails when another made it first, which is right
_ = os.Remove(tmp)
return nil
}
// ---- the long-running half ---------------------------------------------------------------------------
@@ -2,7 +2,10 @@ package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
)
@@ -41,3 +44,29 @@ func TestABoxOpensOnlyForItsRecipient(t *testing.T) {
t.Fatal("a box opened for another key")
}
}
// design 39 bus-and-switch C2: a key pair the manager finds and cannot read (left by another account, here mode 000) is
// replaced in its place by a new one, said in its journal; the new one is read back by the next start.
func TestAKeyPairItCannotReadIsMadeAnew(t *testing.T) {
if os.Geteuid() == 0 {
t.Fatal("run as root, a file of mode 000 is still read: this test needs an ordinary account")
}
dir := t.TempDir()
file := filepath.Join(dir, "manager-key.json")
if err := os.WriteFile(file, []byte(`{"publicKey":"old","privateKey":"old"}`), 0o000); err != nil {
t.Fatal(err)
}
var said []string
log := func(format string, a ...any) { said = append(said, fmt.Sprintf(format, a...)) }
k, err := keypair(dir, log)
if err != nil {
t.Fatalf("a key pair it cannot read stops the manager: %v", err)
}
if k.PublicKey == "" || k.PublicKey == "old" || len(said) != 1 || !strings.Contains(said[0], "manager-key.json") {
t.Fatalf("not made anew and said: key %q, journal %q", k.PublicKey, said)
}
again, err := keypair(dir, log)
if err != nil || again != k || len(said) != 1 {
t.Fatalf("the new pair is not the one read at the next start: %v, %v, %q", again == k, err, said)
}
}