Verify chain-delegated certs against the xid registry - #434
Merged
Conversation
cert_signers ["chain"] used to accept any cert_user_id without checking
cert_sign, so a user directory named by a raw auth address could carry a
forged chain identity. verify_cert now recovers the keccak signer from
cert_sign over "{auth_address}#xid/{name}", resolves the name's linked
identities through the chain, and requires the recovered address to be
one of them. Revoked identities keep their pre-revocation content: a
modified stamp at or past revoked_at_time plus the 60 second grace
window is rejected, matching the archived Python XidResolver behavior.
VerifyContext gains resolve_xid_identities, and the pre-resolved xid
maps now carry full identity records (address, active, revoked_at_time)
instead of bare address lists. Resolution failures stay fail closed: an
unresolved name makes the cert check reject, the same way an unresolved
dot-form dir signer already failed signature counting. Chain lookups
reuse the existing epix-chain signer cache, which now stores the
identity records.
Callers that verify a child manifest also resolve the name from the
child's own cert_user_id (update receiver, child commit, sign path,
stored-manifest walks, promotion recovery, rehome snapshots), since a
raw-address dir's cert name is not among the parent's dir names.
Also adds tests proving the classic ban path: permissions[user] = false
strips the self-signer for bans keyed by auth address or cert_user_id,
permission_rules null zeroes the write quota, and rule-granted admin
signers still verify in a banned user's directory. No fixes were
needed; the checks were already enforced.
Browser extensions (wallet content scripts) post into every frame, including null and string payloads. onMessageInner read message.cmd unconditionally, so a null message threw a TypeError on every such event. Ignore anything that is not an object with a cmd.
Each test repeated the same fixture, context, verify, and assert block, which pushed duplication on new code past the quality gate. Those blocks now go through shared helpers: new_identity, loaded_parent, user_ctx, ban_parent, user_content, and verify_chain_cert. The assert helper matches the rejection reason instead of formatting the error into the panic message. The fixtures are built from signing keys, so code scanning read those messages as logging key-derived data. Same coverage: 62 tests pass, and clippy reports the same six pre-existing warnings as before.
Five contexts implemented resolve_xid with the same body: look the name up in the pre-resolved map, map the identity records to addresses, fall back to empty. The trait can do that itself, since resolve_xid_identities already carries the records. Each context now implements resolve_xid_identities only, which removes the repeated blocks that kept duplication on new code above the quality gate. Behaviour is unchanged: an unresolved name still yields an empty signer list, so chain-cert and dot-form signer checks still fail closed. The one remaining override is the file-overlay wrapper, which delegates to its base rather than reading a map.
sign_user_content_owned picked up the cert hint inline, which pushed its cognitive complexity past the analyzer limit. The hint is a self contained step: read the stored content, and add the user's own cert id when the node signs as them. Moving it out leaves the signing path reading as a sequence of steps and keeps behaviour identical.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Closes the chain-cert verification TODO at
crates/epix-content/src/verify.rs:451and proves the user-ban path with tests.Why
When a xite's
user_contents.cert_signersnames["chain"],verify_certreturnedOk(true)without checkingcert_signat all. A user directory named by a raw auth address could therefore carry anycert_user_idit liked. For any xite that treats a chain identity as a paid, scarce thing (the EpixSites directory does: it gives vote weight only to chain-registered names), that gap means free identities.What it does
verify_chain_cert(verify.rs:533) ports the archived PythonXidResolverPlugin._verifyXidCert:cert_signover{auth_address}#xid/{name}. For a raw-address dir the recovered address must equal the dir address; for an xID-named dir every linked identity is tried as a candidate, matching the Python loop.VerifyContext::resolve_xid_identities.REVOCATION_GRACE_PERIOD_SECS): content modified at or afterrevoked_at_time + 60sis rejected, earlier content stays valid, and a revoked identity with no timestamp is rejected outright.Legacy provider certs (static issuer address,
verify_cert_sign) are unchanged.Failure semantics: fail closed, matching the existing dir-signer pattern. An unresolved name rejects the cert, the same way an unresolved dot-form dir signer already yields "Valid signs: 0/1". Caching reuses the existing
epix_chain::xid_signerslayer only (30 minute positive TTL, failures never cached), so a transient RPC failure causes temporary rejection and retry, never acceptance. No new cache was added.Plumbing change: the pre-resolved xid maps now carry full identity records (address, active, revoked_at_time) instead of bare address lists, so
XidMapand its call sites in epix-xite, epix-ui, and epix-node are retyped.epix-chaingainsresolve_identities_checked;resolve/resolve_checkedare unchanged on top of it. Because a raw-address dir's cert name is not among the parent's dir names, every map-building site also resolveschain_cert_xid_name.Ban path
user_contents.permissions["<id>"] = falsewas believed unexercised on the Rust node since the Python era. Four new tests prove it works: a banned user's signed content fails verification network wide, the ban works keyed by raw auth address and bycert_user_id, a nullpermission_rulesvalue zeroes the write quota, and a rule-granted admin can still sign in a banned directory so moderation tombstones keep working. All passed as written, so no fix was needed. Key precedence matches Python'sgetUserContentRules.Wrapper null message guard
Separate one line fix in
ui/media/all.js. Browser wallet extensions post into every frame, including null payloads.onMessageInnerreadmessage.cmdunconditionally and threw a TypeError on each one. It now ignores anything that is not an object with acmd.Tests
cargo test -p epix-content: 62 passed, 0 failed (11 new).cargo test -p epix-chain -p epix-xite: all passed.cargo test -p epix-ui -p epix-node: full suites, no failures.cargo check --testsclean for every touched crate plus epix-server.Deliberately left out
bob.epixcarrying a replayed validalice@xid.epixcert passes the cert check, though the content must still be signed by bob's identity. This matches the Python reference exactly. Tightening it is a semantics change and is flagged here rather than made.content.modifiedis newer than the cache entry). Verification is synchronous and no new caching machinery was added, so revocation data can be up to the existing 30 minute signer-cache TTL stale, bounded further by the 60 second grace design.resolve_xiddir-signer semantics are unchanged: it still returns all linked identities including revoked ones. That is pre-existing behavior outside the cert scope.