feat(bluetooth): Add trusted device pairing and encrypted sync - #3223
Conversation
- Pairing now required before a watch can sync loyalty cards over Bluetooth. - Adds encryption, trusted/blocked device management, and user-facing notifications for allow/block decisions.
TheLastProject
left a comment
There was a problem hiding this comment.
I haven't tested this code yet, just looking through it. I'm definitely struggling at some parts. I understand encrypting adds complexity, but the general communication flow has become hard to understand so I do think we'll probably have to change this a bit.
Perhaps a flow more like this would be easier to maintain?
Each onResume:
- Watch calls /VERSIONS (always cleartext) to see which API version is supported
- Watch call /AUTH with a public key, if the MAC + pubkey combination is known by the Android app, return OK with a pubkey the watch should use. Otherwise return UNAUTHORIZED (on Unauthorized, show an alert on the watch)
- If OK, call the card sync, etc.
So, basically, the old flow with just 1 additional step. The decryption is just hooked up in the regular flow so the regular loop doesn't have to encrypt/decrypt, it just sees the decrypted data.
But maybe I'm missing something :)
No official release has been done yet -> no need to bump the protocol version. Addresses: CatimaLoyalty#3223 (comment)
The Bluetooth pairing authorization notification was using `NotificationCompat.CATEGORY_CALL`, which is semantically meant for incoming phone/video calls. `CATEGORY_STATUS` is a better fit for a device connection request since no call is involved - even though it's still not the semantically perferct choice.
…IBLE The watch cannot know which side of a protocol mismatch is older, so PHONE_OUTDATED and WATCH_OUTDATED are replaced by a neutral VERSION_INCOMPATIBLE status. The UI now tells the user to update both apps, and the watch stops retrying sync when versions are incompatible. Addresses: CatimaLoyalty#3223 (comment)
The Wear OS device settings previously mixed trusted and blocked devices in a single "Paired Wear devices" dialog. Add a separate "Blocked Wear devices" preference and dialog so each list is shown and managed independently. - Remove from the allowed list calls untrustDevice(). - Remove from the blocked list calls unblockDevice(). - Update summaries and dialog refresh logic for both preferences. - Ensure trustDevice() also removes the address from the blocked set so a device cannot be trusted and blocked simultaneously. Addresses: CatimaLoyalty#3223 (comment)
…ecks Mirror the existing Bluetooth permission helper pattern by exposing isPostNotificationsRequired() and making isPostNotificationsGranted() use it. Update WearSyncServiceManager to use the helper for both permissions. Addresses: CatimaLoyalty#3223 (comment)
Replace the AES-256-GCM application-layer encryption with a simpler shared-secret token scheme. Bluetooth link-layer encryption already protects the transport, so the phone-watch AES key exchange was redundant. The watch now generates and persists a 256-bit token, sends TOKEN:<token> before each V1 command, and the server authorizes or denies requests based on the stored token. New devices and token mismatches surface pairing notifications. Addresses: CatimaLoyalty#3223 (comment)
…ear OS Different companion apps should display different notification titles. Addresses: CatimaLoyalty#3223 (comment)
TheLastProject
left a comment
There was a problem hiding this comment.
This feels a lot better! I'm quite happy with this. I added a few small notes to #3218 for some things I feel should be fixed but can be done outside of this MR to not scope creep to much.
Just have a few last things I think should be part of this MR :)
|
Thank you for your patience by the way, I know I can be a bit of a perfectionist at times but I just want to make sure the users will have the best experience possible and that I can have an easy time maintaining things in the future :) |
Co-authored-by: Sylvia van Os <sylvia@hackerchick.me>
Make it clear that tapping an entry in the allowed or blocked Wear device list opens the removal confirmation dialog. Closes: CatimaLoyalty#3223 (comment)
Convert settings_wear_sync_devices_summary_count and settings_wear_sync_blocked_devices_summary_count from <string> to <plurals>, matching the existing groupCardCountWithArchived pattern. Update SettingsActivity to use resources.getQuantityString() for the correct singular/plural form. Closes: CatimaLoyalty#3223 (comment)
Swap the Wear Bluetooth wire format from token-then-command to command-then-token. The server now inspects the version prefix on the command before reading or validating the token, making it possible to select a version-specific token parser in the future. Closes: CatimaLoyalty#3223 (comment)
We need a custom dialog layout for Wear OS sync allowed and blocked device lists, which can support both a text item and a list.
TheLastProject
left a comment
There was a problem hiding this comment.
This seems fine, one last nitpick before I can hit merge :)
Co-authored-by: Sylvia van Os <sylvia@hackerchick.me>
Catch IOException during read loop and log normal disconnect Closes: CatimaLoyalty#3223 (comment)
|
One last paddingStart/End left 🙈 |
Co-authored-by: Sylvia van Os <sylvia@hackerchick.me>
|
Thanks! :) |
Co-authored-by: Sylvia van Os <sylvia@hackerchick.me>
Bluetooth Wear Sync Security Implementation
Current State
protect.card_locker.wearos.BluetoothServerServiceis a Classic Bluetooth RFCOMM server (not BLE). It advertises the UUIDe5b4f020-3a7e-4b6d-9f2c-1a8c5d3e7f90with the nameCatimaWear.me.hackerchick.catima.wear.BluetoothCardClient) scans the phone's bonded devices and tries to connect to each one using the same UUID.listenUsingRfcommWithServiceRecord/createRfcommSocketToServiceRecord), which means the Bluetooth link is encrypted and authenticated by the OS pairing/link key.Threat Model
/V1/CARDS_REQUEST_PAGE/<n>) until the full set is exfiltrated.Proposed Mitigations
1. App-level pairing confirmation
AUTH_REQUIREDand show a notification/activity on the phone:<device name>to access your cards?"2. Paired devices list in Settings
3. Application-layer encryption
/V2/AUTHhandshake. The key exchange happens over the already authenticated and encrypted RFCOMM link.<base64(nonce)>:<base64(ciphertext+tag)>\n.Limitations and Caveats