While checking a post for this build log in October, we read our own collar firmware again and found a hole: the collar obeyed any phone in Bluetooth range. Nobody needed our app or an account. A stranger nearby could have cleared the collar's stored activity, changed its settings or started a firmware update. This post covers how we closed it in firmware 0.10.0.
What was wrong
The collar exposes a few Bluetooth characteristics: one streams activity, one reports battery and status, one takes commands. The command channel accepted every write without asking who sent it. Three things followed from that:
- Anyone could send commands. Clear data, change config, start an update.
- Anyone could start an update. The collar would restart into update mode for any phone that asked.
- Anyone could crowd out the owner. The collar holds two phone connections at once, so two strangers' phones could keep yours out.
The collar is not on sale yet, so the only collars exposed were our own prototypes. We fixed it before any collar reaches a customer, and waited until the fix was running on our collars before publishing this.
Why not normal Bluetooth pairing
The obvious fix is the pairing your phone does with headphones. On a device with no screen and no keypad, that pairing uses a mode called "Just Works", which encrypts the link but proves nothing about who is on the other end. Anyone can pair. It also needs a way to say "pair now", and our only button is reset. And it would have broken a feature we like: a second family phone that shares the dog joins the collar automatically, with no setup.
What we built: a key per collar
Each collar now has its own 256-bit secret key.
- Claiming. The first phone to connect to a new collar gives it a fresh random key. After that the collar only accepts a new key while it is on the charger, which doubles as a factory reset for a lost phone or a resale.
- Sharing. The phone saves the key to our backend, encrypted at rest, where only the dog's owner and the people it is shared with can read it. A family phone fetches it and connects with no extra step.
- Proving it, both ways. On every connection the collar sends a random challenge, and the phone must answer with a code computed from the key (HMAC-SHA256). The collar answers the phone's own challenge the same way, so the phone knows it is talking to the real collar, not an impostor collecting data.
- Signing every command. Each command carries a counter and a short code tied to that session. A recorded command cannot be replayed, and nobody can slip one into an existing connection.
- Dropping strangers. A connection that has not proved itself within 15 seconds is closed, and three wrong answers close it at once. That frees the slot a stranger would otherwise sit on.
- Revoking. When the owner stops sharing the dog, or someone leaves, the backend flags the key, and the owner's phone replaces it on its next connection. A phone that has lost access also loses the collar.
Four implementations, one set of test vectors
The protocol lives in four places: the collar firmware (C), the iPhone app (Swift), the Android app (Kotlin) and our bench tool (Python). A one-byte mismatch between any two means a collar that silently refuses its owner. So we wrote the spec first, generated a fixed set of test vectors from it, and made every implementation reproduce them before it touched a real collar. The bench tool also runs the attacks: an unsigned command, a wrong key, a replayed command and a forged one. All four must fail, and the stranger's connection must drop.
The key handshake costs the battery almost nothing: a few hashes per connection. Waking the collar's main loop for it uses a task notification instead of polling, so a phone gets an answer straight away even while the collar is asleep.
What is still open
- The first claim. The key travels over the air once, when a new collar is claimed at home. Someone listening at exactly that moment could catch it. Production collars will come with a key written at the factory and a QR code on the box, so nothing secret goes over the air.
- Signed firmware. Only an authenticated phone can start an update now. Production collars will also ship with an update loader that only accepts images we signed, and with the chip's debug port locked.
- Tracking. The collar still advertises a fixed name, so someone could note when it passes. Rotating Bluetooth addresses are next.
If you are building something similar
- Assume anyone in radio range can write to every characteristic you expose, because they can.
- "Just Works" pairing encrypts the link but authenticates nobody.
- Write the spec and the test vectors before the code, and make every implementation pass them.
- Test the attacks, not only the happy path.