fix(trust): also user-sign other users' master keys in apply_trust_policy #7

Merged
jmz merged 1 commit from kitty/sync-wedge-resilience-fixes into main 2026-05-02 16:31:38 +00:00 AGit
Contributor

matrix-sdk-crypto's outbound Megolm-key-share gate requires that the
sender has user-signed the recipient's cross-signing master key —
device-level local verification alone is not enough. Without this,
two bridges can have:

  • both bot users with cross-signing identities published,
  • both bridges with each other's devices in their local device tables,
  • both bridges running TOFU device-verification (apply_trust_policy
    calling device.verify()),
  • automatic-room-key-forwarding feature enabled,

…and Megolm session keys still won't flow between them. Empirically
verified across both bridge instances on 2026-05-02: even after
bootstrapping cross-signing for both @claudius-felinus and
@claudius-maximus, post-bootstrap test messages from one bot to the
other still arrived as UnableToDecrypt.

Fix: in apply_trust_policy (Tofu/All branch), for every other room
member, in addition to the existing device.verify() loop, also call
UserIdentity::verify() once on the user's identity if it's
unverified. UserIdentity::verify() performs the user-signing —
generates a signature with our user-signing key against the other
user's master key, uploads it via /keys/signatures/upload. After
both sides do this on next sync, matrix-sdk-crypto's outbound-share
check sees mutual user-signing and lets the Megolm session key flow.

Operational caveat: this fix must be deployed on BOTH bridges
participating in the room. One-sided user-signing only establishes
trust in one direction. The setup-once cost: a single sync cycle
after deploy.

Defensive: no-ops if the other user has no cross-signing identity
yet (e.g. they haven't run bootstrap-cross-signing), and no-ops if
we've already user-signed them (matrix-sdk-crypto's is_verified()
check). All errors are logged at warn level and skipped — one bad
identity can't break the whole policy run.

Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

matrix-sdk-crypto's outbound Megolm-key-share gate requires that the sender has user-signed the recipient's cross-signing master key — device-level local verification alone is not enough. Without this, two bridges can have: * both bot users with cross-signing identities published, * both bridges with each other's devices in their local device tables, * both bridges running TOFU device-verification (apply_trust_policy calling `device.verify()`), * `automatic-room-key-forwarding` feature enabled, …and Megolm session keys still won't flow between them. Empirically verified across both bridge instances on 2026-05-02: even after bootstrapping cross-signing for both `@claudius-felinus` and `@claudius-maximus`, post-bootstrap test messages from one bot to the other still arrived as UnableToDecrypt. Fix: in `apply_trust_policy` (Tofu/All branch), for every other room member, in addition to the existing `device.verify()` loop, also call `UserIdentity::verify()` once on the user's identity if it's unverified. `UserIdentity::verify()` performs the user-signing — generates a signature with our user-signing key against the other user's master key, uploads it via `/keys/signatures/upload`. After both sides do this on next sync, matrix-sdk-crypto's outbound-share check sees mutual user-signing and lets the Megolm session key flow. Operational caveat: this fix must be deployed on BOTH bridges participating in the room. One-sided user-signing only establishes trust in one direction. The setup-once cost: a single sync cycle after deploy. Defensive: no-ops if the other user has no cross-signing identity yet (e.g. they haven't run `bootstrap-cross-signing`), and no-ops if we've already user-signed them (matrix-sdk-crypto's `is_verified()` check). All errors are logged at warn level and skipped — one bad identity can't break the whole policy run. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
matrix-sdk-crypto's outbound Megolm-key-share gate requires that the
sender has user-signed the recipient's cross-signing master key —
device-level local verification alone is not enough. Without this,
two bridges can have:

* both bot users with cross-signing identities published,
* both bridges with each other's devices in their local device tables,
* both bridges running TOFU device-verification (apply_trust_policy
  calling `device.verify()`),
* `automatic-room-key-forwarding` feature enabled,

…and Megolm session keys still won't flow between them. Empirically
verified across both bridge instances on 2026-05-02: even after
bootstrapping cross-signing for both `@claudius-felinus` and
`@claudius-maximus`, post-bootstrap test messages from one bot to the
other still arrived as UnableToDecrypt.

Fix: in `apply_trust_policy` (Tofu/All branch), for every other room
member, in addition to the existing `device.verify()` loop, also call
`UserIdentity::verify()` once on the user's identity if it's
unverified. `UserIdentity::verify()` performs the user-signing —
generates a signature with our user-signing key against the other
user's master key, uploads it via `/keys/signatures/upload`. After
both sides do this on next sync, matrix-sdk-crypto's outbound-share
check sees mutual user-signing and lets the Megolm session key flow.

Operational caveat: this fix must be deployed on BOTH bridges
participating in the room. One-sided user-signing only establishes
trust in one direction. The setup-once cost: a single sync cycle
after deploy.

Defensive: no-ops if the other user has no cross-signing identity
yet (e.g. they haven't run `bootstrap-cross-signing`), and no-ops if
we've already user-signed them (matrix-sdk-crypto's `is_verified()`
check). All errors are logged at warn level and skipped — one bad
identity can't break the whole policy run.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jmz merged commit 3800ebf93e into main 2026-05-02 16:31:38 +00:00
Sign in to join this conversation.
No description provided.