feat: Support acting as a notary server #2314
Open
nex
wants to merge 25 commits from
nex/feat/notary-server into main
pull from: nex/feat/notary-server
merge into: continuwuation:main
continuwuation:main
continuwuation:renovate/sentry-rust-monorepo
continuwuation:renovate/p256-0.x
continuwuation:renovate/loole-0.x
continuwuation:renovate/rust-non-major
continuwuation:renovate/rust-zerover-patch-updates
continuwuation:renovate/cargo-bins-cargo-binstall-1.x
continuwuation:nex/feat/intuitive-extremity-tracking
continuwuation:nex/feat/lockfree-event-builder
continuwuation:nex/fix/local-event-current-state
continuwuation:ginger/client-credentials
continuwuation:jade/deadlocks
continuwuation:aranje/illegal-car-mods
continuwuation:nex/feat/admin-api
continuwuation:nex/refactor/ruma-state-res
continuwuation:jade/mass-rename
continuwuation:nex/feat/msc-fed-bidi-ping
continuwuation:nex/feat/centralise-remote-memberships
continuwuation:morgan/arm64-runner
continuwuation:renovate/argon2-0.x
continuwuation:next/meta/disable-ci-build-cache
continuwuation:fix/26.7.3
continuwuation:1235-add-arm-deb
continuwuation:nex/meta/release-schedule
continuwuation:nex/feat/pdu-versions
continuwuation:ginger/revert-direct-tls
continuwuation:nex/fix/federated-invite-revoke
continuwuation:nex/fix/plunger
continuwuation:jade/perf/gme-rebased
continuwuation:nex/perf/get-missing-events2
continuwuation:nex/backport/v0.5.11
continuwuation:nex/backport/v0.5.10-backport
continuwuation:nex/feat/msc4491-invite-reasons-in-room-creation
continuwuation:nex/feat/deprecated-room-versions
continuwuation:release/v0.5.9
continuwuation:nex/feat/enable-debug-log-release-builds
continuwuation:nex/feat/room-purging
continuwuation:nex/feat/room-shutdown
continuwuation:ginger/ruma-upstreaming
continuwuation:jade/tls-backends
continuwuation:ginger/email-fixes
continuwuation:jade/changelog-labels
continuwuation:nex/fix/v12-publishing
continuwuation:jade/build-info
continuwuation:jade/purge-sync-tokens
continuwuation:ginger/terms-and-conditions
continuwuation:ginger/remove-sliding-sync-proxy
continuwuation:nex/fix/pusher-association
continuwuation:ginger/email-support
continuwuation:jade/community-guidelines
continuwuation:nex/fix/federation-format
continuwuation:jade/git-deps-updates
continuwuation:jade/changelog-check
continuwuation:jade/rust-1-92
continuwuation:ginger/password-reset
continuwuation:nex/experiment/push-gateway-logs
continuwuation:ginger/msc3575-obliteration
continuwuation:nex/feat/block-busted-rooms
continuwuation:nex/fix/informative-startup-errs
continuwuation:ginger/no-left-room-initial-sync
continuwuation:jade/docker-entrypoint
continuwuation:jade/dehydrated-devices
continuwuation:ginger/complement-fixes
continuwuation:nex/fix/stale-destination-cache
continuwuation:nex/experiment/sync-mutex
continuwuation:tcpipuk/docker-docs
continuwuation:jade/snafu
continuwuation:jade/rand-update
continuwuation:nex/stateres-refactor
continuwuation:ginger/779-in-troubleshooting
continuwuation:jade/liveit-guide
continuwuation:jade/http3
continuwuation:nex/feat/admin-hide-empty-rooms
continuwuation:ginger/oobe
continuwuation:nex/fix/debian-thingy
continuwuation:jade/ldap-admin-check
continuwuation:nex/fix/remote-restricted-joins
continuwuation:nex/feat/msc4406-sender-ignored
continuwuation:jade/deadlock-detection
continuwuation:jade/get-started
continuwuation:jade/docs-guide
continuwuation:ginger/fix-local-invites
continuwuation:nex/fix/tpi
continuwuation:nex/feat/room-deletion
continuwuation:nex/feat/msc4322-media-redaction
continuwuation:ginger/stitched-order
continuwuation:ginger/deps/update-rspress
continuwuation:jade/admin-announce-improvements
continuwuation:ginger/xtask-improvements
continuwuation:jade/improve-admin-config-display
continuwuation:nex/fix/better-stateres-error-logs
continuwuation:jade/sender-timeouts
continuwuation:nex/feat/custom-v12-room-ids
continuwuation:ginger/update-metadata
continuwuation:nex/feat/admin-force-logout
continuwuation:tom/max-perf-docs
continuwuation:nex/fix/invalid-appservice-reg
continuwuation:nex/feat/antispam
continuwuation:nex/feat/account-locking
continuwuation:jade/logging-cleanup
continuwuation:jade/remove-legacy-appservice-auth
continuwuation:nex/fix/key-query
continuwuation:jade/update-prek
continuwuation:nex/fix/room-summaries
continuwuation:ginger/restrict-admin-commands
continuwuation:ginger/enable-console-by-default
continuwuation:jade/tag-fixes
continuwuation:jade/otlp
continuwuation:nex/meta/pull-req-template
continuwuation:nex/fix/fed-invite-compliance
continuwuation:nex/feat/build-commit
continuwuation:nex/feat/join-logging
continuwuation:jade/mailmap-updates
continuwuation:jade/hack-ci-tmp
continuwuation:jade/v12-stable
continuwuation:jade/relations
continuwuation:ginger/database-refactor
continuwuation:jade/fix-ldap-uiaa
continuwuation:nex/fix/validation
continuwuation:ginger/nuke-invalid-msc4133-fields-in-migration
continuwuation:ginger/downgrade-artifact-actions
continuwuation:oddlid/reload-fix
continuwuation:jade/fix-assert
continuwuation:ginger/sync-v3-cleanup
continuwuation:ginger/remove-absolute-action-urls
continuwuation:jade/website
continuwuation:nex/fix/backoff
continuwuation:ginger/fix-mdbook-for-0.5
continuwuation:ginger/no-docker-on-prs
continuwuation:backport/v0.5.0-rc.8-1
continuwuation:nex/fed-improvements
continuwuation:jade/rust-1.90
continuwuation:jade/mirror-dockerhub
continuwuation:jade/clippy-fixes
continuwuation:jade/fix-support
continuwuation:jade/clean-images
continuwuation:jade/wal-compression-type
continuwuation:jade/flake-clone
continuwuation:ginger/upload-rpms-on-schedule
continuwuation:nex/fix/incoming-fetch
continuwuation:nex/fix/upgrade
continuwuation:tom/ci-fedora-rpm
continuwuation:jade/ci-release-fix
continuwuation:jade/rocksdb-10-5
continuwuation:ginger/fix-msc4133-migration
continuwuation:ginger/migrate-busted-tz
continuwuation:hydra/public
continuwuation:nex/feat/manual-extremities
continuwuation:nex/feat/async-media
continuwuation:nex/feat/fast-joins-hack-do-not-use-DO-NOT-USE
continuwuation:nex/feat/better-logging
continuwuation:trigger-ci-so-latest-isnt-on-illegal-car-mods
continuwuation:nex/feat/pins-backfill
continuwuation:jade/tuwunel-2025-06-old
continuwuation:jade/ai-slop-db-docs
continuwuation:nex/fix-create-auth
continuwuation:jade/version-stats
continuwuation:jade/read-receipts
continuwuation:jade/rust-toolchain-no-targets
continuwuation:jade/logging-features
continuwuation:jade/syncv5-typing
continuwuation:jade/msc2815
continuwuation:morguldir/see-eye
continuwuation:jade/css-small-screen
continuwuation:nex/wip-751
continuwuation:tuwunel-rebase
continuwuation:test
continuwuation:oddlid/rename-admin-room-bot
continuwuation:strawberry/nix-ci-stuff
continuwuation:strawberry/valgrind
continuwuation:phonemain
continuwuation:strawberry/morgs-snake-sync-jason-main
continuwuation:newer-media-endpoints
continuwuation:folly-coroutines-async-io
continuwuation:federation-retry-timer-port
continuwuation:bad-attempt-at-extracting-homeserver-signing-key
continuwuation:room-deletion-attempt-do-not-use
Labels
Clear labels
Abandoned
This pull request appears to be abandoned by its author.
Blocked
This pull request or issue is currently blocked from being merged/closed
Bug
Something isn't working as intended
Changelog
Added
Changelog entry added
Changelog
Missing
No changelog when one is needed
Changelog
None
Changelog is unnecesary for this change
Cherry-picking
Commits picked from other conduit projects
Database
This requires or includes changes to the database
Dependencies
Something dependency related
Dependencies/Renovate
Automatic dependency upgrades by Renovate
Difficulty
Easy
Low difficulty to implement - touches few parts of the codebase, low complexity
Difficulty
Hard
High difficulty to implement - touches many parts of the codebase, high complexity
Difficulty
Medium
Medium difficulty to implement - touches more parts of the codebase, higher complexity
Documentation
Improvements or additions to documentation
Enhancement
New feature or request
Good first issue
Good for newcomers
Help wanted
Additional eyes and keyboards are required for this one
Inherited
Issues that have been inhereted from the project pre-fork
Matrix/Administration
Features pertaining to homeserver administration
Matrix/Appservices
Features pertaining to the appservice API
Matrix/Auth
Features pertaining to authentication
Matrix/Client
Features pertaining to client-to-server interactions
Matrix/Core
Issues relating to core matrix functionality, such as state resolution and PDU formats
Matrix/E2EE
Issues related to end to end encryption
Matrix/Federation
Features pertaining to server-to-server interactions
Matrix/Hydra
Issues related to room version 12 and related changes (temporary label)
Matrix/MSC
Features pertaining to unstable matrix features
Matrix/Media
Features pertaining to media interactions
Matrix/T&S
Changes or issues related to trust & safety tooling
Merge
This PR is ready to be merged
Merge/Manual
This PR should be manually merged
Merge/Squash
This PR should be squashed when it is merged
Meta
Related to housekeeping, maintenance, or other repo-meta.
Meta/CI
Issues related to CI changes
Meta/Packaging
Packaging
Priority
Blocking
This issue is blocking the next release
Priority
High
This issue is very important
Priority
Low
This issue is of a rather low priority
Security
This item is related to general security
Status
Confirmed
This issue has enough information and is confirmed
Status
Duplicate
This issue or pull request already exists
Status
Invalid
This issue doesn't seem right
Status
Needs Investigation
This issue needs further investigation
Support
Questions or support requests
bob the builder
pdu logic rewrite starring nexus nicholson
No labels
Abandoned
Blocked
Bug
Changelog
Added
Changelog
Missing
Changelog
None
Cherry-picking
Database
Dependencies
Dependencies/Renovate
Difficulty
Easy
Difficulty
Hard
Difficulty
Medium
Documentation
Enhancement
Good first issue
Help wanted
Inherited
Matrix/Administration
Matrix/Appservices
Matrix/Auth
Matrix/Client
Matrix/Core
Matrix/E2EE
Matrix/Federation
Matrix/Hydra
Matrix/MSC
Matrix/Media
Matrix/T&S
Merge
Merge/Manual
Merge/Squash
Meta
Meta/CI
Meta/Packaging
Priority
Blocking
Priority
High
Priority
Low
Security
Status
Confirmed
Status
Duplicate
Status
Invalid
Status
Needs Investigation
Support
bob the builder
Milestone
Clear milestone
No items
No milestone
Assignees
Clear assignees
No assignees
4 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set
Reference
continuwuation/continuwuity!2314
Loading…
Reference in a new issue
No description provided.
Delete branch "nex/feat/notary-server"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
This PR implements
POST /_matrix/key/v2/queryandGET /_matrix/key/v2/query/{server_name}, allowing continuwuity to act as a key notary server, and finally a trusted server..Closes: #784, #1208
Pull request checklist:
mainbranch, and the branch is named something other thanmain.myself, if applicable. This includes ensuring code compiles.
@ -90,0 +103,4 @@State(services): State<crate::State>,body: Ruma<get_remote_server_keys_batch::v2::Request>,) -> Result<get_remote_server_keys_batch::v2::Response> {let total_queried_keys = bodyyou're gonna lose your mind when you learn about
BTreeMap::len()Yeah I will because you made me realise I'm calculating bullshit lmao. It's supposed to sum the length of each value map but instead it's just counting the length of the top level map. I can't believe I reimplemented len()
@ -90,0 +119,4 @@let mut response = Vec::with_capacity(total_queried_keys);for (server_name, queries) in &body.server_keys {if queries.is_empty() {there is a fair bit of logic duplicated between this
ifand the code after it@ -90,0 +151,4 @@else {continue;};if ssk.valid_until_ts < minimum_valid_until_ts || ssk.valid_until_ts > in_one_week() {you might as well fix !2312 while you're at it (maybe by having
in_one_week()be "in one week and one hour")Didn't see that issue, that's actually deliberate because otherwise the range check will always return true if someone returns keys that last forever, which defeats the purpose of the 7-day limit
I'm not sure I follow your logic, why isn't that possible with the current seven-day limit as opposed to a seven-day-and-one-hour limit?
If you query a key on 2026-01-01, and receive a response that expires at 2026-02-01, accepting that response will result in a cache hit against that response for every lookup until February. This means a request to the origin won't be made until Feb, meaning the origin can't revoke any keys that may have been maliciously injected, or leaked, etc. All that clamping would do is prevent someone then sending an event that was created on 2026-02-01, at 2026-01-02 (since the 1st of Feb is more than 7 days in the future as of the 2nd of Jan). Now imagine someone's web server was hacked and someone published a key response for a decade...
I don't know where this "plus one hour" is coming from, the spec very clearly states exactly 7 days
"plus one hour" allows for clock skew, basically. intentionally lightly imprecise definition of 7 days
But whose skew? That accounts for a +1 timezone offset, but there's more timezones with >+1. People should just have the right time?
A) the issue isn't timezones, that's not what clock skee is, but latency and clocks that are off by a second or two
B) that over-caching sounds like a bug to me, you can make it so that you only treat the key as valid for the clamped length and then recheck that it's still on the origin.
I can, but I'm not going to. It's not over-caching, it's working as intended: if there's a locally stored key that is in-date and valid, use that. I don't see why you'd do anything less?
(discussion out-of-band decided this is out of scope for this PR but Jade wants to tackle 2312 herself, so the issue was re-opened and assigned)
also you need a news fragment :3
feat: Support acting as a notary serverto WIP: feat: Support acting as a notary server80dd7d507169b4d6ffa4WIP: feat: Support acting as a notary serverto feat: Support acting as a notary server912880fb6a8cb173ad08@ -90,0 +137,4 @@// )));}if total_queried_keys > MAX_KEYS_PER_QUERY {// We shouldn't really enforce this before MSC4456 either, but not doingTypo (MSC4556)
@ -90,0 +203,4 @@// First contact the origin (if we're allowed to)if server_keys.notary_may_contact_origin(&remote) {debug_info!("Asking remote directly for verify keys");if let Ok(res) = server_keys.origin_request(remote.clone(), start).awaitSame here, i don't think this persists the keys
No check to avoid self-federation in these too I don't think
That check is in the federation execute layer*
*or it should be, ginger told me a few hours ago that it's suspiciously absent now
@ -90,0 +209,4 @@let index = results.len();results.push(ssk);for key_id in res.verify_keys.keys().chain(res.old_verify_keys.keys()) {if queries.is_empty()This changes during iteration, so the condition may change and cause additional keys to be returned
@ -90,0 +213,4 @@|| queries.get(key_id).and_then(|c| c.minimum_valid_until_ts).is_none_or(|m| res.valid_until_ts >= m)Did you mean something like
.and_then(|c| c.minimum_valid_until_ts.is_none_or(|m| res.valid_until_ts >= m))? Otherwise this will return keys that weren't queried forUhh probably? I think clippy complained when I did that though 🤔
@ -90,0 +322,4 @@.notary_may_contact_origin(&body.server_name)&& let Ok(response) = services.server_keys.origin_request(body.server_name.clone(), min_valid_ts)This doesn't persist the keys, right? Might that be an issue?
uhhh I guess storing the keys could be an optimisation, but it's not an issue otherwise afaik
@ -90,0 +242,4 @@}}).ready_for_each(|signed_ssk| {let ssk = signed_ssk.deserialize().unwrap();Can't you just yield the ssk in the broad_filter_map above in a tuple to avoid pointless work here?
I didn't want to because
sign_sskmoves ssk, but I suppose deserializing is probably more expensive than cloning@ -90,0 +332,4 @@services.globals.server_name(),).await.map(|r| Ok(get_remote_server_keys::v2::Response::new(vec![r])))?;.map(|r| get_remote_server_keys::v2::Response::new(vec![r]))?Has to return a
Result<get_remote_server_keys::v2::Response>so the wrap is necessary. I'll just rid of the mapHmm, actually that's visually worse
I'll keep the current chain
@ -90,0 +271,4 @@continue;};if ssk.valid_until_ts < minimum_valid_until_ts || ssk.valid_until_ts > in_one_week() {do you need to check
expired_tstoo? (and in other checks)@ -29,6 +34,7 @@ pub struct Service {verify_keys: VerifyKeys,services: Services,db: Data,last_lookup: SyncRwLock<HashMap<OwnedServerName, Instant>>,Woo! Unbounded growth! Not really a huge issue tho lol
Yeahh I'm not too sure how to get around that without a cleanup task (which just defines a time window for the unbounded growth). I think the map will hold small enough data that it won't really matter
@ -101,3 +101,3 @@};let mut canonical_object = to_canonical_object(notary_signatures)?;Self::strip_extraneous_signatures(&mut canonical_object, &server_keys.server_name, &[strip_extraneous_signatures(&mut canonical_object, &server_keys.server_name, &[This seems like dead code anyway
This is very much live code? what do you mean?
You're making and stripping a canonical object but then not using it for anything as far as I can read?
Oh yeah, you're right, it was meant to be passed to
to_canonical_json_string_for_signingbelowlook at jade's comments 👍she said most of the stuff I would've said
@ -90,0 +119,4 @@}let total_queried_servers = body.server_keys.len();let total_queried_keys = bodyHmm actually is this counting right? Might be overcounting
Deliberately so, if a request is
{"server_keys": {"servername.example": {}}}, we'll count zero total keys, but it's more likely that we'll end up fetching at least 1 key response, so I figured counting each server name in the total query count makes sense8cb173ad08ad9ca737b2ad9ca737b2adc0e5d3b858c5875013298113c0a6You have the wrong MSC in one of your commit messages I think. And tests are failing.
@ -28,0 +59,4 @@State(services): State<crate::State>,body: Ruma<get_remote_server_keys_batch::v2::Request>,) -> Result<get_remote_server_keys_batch::v2::Response> {query_inner(&services, &body.server_keys, false)false->true?@ -28,0 +99,7 @@// Count how many keys are being requested.// Each server incurs a cost of at least one, and then the count of each key// being queried per-server.let total_queried_servers = body.len();let total_queried_keys = body.values().fold(body.len(), |acc, q| acc.saturating_add(q.len()));Maybe
would be better? It'll be less of an overcount.
@ -28,0 +122,4 @@total_queried_servers > MAX_SERVERS_PER_QUERY || total_queried_keys > MAX_KEYS_PER_QUERY;debug_info!("Fetching {total_queried_keys} keys across {} servers", body.len());let mut futs: FuturesUnordered<_> = FuturesUnordered::new();Probably want something to limit concurrency here
@ -83,0 +273,4 @@let minimum_valid_until_ts = criteria.clone().minimum_valid_until_ts.unwrap_or_else(|| MilliSecondsSinceUnixEpoch(uint!(0)));Doesn't this mean that the default could provide expired keys? But spec wants current time by default.
Perhaps:
@ -0,0 +23,4 @@rate_limited: true,authentication: ServerSignatures,history: {unstable("org.continuwuity.msc4553") => "/_matrix/key/unstable/org.continuwuity.msc4556/query",4556?@ -196,0 +260,4 @@// We need to verify that this response is still trusted based on// the current configuration. It's possible we got this response// from a notary who we no longer trust.if resp.valid_until_ts > in_one_week() || resp.valid_until_ts < now {Remind me to come back to this condition later.
@ -100,13 +100,12 @@ impl super::Service {)));};let mut canonical_object = to_canonical_object(notary_signatures)?;Did you mean
let mut canonical_object = to_canonical_object(server_keys)?;(this is probably the cause of the test fail)
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.