seemoo-lab / openhaystack

Build your own 'AirTags' 🏷 today! Framework for tracking personal Bluetooth devices via Apple's massive Find My network.
https://owlink.org
GNU Affero General Public License v3.0
8.49k stars 466 forks source link

PuckJS Firmware #59

Open bettse opened 3 years ago

bettse commented 3 years ago

I ported the firmware to the PuckJS (https://www.puck-js.com/)


function AirTag(pk) {
  var ad = [
    0x1e, /* Length (30) */
    0xff, /* Manufacturer Specific Data (type 0xff) */
    0x4c, 0x00, /* Company ID (Apple) */
    0x12, 0x19, /* Offline Finding type and length */
    0x00, /* State */
    pk[6], pk[7], pk[8], pk[9], pk[10], pk[11], pk[12], pk[13], 
    pk[14], pk[15], pk[16], pk[17], pk[18], pk[19], pk[20], pk[21], 
    pk[22], pk[23], pk[24], pk[25], pk[26], pk[27],
    0x00, /* First two bits */
    0x00, /* Hint (0x00) */
  ];
  ad[29] = pk[0] >> 6;

  const addr = [
    pk[0], pk[1], pk[2], pk[3], pk[4], pk[5]
  ];

  addr[0] |= 0xC0;

  const s = addr
    .map(x => x.toString(0x10).padStart(2, '0'))
    .join(':')

  return {
    Ad: [].slice.call(ad),
    Address: s + " random",
  };
};

const public_key = [
  0xbf, 0x44, 0x1f, 0xa2, 0x44, 0x40, 0x70, 0x65, 0x24, 0xe4, 0x0f, 0x5b,
  0x4b, 0xc0, 0xd1, 0x8e, 0x40, 0x98, 0xe4, 0x6f, 0xa6, 0xd5, 0xd4, 0x14,
  0x0a, 0xfe, 0xa8, 0x05
];

const airtag = AirTag(public_key);

NRF.setAddress(airtag.Address);

NRF.setAdvertising([
  airtag.Ad,
  {} // this will add a 'normal' advertising packet showing name/etc  
], {interval:100});
gfwilliams commented 3 years ago

This is great! Just to add that it's possible to craft a link of the form https://espruino.com/ide?upload&code=... which will automatically upload the firmware to a Puck, so if there's interest we could add this to OpenHaystack really easily?

LowEnergyGenerator commented 1 year ago

This is great! Just to add that it's possible to craft a link of the form https://espruino.com/ide?upload&code=... which will automatically upload the firmware to a Puck, so if there's interest we could add this to OpenHaystack really easily?

Did this ever happen? My Puck.js is coming in a few days.

gfwilliams commented 1 year ago

As far as I know nothing got added past the code here (but uploading this code is itself pretty easy).

Even if this didn't go into openhaystack, it could be added to https://espruino.github.io/EspruinoApps/ or https://banglejs.com/apps/ pretty easily so that uploading could be just a matter of copying/pasting the public key in.

I don't have an iPhone or even a remotely recent Mac, but @LowEnergyGenerator if you're in a position to test then I can add something to https://espruino.github.io/EspruinoApps/ that you can try out.

ak2k commented 1 year ago

As a report, I successfully used the code above (deployed using the Chrome App interface) with a puck.js.

Apologies, I misspoke. Some time had passed so I checked more carefully and it seems that I actually used this modification which the author states was based on the code above.

/*
OPENHAYSTACK
A framework for tracking personal Bluetooth devices via Apple's massive Find My network
2021-07-15 SK
*/
print("OpenHaystack Beacon\n===================\n");

const key_b64 = "Fy5SNl1ehMqUe49PJ4sxgrvTJLj/hlNjVYy5qg=="; // replace with your public (advertisement) key

function b64ToArray(str) {
    let bstr = atob(str);
    let arr = [];
    for (let i = 0; i < bstr.length; i++) {
        arr[i] = bstr.charCodeAt(i);
    }
    return arr;
}

const key = b64ToArray(key_b64); // public key

print("Public key:");
print("- base64:\n" + key_b64);
print("- hexadecimal:\n" + key.map(x => x.toString(16).padStart(2, '0')).join(' '));

const mac = [ key[0] | 0b11000000, key[1], key[2], key[3], key[4], key[5] ].map(x => x.toString(16).padStart(2, '0')).join(':'); // mac address

print("\nMAC address:\n" + mac);

const adv = [ 0x1e, 0xff, 0x4c, 0x00, 0x12, 0x19, 0x00, key[6], key[7], key[8], key[9], key[10], key[11], key[12], key[13], key[14], key[15], key[16], key[17], key[18], key[19], key[20], key[21], key[22], key[23], key[24], key[25], key[26], key[27], key[0] >> 6, 0x00 ]; // advertising packet

print("\nAdvertising packet:\n" + adv.map(x => x.toString(16).padStart(2, '0')).join(' ') + "\n");

NRF.setAddress(mac);
NRF.setAdvertising(adv, {interval:5000});

print("\nTo start beacon click on IDE upper-left icon to disconnect board. BLE stack will restart");
print("Then reset (if code saved in RAM) / hard-reset (if code in Flash) board to reconnect IDE");
print("To hard-reset hold BTN1 for >5s while repowering the board. Type reset(1) to erase Flash");

In this variation, the "public (advertisement) key" string takes as input directly the string given by OpenHaystack. Note also the more typical advertising interval -- after a few months of use at this setting, I've experienced barely any battery drain.

gfwilliams commented 1 year ago

That's great! So what does the public_key string you can copy from OpenHaystack look like?

Is it literally just:

  0xbf, 0x44, 0x1f, 0xa2, 0x44, 0x40, 0x70, 0x65, 0x24, 0xe4, 0x0f, 0x5b,
  0x4b, 0xc0, 0xd1, 0x8e, 0x40, 0x98, 0xe4, 0x6f, 0xa6, 0xd5, 0xd4, 0x14,
  0x0a, 0xfe, 0xa8, 0x05

Or would there be a better format of data for the app to accept?

LowEnergyGenerator commented 1 year ago

I would be happy to test out whatever if someone wants to walk me through it, I'm new at this.

gfwilliams commented 1 year ago

Just added - if you go to https://espruino.com/apps you should be able to paste in the base64 code? If this works I'd love to get it added as a Bangle.js app too so users can find their Bangle with it :)

LowEnergyGenerator commented 1 year ago

I do not believe it worked. I added my public key in the web app and received this error on the web IDE:

 _                  |  _|    |_|  |  | -| . |  | | | |   | . | |__||  || ||||_|__|          || espruino.com  2v15 (c) 2021 G.Williams

Uncaught SyntaxError: Got UNFINISHED STRING expected EOF  at line 6 col 59 in .boot0 const key_b64 = "XXMYPUBLICKEYXX";                                                           ^

OpenHaystack Beacon

Uncaught TypeError: Assignment to a constant  at line 8 col 17 const key_b64 = "XXXMYPUBLICKEYXXX";                 ^ Public key: - base64: XXXMYPUBLICKEYXXX - hexadecimal: XX XX...

MAC address: XX:XX....

Advertising packet: 1e ff .....

BLE Connected, queueing BLE restart for later

To start beacon click on IDE upper-left icon to disconnect board. BLE stack will restart Then reset (if code saved in RAM) / hard-reset (if code in Flash) board to reconnect IDE To hard-reset hold BTN1 for >5s while repowering the board. Type reset(1) to erase Flash

Screen Shot 2022-11-07 at 9 21 59 PM

And I'm not sure which code string to use, Im guessing I need to be using the Advertisement Key, Base64?

gfwilliams commented 1 year ago

Sorry - that was a stupid mistake of mine. Please can you try installing again? I think it should be fixed now.

The Uncaught TypeError: Assignment to a constant might have been because you'd already saved the OpenHaystack code in a different file. Sending require("Storage").eraseAll() in the IDE should clear out anything that's left in there which might get rid of that problem

LowEnergyGenerator commented 1 year ago

Sorry - that was a stupid mistake of mine. Please can you try installing again? I think it should be fixed now.

The Uncaught TypeError: Assignment to a constant might have been because you'd already saved the OpenHaystack code in a different file. Sending require("Storage").eraseAll() in the IDE should clear out anything that's left in there which might get rid of that problem

Ok, that may have worked. Waiting to see if it comes up on OpenHaystack.

LowEnergyGenerator commented 1 year ago

Success! One question, does your app load the code into the Flash or RAM? When the battery is removed, will the OpenHaystack code disappear or remain?

gfwilliams commented 1 year ago

That's great news! The code will remain in flash (it's as if 'save to flash' was selected in the IDE) so it'll remain when the battery is removed

gfwilliams commented 1 year ago

Just to say I've just added the functionality for Bangle.js smartwatches too, so hopefully folks will now be able to use OpenHaystack to find them if they get lost: https://espruino.github.io/BangleApps/?id=openhaystack

ak2k commented 1 year ago

Nice, @gfwilliams! The app indeed makes this super easy.

Out of curiosity, I notice that you've elected not to set a particular interval for advertisements. Are you expecting power usage to be just fine on the default interval?

gfwilliams commented 1 year ago

Hi, yes - the default interval is 375ms which seems to be a reasonable compromise for most cases giving ~1 year of battery life, and since the Puck is alternating advertisements so still advertising its name, that means that the Haystack beacon is only broadcast every 750ms.

Going any slower would make it hard to connect to the Puck reliably - however I guess there could be an option to lower the Puck's advertising interval, not advertise the Puck's name and also make it non-scannable and non-connectable, and together those could have a big impact on battery life (potentially doubling it)