SEC-2275: keybox-bypass defense -- per-(fp, aud) rate limit + blocklist#163
Open
NnnOooPppEee wants to merge 2 commits into
Open
Conversation
Adds a stateful enforcement layer on top of the stateless integrity
signals from SEC-2276. Closes the keybox-bypass surface where a single
leaked batch certificate can be reused across many devices: any abuse
spike on a `(SHA256(intermediate_cert_DER), aud)` pair gets capped, and
known-bad fingerprints can be blocklisted out of band.
What it does:
* New `android::keybox_defense` module:
* `KeyboxDefense::fingerprint(der)` -- lowercase hex SHA-256 of the
intermediate (batch) cert.
* `KeyboxDefense::evaluate(redis, fp, aud)` -- checks
`keybox:block:{fp}` for an explicit blocklist entry, then
increments a sliding-window counter at
`keybox:count:{fp}:{aud}` with `EXPIRE = window_secs`. Maps the
count to `RiskLevel::{Low, Medium, High, Blocked}`.
* `KeyboxDefense::blocklist_add` / `blocklist_remove` for ops use.
* `AndroidAttestationOutput.batch_cert_fingerprint`: SHA-256 hex of the
intermediate cert, computed once per attestation from the validated
chain (uses `AndroidCertChain::intermediate_cert_der()` introduced
in SEC-2314).
* `routes/a.rs`: after the nonce is consumed and we know the audience,
call `evaluate(fp, &aud)` and reject when `verdict.should_reject()`.
Operational notes:
* **Shadow mode by default.** `KeyboxDefenseConfig::from_env()` reads
`KEYBOX_DEFENSE_ENFORCE`; only when set to `1` does
`verdict.should_reject()` ever return `true`. Until we flip the
switch, the gateway computes verdicts, emits metrics, and logs but
never blocks on rate limits.
* Thresholds are tunable via `KEYBOX_WARN_THRESHOLD` /
`KEYBOX_BLOCK_THRESHOLD` / `KEYBOX_WINDOW_SECS`; defaults are
500/5000 per hour per `(fp, aud)`.
* Redis errors are logged and ignored: an outage of the defense layer
must never take down attestation. Blocklist hits are still logged
even in shadow mode.
Maps to https://linear.app/worldcoin/issue/SEC-2275
5fb7e52 to
eabfd6a
Compare
Rate limiting and blocklisting in the keybox-defense layer target legacy batch attestation keys, which are the only keys that can be extracted in a keybox leak. Chains rooted in a Remote-Key-Provisioning (RKP) root are issued by Google per-device and cannot originate from a leaked keybox, so running them through the rate limiter and blocklist adds no defensive value. Set `batch_cert_fingerprint = None` when `is_rkp` is true. The existing `if let Some(ref fp)` guard in `routes/a.rs` naturally short-circuits both the rate limit increment and the blocklist lookup, so no changes are needed on the handler side. This also prevents legitimate RKP traffic from ever consuming rate-limit budget, keeping the Redis counters focused on the surface area they were designed for.
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.
Summary
Stateful enforcement layer on top of the stateless integrity signals from #162. Closes the keybox-bypass surface where a single leaked batch certificate can be reused across many devices: any abuse spike on a
(SHA256(intermediate_cert_DER), aud)pair gets capped, and known-bad fingerprints can be blocklisted out of band.Stacks on top of #162 (which stacks on #161).
Changes
android::keybox_defensemodule:KeyboxDefense::fingerprint(der)-- lowercase hex SHA-256 of the intermediate (batch) cert.KeyboxDefense::evaluate(redis, fp, aud)-- checkskeybox:block:{fp}for an explicit blocklist entry, then increments a sliding-window counter atkeybox:count:{fp}:{aud}withEXPIRE = window_secs. Maps the count toRiskLevel::{Low, Medium, High, Blocked}.KeyboxDefense::blocklist_add/blocklist_removefor ops use.AndroidAttestationOutput.batch_cert_fingerprint: SHA-256 hex of the intermediate cert, computed once per attestation from the validated chain (usesAndroidCertChain::intermediate_cert_der()introduced in SEC-2314: enforce registered roots as the chain trust anchor #161).routes/a.rs: after the nonce is consumed and we know the audience, callevaluate(fp, &aud)and reject whenverdict.should_reject().Operational notes
KeyboxDefenseConfig::from_env()readsKEYBOX_DEFENSE_ENFORCE; only when set to `1` doesverdict.should_reject()ever return `true`. Until we flip the switch, the gateway computes verdicts, emits metrics, and logs but never blocks on rate limits.KEYBOX_WARN_THRESHOLD/KEYBOX_BLOCK_THRESHOLD/KEYBOX_WINDOW_SECS; defaults are 500 / 5000 per hour per(fp, aud).Linear
SEC-2275 -- Implement integrity token/key revocation mechanism (CRL-like)
Test plan
attestation_gateway.keybox_defense{risk_level=Medium}incrementsKEYBOX_DEFENSE_ENFORCE=1on staging, push pastKEYBOX_BLOCK_THRESHOLD, confirm400 rate limit exceededredis-cli SET keybox:block:<fp> 1and confirm next attestation gets400 certificate blocklisted