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.
This commit is contained in:
prosolis
2026-07-09 18:53:23 -07:00
parent ba7b20dfe5
commit 1adcd05dc7
5 changed files with 212 additions and 30 deletions

View File

@@ -131,19 +131,7 @@ func NewClient(cfg Config) (*mautrix.Client, error) {
// verified to users who trust its master key). Not required for E2EE to
// function; under MAS the key upload may be refused, which we ignore.
mach := ch.Machine()
if recoveryKey, _, err := mach.GenerateAndUploadCrossSigningKeys(ctx, func(ui *mautrix.RespUserInteractive) interface{} {
return map[string]interface{}{"session": ui.Session}
}, ""); err != nil {
slog.Warn("cross-signing: key upload skipped (may already exist or need UIA)", "err", err)
} else {
slog.Info("cross-signing: keys uploaded", "recovery_key", recoveryKey)
}
if err := mach.SignOwnDevice(ctx, mach.OwnIdentity()); err != nil {
slog.Warn("cross-signing: sign own device failed", "err", err)
}
if err := mach.SignOwnMasterKey(ctx); err != nil {
slog.Warn("cross-signing: sign master key failed", "err", err)
}
bootstrapCrossSigning(ctx, mach, cfg.DataDir)
// ---- Background token refresher ----
// Refresh ~60s before expiry and push the new token into the live client so