lora-packet
Version:
LoRa packet decoder/encoder
256 lines • 11.4 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateJSKeys = exports.generateWORSessionKeys = exports.generateWORKey = exports.generateSessionKeys10 = exports.generateSessionKeys11 = exports.generateSessionKeys = exports.decryptJoinAccept = exports.decryptFOpts = exports.decryptJoin = exports.decrypt = exports.encrypt = void 0;
const util_1 = require("./util");
const crypto_js_1 = __importDefault(require("crypto-js"));
const LORAIV = crypto_js_1.default.enc.Hex.parse("00000000000000000000000000000000");
var KeyType11;
(function (KeyType11) {
KeyType11["FNwkSIntKey"] = "01";
KeyType11["AppSKey"] = "02";
KeyType11["SNwkSIntKey"] = "03";
KeyType11["NwkSEncKey"] = "04";
})(KeyType11 || (KeyType11 = {}));
var KeyType10;
(function (KeyType10) {
KeyType10["NwkSKey"] = "01";
KeyType10["AppSKey"] = "02";
})(KeyType10 || (KeyType10 = {}));
var KeyTypeJS;
(function (KeyTypeJS) {
KeyTypeJS["JSIntKey"] = "06";
KeyTypeJS["JSEncKey"] = "05";
})(KeyTypeJS || (KeyTypeJS = {}));
var KeyTypeWORSession;
(function (KeyTypeWORSession) {
KeyTypeWORSession["WorSIntKey"] = "01";
KeyTypeWORSession["WorSEncKey"] = "02";
})(KeyTypeWORSession || (KeyTypeWORSession = {}));
function decrypt(payload, AppSKey, NwkSKey, fCntMSB32) {
if (!payload.PHYPayload || !payload.FRMPayload)
throw new Error("Payload was not defined");
if (!fCntMSB32)
fCntMSB32 = Buffer.alloc(2, 0);
const blocks = Math.ceil(payload.FRMPayload.length / 16);
const sequenceS = Buffer.alloc(16 * blocks);
for (let block = 0; block < blocks; block++) {
const ai = _metadataBlockAi(payload, block, fCntMSB32);
ai.copy(sequenceS, block * 16);
}
const key = payload.getFPort() === 0 ? NwkSKey : AppSKey;
if (!key || key.length !== 16)
throw new Error("Expected a appropriate key with length 16");
const cipherstream_base64 = crypto_js_1.default.AES.encrypt(crypto_js_1.default.enc.Hex.parse(sequenceS.toString("hex")), crypto_js_1.default.enc.Hex.parse(key.toString("hex")), {
mode: crypto_js_1.default.mode.ECB,
iv: LORAIV,
padding: crypto_js_1.default.pad.NoPadding,
});
const cipherstream = Buffer.from(cipherstream_base64.toString(), "base64");
const plaintextPayload = Buffer.alloc(payload.FRMPayload.length);
for (let i = 0; i < payload.FRMPayload.length; i++) {
const Si = cipherstream.readUInt8(i);
plaintextPayload.writeUInt8(Si ^ payload.FRMPayload.readUInt8(i), i);
}
return plaintextPayload;
}
exports.decrypt = decrypt;
// Check
function decryptJoin(payload, AppKey) {
if (!(payload === null || payload === void 0 ? void 0 : payload.MACPayloadWithMIC))
throw new Error("Expected parsed payload to be defined");
if (AppKey.length !== 16)
throw new Error("Expected a appropriate key with length 16");
const cipherstream = crypto_js_1.default.AES.decrypt(payload.MACPayloadWithMIC.toString("base64"), crypto_js_1.default.enc.Hex.parse(AppKey.toString("hex")), {
mode: crypto_js_1.default.mode.ECB,
padding: crypto_js_1.default.pad.NoPadding,
});
return Buffer.from(cipherstream.toString(), "hex");
}
exports.decryptJoin = decryptJoin;
function decryptFOpts(payload, NwkSEncKey, fCntMSB32) {
if (!fCntMSB32)
fCntMSB32 = Buffer.alloc(2);
if (!(payload === null || payload === void 0 ? void 0 : payload.FOpts))
throw new Error("Expected FOpts to be defined");
if (!(payload === null || payload === void 0 ? void 0 : payload.DevAddr))
throw new Error("Expected DevAddr to be defined");
if (NwkSEncKey.length !== 16)
throw new Error("Expected a appropriate key with length 16");
if (!payload.FCnt)
throw new Error("Expected FCnt to be defined");
const direction = Buffer.alloc(1);
let aFCntDown = false;
if (payload.getDir() == "up") {
direction.writeUInt8(0, 0);
}
else if (payload.getDir() == "down") {
direction.writeUInt8(1, 0);
if (payload.FPort != null && payload.getFPort() > 0) {
aFCntDown = true;
}
}
else {
throw new Error("Decrypt error: expecting direction to be either 'up' or 'down'");
}
// https://lora-alliance.org/wp-content/uploads/2020/11/00001.002.00001.001.cr-fcntdwn-usage-in-fopts-encryption-v2-r1.pdf
const aBuffer = Buffer.concat([
Buffer.alloc(1, 1),
Buffer.alloc(3, 0),
Buffer.alloc(1, aFCntDown ? 2 : 1),
direction,
(0, util_1.reverseBuffer)(payload.DevAddr),
(0, util_1.reverseBuffer)(payload.FCnt),
fCntMSB32,
Buffer.alloc(1, 0),
Buffer.alloc(1, 1),
]);
const cipherstream_base64 = crypto_js_1.default.AES.encrypt(crypto_js_1.default.enc.Hex.parse(aBuffer.toString("hex")), crypto_js_1.default.enc.Hex.parse(NwkSEncKey.toString("hex")), {
mode: crypto_js_1.default.mode.ECB,
iv: LORAIV,
padding: crypto_js_1.default.pad.NoPadding,
});
const cipherstream = Buffer.from(cipherstream_base64.toString(), "base64");
const plaintextPayload = Buffer.alloc(payload.FOpts.length);
for (let i = 0; i < payload.FOpts.length; i++) {
plaintextPayload[i] = cipherstream[i] ^ payload.FOpts[i];
}
return plaintextPayload;
}
exports.decryptFOpts = decryptFOpts;
function generateKey(key, AppNonce, NetIdOrJoinEui, DevNonce, keyType) {
let keyNonceStr = keyType;
keyNonceStr += (0, util_1.reverseBuffer)(AppNonce).toString("hex");
keyNonceStr += (0, util_1.reverseBuffer)(NetIdOrJoinEui).toString("hex");
keyNonceStr += (0, util_1.reverseBuffer)(DevNonce).toString("hex");
keyNonceStr = keyNonceStr.padEnd(32, "0");
const keyNonce = Buffer.from(keyNonceStr, "hex");
const nwkSKey_base64 = crypto_js_1.default.AES.encrypt(crypto_js_1.default.enc.Hex.parse(keyNonce.toString("hex")), crypto_js_1.default.enc.Hex.parse(key.toString("hex")), {
mode: crypto_js_1.default.mode.ECB,
padding: crypto_js_1.default.pad.NoPadding,
});
return Buffer.from(nwkSKey_base64.toString(), "base64");
}
function generateSessionKeys(AppKey, NetId, AppNonce, DevNonce) {
return generateSessionKeys10(AppKey, NetId, AppNonce, DevNonce);
}
exports.generateSessionKeys = generateSessionKeys;
function generateSessionKeys10(AppKey, NetId, AppNonce, DevNonce) {
if (AppKey.length !== 16)
throw new Error("Expected a AppKey with length 16");
if (NetId.length !== 3)
throw new Error("Expected a NetId with length 3");
if (AppNonce.length !== 3)
throw new Error("Expected a AppNonce with length 3");
if (DevNonce.length !== 2)
throw new Error("Expected a DevNonce with length 2");
return {
AppSKey: generateKey(AppKey, AppNonce, NetId, DevNonce, KeyType10.AppSKey),
NwkSKey: generateKey(AppKey, AppNonce, NetId, DevNonce, KeyType10.NwkSKey),
};
}
exports.generateSessionKeys10 = generateSessionKeys10;
function generateSessionKeys11(AppKey, NwkKey, JoinEUI, AppNonce, DevNonce) {
if (AppKey.length !== 16)
throw new Error("Expected a AppKey with length 16");
if (NwkKey.length !== 16)
throw new Error("Expected a NwkKey with length 16");
if (AppNonce.length !== 3)
throw new Error("Expected a AppNonce with length 3");
if (DevNonce.length !== 2)
throw new Error("Expected a DevNonce with length 2");
return {
AppSKey: generateKey(AppKey, AppNonce, JoinEUI, DevNonce, KeyType11.AppSKey),
FNwkSIntKey: generateKey(NwkKey, AppNonce, JoinEUI, DevNonce, KeyType11.FNwkSIntKey),
SNwkSIntKey: generateKey(NwkKey, AppNonce, JoinEUI, DevNonce, KeyType11.SNwkSIntKey),
NwkSEncKey: generateKey(NwkKey, AppNonce, JoinEUI, DevNonce, KeyType11.NwkSEncKey),
};
}
exports.generateSessionKeys11 = generateSessionKeys11;
function generateJSKeys(NwkKey, DevEui) {
if (DevEui.length !== 8)
throw new Error("Expected a DevEui with length 8");
if (NwkKey.length !== 16)
throw new Error("Expected a NwkKey with length 16");
return {
JSIntKey: generateKey(NwkKey, DevEui, Buffer.alloc(0), Buffer.alloc(0), KeyTypeJS.JSIntKey),
JSEncKey: generateKey(NwkKey, DevEui, Buffer.alloc(0), Buffer.alloc(0), KeyTypeJS.JSEncKey),
};
}
exports.generateJSKeys = generateJSKeys;
function generateWORKey(NwkSKey) {
if (NwkSKey.length !== 16)
throw new Error("Expected a NwkKey/NwkSEncKey with length 16");
return {
RootWorSKey: generateKey(NwkSKey, Buffer.alloc(0), Buffer.alloc(0), Buffer.alloc(0), KeyTypeWORSession.WorSIntKey),
};
}
exports.generateWORKey = generateWORKey;
function generateWORSessionKeys(RootWorSKey, DevAddr) {
if (DevAddr.length !== 4)
throw new Error("Expected a DevAddr with length 4");
if (RootWorSKey.length !== 16)
throw new Error("Expected a RootWorSKey with length 16");
return {
WorSIntKey: generateKey(RootWorSKey, DevAddr, Buffer.alloc(0), Buffer.alloc(0), KeyTypeWORSession.WorSIntKey),
WorSEncKey: generateKey(RootWorSKey, DevAddr, Buffer.alloc(0), Buffer.alloc(0), KeyTypeWORSession.WorSEncKey),
};
}
exports.generateWORSessionKeys = generateWORSessionKeys;
function encrypt(buffer, key) {
// CHECK
const ciphertext = crypto_js_1.default.AES.encrypt(crypto_js_1.default.enc.Hex.parse(buffer.toString("hex")), crypto_js_1.default.enc.Hex.parse(key.toString("hex")), {
mode: crypto_js_1.default.mode.ECB,
iv: LORAIV,
padding: crypto_js_1.default.pad.NoPadding,
}).ciphertext.toString(crypto_js_1.default.enc.Hex);
return Buffer.from(ciphertext, "hex");
}
exports.encrypt = encrypt;
function decryptJoinAccept(payload, appKey) {
const payloadBuffer = payload.PHYPayload || Buffer.alloc(0);
// Check
const mhdr = payloadBuffer.slice(0, 1);
const joinAccept = encrypt(payloadBuffer.slice(1), appKey);
return Buffer.concat([mhdr, joinAccept]);
}
exports.decryptJoinAccept = decryptJoinAccept;
// Encrypt stream mixes in metadata blocks, as Ai =
// 0x01
// 0x00 0x00 0x00 0x00
// direction-uplink/downlink [1]
// DevAddr [4]
// FCnt as 32-bit, lsb first [4]
// 0x00
// counter = i [1]
function _metadataBlockAi(payload, blockNumber, fCntMSB32) {
if (!fCntMSB32)
fCntMSB32 = Buffer.alloc(2);
let direction;
if (payload.getDir() == "up") {
direction = Buffer.alloc(1, 0);
}
else if (payload.getDir() == "down") {
direction = Buffer.alloc(1, 1);
}
else {
throw new Error("Decrypt error: expecting direction to be either 'up' or 'down'");
}
if (!payload.DevAddr)
throw new Error("Decrypt error: DevAddr not defined'");
if (!payload.FCnt)
throw new Error("Decrypt error: FCnt not defined'");
const aiBuffer = Buffer.concat([
Buffer.from("0100000000", "hex"),
direction,
(0, util_1.reverseBuffer)(payload.DevAddr),
(0, util_1.reverseBuffer)(payload.FCnt),
fCntMSB32,
Buffer.alloc(1, 0),
Buffer.alloc(1, blockNumber + 1),
]);
return aiBuffer;
}
//# sourceMappingURL=crypto.js.map