UNPKG

lora-packet

Version:
250 lines (214 loc) 10.9 kB
import LoraPacket, { LorawanVersion } from "./LoraPacket"; import { reverseBuffer } from "./util"; import { AesCmac } from "aes-cmac"; // calculate MIC from payload function calculateMIC( payload: LoraPacket, NwkSKey?: Buffer, //NwkSKey for DataUP/Down; SNwkSIntKey in data 1.1; SNwkSIntKey in Join 1.1 AppKey?: Buffer, //AppSKey for DataUP/Down; FNwkSIntKey in data 1.1; JSIntKey in Join 1.1 FCntMSBytes?: Buffer, ConfFCntDownTxDrTxCh?: Buffer ): Buffer { let LWVersion: LorawanVersion = LorawanVersion.V1_0; if (payload.isJoinRequestMessage()) { if (AppKey && AppKey.length !== 16) throw new Error("Expected a AppKey with length 16"); if (!payload.MHDR) throw new Error("Expected MHDR to be defined"); if (!payload.AppEUI) throw new Error("Expected AppEUI to be defined"); if (!payload.DevEUI) throw new Error("Expected DevEUI to be defined"); if (!payload.DevNonce) throw new Error("Expected DevNonce to be defined"); if (!payload.MACPayload) throw new Error("Expected DevNonce to be defined"); // const msgLen = payload.MHDR.length + payload.AppEUI.length + payload.DevEUI.length + payload.DevNonce.length; // CMAC over MHDR | AppEUI | DevEUI | DevNonce // the seperate fields are not in little-endian format, use the concatenated field const cmacInput = Buffer.concat([payload.MHDR, payload.MACPayload]); // CMAC calculation (as RFC4493) let fullCmac = new AesCmac(AppKey).calculate(cmacInput); if (!(fullCmac instanceof Buffer)) fullCmac = Buffer.from(fullCmac); // only first 4 bytes of CMAC are used as MIC const MIC = fullCmac.slice(0, 4); return MIC; } else if (payload.isReJoinRequestMessage()) { if (payload.RejoinType[0] === 1 && (!AppKey || AppKey.length !== 16)) throw new Error("Expected a JSIntKey with length 16"); if ((payload.RejoinType[0] === 0 || payload.RejoinType[0] === 2) && (!NwkSKey || NwkSKey.length !== 16)) throw new Error("Expected a SNwkSIntKey with length 16"); if (AppKey && AppKey.length !== 16) throw new Error("Expected a AppKey with length 16"); if (!payload.MHDR) throw new Error("Expected MHDR to be defined"); if (!payload.RejoinType) throw new Error("Expected RejoinType to be defined"); if (!payload.NetID && !payload.AppEUI) throw new Error("Expected NetID or JoinEUI to be defined"); if (!payload.DevEUI) throw new Error("Expected DevEUI to be defined"); if (!payload.RJCount0 && !payload.RJCount1) throw new Error("Expected RJCount0 or RJCount1 to be defined"); // const msgLen = payload.MHDR.length + payload.AppEUI.length + payload.DevEUI.length + payload.DevNonce.length; // CMAC over MHDR | AppEUI | DevEUI | DevNonce // the seperate fields are not in little-endian format, use the concatenated field const cmacInput = Buffer.concat([payload.MHDR, payload.MACPayload]); // CMAC calculation (as RFC4493) const calcKey = payload.RejoinType[0] === 1 ? AppKey : NwkSKey; let fullCmac = new AesCmac(calcKey).calculate(cmacInput); if (!(fullCmac instanceof Buffer)) fullCmac = Buffer.from(fullCmac); // only first 4 bytes of CMAC are used as MIC const MIC = fullCmac.slice(0, 4); return MIC; } else if (payload.isJoinAcceptMessage()) { if (AppKey && AppKey.length !== 16) throw new Error("Expected a AppKey with length 16"); if (!payload.MHDR) throw new Error("Expected MHDR to be defined"); if (!payload.AppNonce) throw new Error("Expected AppNonce to be defined"); if (!payload.NetID) throw new Error("Expected NetID to be defined"); if (!payload.DevAddr) throw new Error("Expected DevAddr to be defined"); if (!payload.DLSettings) throw new Error("Expected DLSettings to be defined"); if (!payload.RxDelay) throw new Error("Expected RxDelay to be defined"); if (!payload.CFList) throw new Error("Expected CFList to be defined"); if (!payload.MACPayload) throw new Error("Expected MACPayload to be defined"); if (payload.getDLSettingsOptNeg()) LWVersion = LorawanVersion.V1_1; let cmacInput: Buffer = Buffer.alloc(0); let cmacKey: Buffer = AppKey; if (LWVersion === LorawanVersion.V1_0) { // const msgLen = // payload.MHDR.length + // payload.AppNonce.length + // payload.NetID.length + // payload.DevAddr.length + // payload.DLSettings.length + // payload.RxDelay.length + // payload.CFList.length; // CMAC over MHDR | AppNonce | NetID | DevAddr | DLSettings | RxDelay | CFList // the seperate fields are not encrypted, use the encrypted concatenated field cmacInput = Buffer.concat([payload.MHDR, payload.MACPayload]); } else if (LWVersion === LorawanVersion.V1_1) { if (!payload.JoinReqType) throw new Error("Expected JoinReqType to be defined"); if (!payload.JoinEUI) throw new Error("Expected JoinEUI to be defined"); if (!payload.DevNonce) throw new Error("Expected DevNonce to be defined"); if (!NwkSKey || NwkSKey.length !== 16) throw new Error("Expected a NwkSKey with length 16"); cmacKey = NwkSKey; cmacInput = Buffer.concat([ payload.JoinReqType, reverseBuffer(payload.JoinEUI), reverseBuffer(payload.DevNonce), payload.MHDR, payload.MACPayload, ]); } // CMAC calculation (as RFC4493) let fullCmac = new AesCmac(cmacKey).calculate(cmacInput); if (!(fullCmac instanceof Buffer)) fullCmac = Buffer.from(fullCmac); // only first 4 bytes of CMAC are used as MIC const MIC = fullCmac.slice(0, 4); return MIC; } else { // ConfFCntDownTxDrTxCh = ConfFCntDownTxDrTxCh || Buffer.alloc(2, 0); if (NwkSKey && NwkSKey.length !== 16) throw new Error("Expected a NwkSKey with length 16"); if (payload.DevAddr && payload.DevAddr.length !== 4) throw new Error("Expected a payload DevAddr with length 4"); if (payload.FCnt && payload.FCnt.length !== 2) throw new Error("Expected a payload FCnt with length 2"); if (!payload.MHDR) throw new Error("Expected MHDR to be defined"); if (!payload.DevAddr) throw new Error("Expected DevAddr to be defined"); if (!payload.FCnt) throw new Error("Expected FCnt to be defined"); if (!payload.MACPayload) throw new Error("Expected MACPayload to be defined"); if (!FCntMSBytes) { FCntMSBytes = Buffer.from("0000", "hex"); } if (ConfFCntDownTxDrTxCh) { if (!AppKey || AppKey?.length !== 16) throw new Error("Expected a FNwkSIntKey with length 16"); LWVersion = LorawanVersion.V1_1; } // if (NwkSKey && AppKey) { // LWVersion = LorawanVersion.V1_1; // } let dir; const isUplinkAndIs1_1 = payload.getDir() === "up" && LWVersion === LorawanVersion.V1_1; const isDownlinkAndIs1_1 = payload.getDir() === "down" && LWVersion === LorawanVersion.V1_1; if (payload.getDir() == "up") { dir = Buffer.alloc(1, 0); } else if (payload.getDir() == "down") { dir = Buffer.alloc(1, 1); if (!ConfFCntDownTxDrTxCh) { ConfFCntDownTxDrTxCh = Buffer.alloc(4, 0); } else if (ConfFCntDownTxDrTxCh && ConfFCntDownTxDrTxCh?.length !== 2) { throw new Error("Expected a ConfFCntDown with length 2"); } else { ConfFCntDownTxDrTxCh = Buffer.concat([ConfFCntDownTxDrTxCh, Buffer.alloc(2, 0)]); } } else { throw new Error("expecting direction to be either 'up' or 'down'"); } if (isUplinkAndIs1_1) { if (!ConfFCntDownTxDrTxCh || ConfFCntDownTxDrTxCh?.length !== 4) { throw new Error("Expected a ConfFCntDownTxDrTxCh with length 4 Expected ( ConfFCnt | TxDr | TxCh)"); } if (payload.getFCtrlACK() || (isUplinkAndIs1_1 && payload.getFPort() === 0)) { ConfFCntDownTxDrTxCh.writeUInt16BE(ConfFCntDownTxDrTxCh.readUInt16LE(0)); } else { ConfFCntDownTxDrTxCh.writeUInt16BE(0); } } const msgLen = payload.MHDR.length + payload.MACPayload.length; const B0 = Buffer.concat([ Buffer.from([0x49]), // as spec isDownlinkAndIs1_1 ? ConfFCntDownTxDrTxCh : Buffer.alloc(4, 0), // LoraWan Spec 1.1, pag. 27 dir, // direction ('Dir') reverseBuffer(payload.DevAddr), reverseBuffer(payload.FCnt), FCntMSBytes, // upper 2 bytes of FCnt (zeroes) Buffer.alloc(1, 0), // 0x00 Buffer.alloc(1, msgLen), // len(msg) ]); // CMAC over B0 | MHDR | MACPayload const cmacInput = Buffer.concat([B0, payload.MHDR, payload.MACPayload]); // CMAC calculation (as RFC4493) let key = NwkSKey; if (isDownlinkAndIs1_1) key = AppKey; let fullCmac = new AesCmac(key).calculate(cmacInput); if (!(fullCmac instanceof Buffer)) fullCmac = Buffer.from(fullCmac); // only first 4 bytes of CMAC are used as MIC const MIC = fullCmac.slice(0, 4); if (isUplinkAndIs1_1) { const B1 = Buffer.concat([ Buffer.from([0x49]), // as spec ConfFCntDownTxDrTxCh, // LoraWan Spec 1.1, pag. 27 dir, // direction ('Dir') reverseBuffer(payload.DevAddr), reverseBuffer(payload.FCnt), FCntMSBytes, // upper 2 bytes of FCnt (zeroes) Buffer.alloc(1, 0), // 0x00 Buffer.alloc(1, msgLen), // len(msg) ]); const cmacSInput = Buffer.concat([B1, payload.MHDR, payload.MACPayload]); let fullCmacS = new AesCmac(AppKey).calculate(cmacSInput); if (!(fullCmacS instanceof Buffer)) fullCmacS = Buffer.from(fullCmacS); // only first 2 bytes of CMAC and CMACS are used as MIC const MICS = fullCmacS.slice(0, 4); return Buffer.concat([MICS.slice(0, 2), MIC.slice(0, 2)]); } return MIC; } } // verify is just calculate & compare function verifyMIC( payload: LoraPacket, NwkSKey?: Buffer, AppKey?: Buffer, FCntMSBytes?: Buffer, ConfFCntDownTxDrTxCh?: Buffer ): boolean { if (payload.MIC && payload.MIC.length !== 4) throw new Error("Expected a payload payload.MIC with length 4"); const calculated = calculateMIC(payload, NwkSKey, AppKey, FCntMSBytes, ConfFCntDownTxDrTxCh); if (!payload.MIC) return false; return Buffer.compare(payload.MIC, calculated) === 0; } // calculate MIC & store function recalculateMIC( payload: LoraPacket, NwkSKey?: Buffer, AppKey?: Buffer, FCntMSBytes?: Buffer, ConfFCntDownTxDrTxCh?: Buffer ): void { const calculated = calculateMIC(payload, NwkSKey, AppKey, FCntMSBytes, ConfFCntDownTxDrTxCh); payload.MIC = calculated; if (!payload.MHDR) throw new Error("Missing MHDR"); if (!payload.MACPayload) throw new Error("Missing MACPayload"); if (!payload.MIC) throw new Error("Missing MIC"); if (!payload.MHDR) throw new Error("Missing MHDR"); payload.PHYPayload = Buffer.concat([payload.MHDR, payload.MACPayload, payload.MIC]); payload.MACPayloadWithMIC = payload.PHYPayload.slice(payload.MHDR.length, payload.PHYPayload.length); } export { calculateMIC, verifyMIC, recalculateMIC };