Files
gogobee/internal/bot/crosssigning_test.go
prosolis 1adcd05dc7 cross-signing: persist the bot's identity instead of reminting it every start
Both session paths called GenerateAndUploadCrossSigningKeys unconditionally on
every start. That published a fresh cross-signing identity each time the bot
restarted, so every user's client saw the bot's verification break and had to
re-verify it. The freshly-minted recovery key was only ever logged, never kept,
so the identity couldn't be recovered either.

bootstrapCrossSigning now mints once, persists the recovery key to
DATA_DIR/cross_signing.json (0600), and on later starts reuses the stored
identity untouched. Two escape hatches, both normally unset:
CROSS_SIGNING_REGENERATE=1 forces exactly one remint (every user must then
re-verify), and CROSS_SIGNING_RECOVERY_KEY imports an identity created before
the bot persisted its own key.

Shared by the appservice and masdevice paths, which had drifted into two copies
of the same block.
2026-07-09 18:53:23 -07:00

57 lines
1.6 KiB
Go

package bot
import (
"os"
"path/filepath"
"testing"
)
func TestRecoveryKeyRoundTrip(t *testing.T) {
dir := t.TempDir()
if got := loadRecoveryKey(dir); got != "" {
t.Fatalf("fresh install returned %q, want empty", got)
}
const key = "EsTd o49d mMLt 3Uf9 Gjn9 x5fv YE9H wF6n aadC q2D8 Fv7j rQ4c"
saveRecoveryKey(dir, key)
if got := loadRecoveryKey(dir); got != key {
t.Fatalf("loadRecoveryKey() = %q, want %q", got, key)
}
}
// The key is crypto material sitting next to crypto.db; it must not be readable
// by other users on the host.
func TestRecoveryKeyFileIsPrivate(t *testing.T) {
dir := t.TempDir()
saveRecoveryKey(dir, "some-key")
info, err := os.Stat(filepath.Join(dir, crossSigningFile))
if err != nil {
t.Fatalf("stat: %v", err)
}
if perm := info.Mode().Perm(); perm != 0o600 {
t.Fatalf("recovery key file mode = %o, want 600", perm)
}
}
// A corrupt store must degrade to "no key" rather than panicking or returning
// garbage that would be fed to VerifyWithRecoveryKey.
func TestRecoveryKeyCorruptStore(t *testing.T) {
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, crossSigningFile), []byte("{not json"), 0o600); err != nil {
t.Fatalf("write: %v", err)
}
if got := loadRecoveryKey(dir); got != "" {
t.Fatalf("corrupt store returned %q, want empty", got)
}
}
// Overwriting an adopted key must not leave trailing bytes from the longer value.
func TestRecoveryKeyOverwrite(t *testing.T) {
dir := t.TempDir()
saveRecoveryKey(dir, "a-much-longer-original-recovery-key-value")
saveRecoveryKey(dir, "short")
if got := loadRecoveryKey(dir); got != "short" {
t.Fatalf("loadRecoveryKey() = %q, want %q", got, "short")
}
}