Verify 16 bits public MIC of LoRaWAN 1.1 uplink
/**
* Validate the "public" 16 bits of full 32 bits LoRaWAN 1.1 MIC in uplink frame.
*
* To calculate the other 16 bits, one must also know at which data rate and channel the uplink
* was transmitted (and, in case of a downlink confirmation, also the downlink's frame counter),
* along with the secret SNwkSIntKey.
*
* See https://lora-alliance.org/sites/default/files/2018-04/lorawantm_specification_-v1.1.pdf#page=27
* and https://github.com/anthonykirby/lora-packet/issues/29
*/
// This throws deprecation warnings; see https://github.com/allan-stewart/node-aes-cmac/pull/2
const aesCmac = require('node-aes-cmac').aesCmac;
// Secret FNwkSIntKey as programmed in the device or derived from a 1.1 Join Accept
const fNwkSIntKey = Buffer.from('448B20D2E0ED62F3DC61ADE7B589B65A', 'hex');
// Regular 1.1 uplink
const phyPayload = Buffer.from('800E21062602900AE4AE010FCD776DB3DF96F4CB75656695F9CC', 'hex');
const devAddr = phyPayload.readUInt32LE(1);
// Counter is 32 bits, but only 16 LSB are in the LoRaWAN packet; assume MSB is 0x0000
const fCntUp = phyPayload.readUInt16LE(6);
const msgLen = phyPayload.length - 4;
const msg = phyPayload.slice(0, msgLen);
const mic = phyPayload.slice(msgLen);
// See https://lora-alliance.org/sites/default/files/2018-04/lorawantm_specification_-v1.1.pdf#page=27
const b0 = Buffer.allocUnsafe(16);
let i = 0;
i = b0.writeUInt8(0x49, i);
i = b0.writeUInt32LE(0x00000000, i);
i = b0.writeUInt8(0x00, i);
i = b0.writeUInt32LE(devAddr, i);
i = b0.writeUInt32LE(fCntUp, i);
i = b0.writeUInt8(0x00, i);
i = b0.writeUInt8(msgLen, i);
// cmacF = aes128_cmac(FNwkSIntKey, B0 | msg)
const cmacFVerify = aesCmac(
fNwkSIntKey,
Buffer.concat([b0, msg]),
{returnAsBuffer: true}
).slice(0, 4);
`<pre>
Full MIC from packet = ${mic.toString('hex')}
Calculated cmacF = ${cmacFVerify.toString('hex')}
</pre>`;
no comments