Skip to content

Instantly share code, notes, and snippets.

@kabbi
Created August 16, 2020 01:32
Show Gist options
  • Save kabbi/242a48dde4d3ae548f6d54cf1f987776 to your computer and use it in GitHub Desktop.
Save kabbi/242a48dde4d3ae548f6d54cf1f987776 to your computer and use it in GitHub Desktop.
Yeelight mesh bulb ble connection / auth PoC
const noble = require('@abandonware/noble');
const EC = require('elliptic').ec;
const hkdf = require('futoin-hkdf');
const { crc32 } = require('crc');
const ccm = require('aes-ccm');
const ec = EC('p256');
noble.on('stateChange', async (state) => {
if (state === 'poweredOn') {
console.log('- scanning');
await noble.startScanningAsync(['fe95']);
}
});
const waitFor = (events) =>
new Promise((resolve) => {
let interval = setInterval(() => {
if (events.length === 0) {
return;
}
const data = events.shift();
console.log('>', data.toString('hex'));
resolve(data);
clearInterval(interval);
}, 100);
});
const write = async (char, data) => {
console.log('<', data.toString('hex'));
await char.writeAsync(data, true);
};
const hex = (parts) => Buffer.from(parts.join(''), 'hex');
const delay = (time) => new Promise((resolve) => setTimeout(resolve, time));
noble.on('discover', async (peripheral) => {
if (peripheral.address !== '50-ec-50-de-36-ac') {
return;
}
console.log('- connecting to', peripheral.address);
await noble.stopScanningAsync();
await peripheral.connectAsync();
const {
characteristics: [prepareChar, mainChar],
} = await peripheral.discoverSomeServicesAndCharacteristicsAsync(
['fe95'],
['0010', '0016']
);
console.log('- connected');
const key = ec.genKeyPair();
await prepareChar.subscribeAsync();
await mainChar.subscribeAsync();
const prepareEvents = [];
const mainEvents = [];
prepareChar.on('data', (data) => prepareEvents.push(data));
mainChar.on('data', (data) => mainEvents.push(data));
await write(prepareChar, Buffer.of(0xa4));
await waitFor(mainEvents);
await write(mainChar, hex`000005000650`);
await waitFor(mainEvents);
await write(
mainChar,
hex`00000501505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050`
);
await delay(200);
await write(prepareChar, hex`50000000`);
await write(mainChar, hex`000000030100`);
await waitFor(mainEvents);
await write(
mainChar,
Buffer.concat([hex`0100`, Buffer.of(...key.getPublic().encode().slice(1))])
);
await waitFor(mainEvents);
const devicePubKeyData = await waitFor(mainEvents);
await write(mainChar, hex`00000300`);
const deviceKey = ec.keyFromPublic(
Buffer.concat([Buffer.of(4), devicePubKeyData.slice(4)])
);
const cloudKey = hex`XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX`; // 32 bytes ltmk key of your device
const secretKey = key.derive(deviceKey.getPublic()).toBuffer();
const sessionKey = hkdf(Buffer.concat([secretKey, cloudKey]), 64, {
salt: 'miot-mesh-login-salt',
info: 'miot-mesh-login-info',
hash: 'sha256',
});
const devicePubKeyCRC = Buffer.alloc(4);
devicePubKeyCRC.writeUInt32LE(crc32(devicePubKeyData.slice(4)));
const { ciphertext, auth_tag } = ccm.encrypt(
sessionKey.slice(16, 32),
hex`101112131415161718191a1b`,
devicePubKeyCRC,
Buffer.alloc(0),
4
);
const authBlob = Buffer.concat([ciphertext, auth_tag]);
await write(mainChar, hex`000000050100`);
await waitFor(mainEvents);
await write(mainChar, Buffer.concat([hex`0100`, authBlob]));
await waitFor(mainEvents);
await waitFor(prepareEvents);
await peripheral.disconnectAsync();
process.exit(0);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment