Repository navigation
feat(fw): answer /f/c rrcheck with a locally-forged access grant (#120) - #124
Merged
Merged
Conversation
The vacuum periodically POSTs /f/c to renew a local "rrcheck" access grant.
With no route for it, the request hit the generic catchall, which returns
{"ok": true, ...} -- not a valid grant. The firmware scores every check as
failed, tolerates it for a ~48h grace period, then flips its RPC AccessControl
state to denied, after which every command returns -10002 "rrcheck access
denied" while MQTT stays connected. Re-onboarding only resets the grace window.
Add an /f/c route that forges an accepted grant using the device RSA public key
already recovered during onboarding (the same key used to encrypt /region), so
the robot never has to reach the Roborock cloud:
{"k": b64(RSA-OAEP-SHA1(device_pubkey, aes128_key)),
"d": b64(AES-128-ECB(pkcs7({"f":"t","c":<country>,"t":<unix>})))}
The {k,d}/{f,c,t} contract and the absence of a response signature are verified
on S7-family firmware. If a newer model expects a different shape, the response
is treated as another failed check -- identical to the old catchall behaviour --
so this cannot regress a working device. When no device key is available we fall
back to the catchall.
- shared/bootstrap_crypto: expose BootstrapEncryptor.get_pubkey
- shared/context: add ServerContext.device_public_key
- https_server: register the fw_check route ahead of the catchall
- tests: decrypt the grant end-to-end, cover path variants and the no-key fallback
Merged
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.
Problem
Fixes #120. On (at least) the Qrevo Edge 2 (
a298), the vacuum works for ~48 h after onboarding and then every RPC comes back as-10002 "rrcheck access denied"while MQTT stays connected. Re-onboarding restores control for another ~48 h.Root cause
The firmware periodically calls
POST /f/cto renew a local rrcheck access grant (at onboarding, daily, and an extra call exactly ~48 h after onboarding — see @novinc's request-level timeline in the issue). We had no route for/f/c, so it fell through to the generic catchall, which returns:{"ok": true, "route": "/f/c"}That is not a valid grant. The firmware scores every check as failed, tolerates the failures during a ~48 h grace window, then flips its RPC
AccessControlstate to denied. That is the-10002. Re-onboarding only resets the grace window, which is exactly the observed behaviour. This matches the RRCheck logic reconstructed from the S7-familyAppProxy(48 h = the built-in172800s default).Fix
Add an
/f/croute that forges an accepted grant locally, so the robot never has to contact the Roborock cloud. The response contract (from S7 firmware) is:f == "t"is the accepted flag;tmust be within ±300 s of the device clock (the robot syncs its clock from our/time/now).kis unwrapped by the device with the same RSA private key it uses for/region, so the public key the server already recovers during onboarding is exactly what we need. No Roborock key is involved.That the device onboards through this server at all proves the recovered pubkey is correct and that the device uses this same RSA-OAEP-SHA1 wrapping for its bootstrap — the same wrapping
/f/c'skfield relies on.Why this is safe to enable by default
The
{k,d}/{f,c,t}shape and the absence of a response signature are verified only on S7 firmware. If a newer model (e.g.a298) expects a different shape or checks a signature, the device simply treats our response as another failed check — identical to the previous catchall behaviour — so this cannot regress a device that works today. If we have no recovered key for the device, we fall back to the catchall.Changes
shared/bootstrap_crypto: exposeBootstrapEncryptor.get_pubkeyshared/context: addServerContext.device_public_keyhttps_server: newfw_checkroute registered just ahead of the catchalltests/test_fw_check.py: play the device — build a keypair, register the modulus the way onboarding recovery does, drive the request throughresolve_route, decrypt the grant, and assertf == "t"; also covers path variants and the no-key fallbackTesting
pytest -q→ 291 passed locally (including the 3 new tests).Note for testers
This needs verification on a real
a298beyond the 48 h mark with the robot fully offline. If it holds past ~48 h without re-onboarding, the fix is confirmed.