UNPKG

@smartdcc/gbcs-parser

Version:
423 lines 15.9 kB
"use strict"; /* * * Original copyright holders for the GBCS message parser tool: * * Copyright (c) 2019 Andre B. Oliveira * 2019 Enrique Giraldo * 2019 Cristóbal Borrero * * Copyright for subsequent changes, including porting to NodeJS, * updating for TypeScript and refactor to support unit testing: * * Copyright (c) 2022 Smart DCC Limited * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.parseGbcsMessage = parseGbcsMessage; const crypto_1 = require("crypto"); const context_1 = require("./context"); const crypto_2 = require("./crypto"); const common_1 = require("./common"); const util_1 = require("./util"); const dlms = __importStar(require("./dlms")); const gbz = __importStar(require("./zigbee")); const asn1 = __importStar(require("./asn1")); async function parseGeneralCiphering(ctx, x) { (0, context_1.putSeparator)(ctx, 'MAC Header'); (0, context_1.putBytes)(ctx, 'General Ciphering', (0, util_1.getBytes)(x, 7)); const len = parseEncodedLength(ctx, x, 'Ciphered Service Length'); const y = (0, util_1.getBytes)(x, len); (0, context_1.putBytes)(ctx, 'Security Header', (0, util_1.getBytes)(y, 5)); const macDataStart = y.index; const cipherInfo = await parseGeneralSigning(ctx, (0, util_1.getBytes)(y, len - 5 - 12)); const macDataEnd = y.index; (0, context_1.putSeparator)(ctx, 'MAC'); const mac = (0, util_1.getBytes)(y, 12); const aad = new Uint8Array(6 + (macDataEnd - macDataStart)); aad.set([0x11, 0, 0, 0, 0, 0], 0); aad.set(y.input.buffer.subarray(macDataStart, macDataEnd), 6); if (cipherInfo.cra === 'command' && ctx.acbEui === undefined) { (0, context_1.putBytes)(ctx, 'MAC', mac, 'unknown acb'); } else { let pubKey = undefined; let prvKey = undefined; try { pubKey = await ctx.lookupKey(cipherInfo.cra === 'command' && ctx.acbEui !== undefined ? ctx.acbEui : cipherInfo.origSysTitle, 'KA', {}); prvKey = await ctx.lookupKey(cipherInfo.recipSysTitle, 'KA', { privateKey: true, }); } catch { try { prvKey = await ctx.lookupKey(cipherInfo.cra === 'command' && ctx.acbEui !== undefined ? ctx.acbEui : cipherInfo.origSysTitle, 'KA', { privateKey: true, }); pubKey = await ctx.lookupKey(cipherInfo.recipSysTitle, 'KA', {}); } catch { pubKey = undefined; prvKey = undefined; } } if (pubKey !== undefined && prvKey !== undefined) { const aesKey = (0, crypto_2.deriveKeyFromPair)(prvKey, pubKey, cipherInfo, cipherInfo.cra); try { (0, crypto_2.ungcm)(cipherInfo, new Uint8Array(0), aad, aesKey, mac.input.buffer.subarray(mac.index, mac.end)); (0, context_1.putBytes)(ctx, 'MAC', mac, 'valid'); } catch { (0, context_1.putBytes)(ctx, 'MAC', mac, 'invalid'); } } else { (0, context_1.putBytes)(ctx, 'MAC', mac, 'unknown'); } } return cipherInfo; } async function parseGeneralSigning(ctx, x) { (0, context_1.putSeparator)(ctx, 'Grouping Header'); const signedDataStart = x.index + 1; (0, context_1.putBytes)(ctx, 'General Signing', (0, util_1.getBytes)(x, 2)); const craFlag = (0, common_1.parseCraFlag)(ctx, x, ''); const cipherInfo = { origCounter: x.input.buffer.subarray(x.index, x.index + 8), origSysTitle: x.input.buffer.subarray(x.index + 9, x.index + 17), recipSysTitle: x.input.buffer.subarray(x.index + 18, x.index + 26), cra: craFlag === 2 ? 'response' : craFlag === 3 ? 'alert' : 'command', }; (0, common_1.parseCounter)(ctx, 'Originator Counter', x); (0, context_1.putBytes)(ctx, 'Originator System Title', (0, util_1.getBytes)(x, 9)); (0, context_1.putBytes)(ctx, 'Recipient System Title', (0, util_1.getBytes)(x, 9)); dlms.parseDlmsOctetString(ctx, x, 'Date Time', false); const otherInfoLen = parseEncodedLength(ctx, x, 'Other Information Length'); const otherInfo = (0, util_1.getBytes)(x, otherInfoLen); const messageCode = (0, common_1.parseMessageCode)(ctx, ' Message Code', otherInfo); if (otherInfoLen >= 10) { cipherInfo.supplimentryRemotePartyId = otherInfo.input.buffer.subarray(otherInfo.index, otherInfo.index + 8); (0, context_1.putBytes)(ctx, ' Supplementary Remote Party ID', (0, util_1.getBytes)(otherInfo, 8)); if (otherInfoLen >= 18) { (0, common_1.parseCounter)(ctx, ' Supplementary Remote Party Counter', otherInfo); if (otherInfoLen === 26) { cipherInfo.supplimentryOriginatorCounter = otherInfo.input.buffer.subarray(otherInfo.index, otherInfo.index + 8); (0, common_1.parseCounter)(ctx, ' Supplementary Originator Counter', otherInfo); } else if (otherInfoLen > 26) { asn1.parseCertificate(ctx, otherInfo, ' Supplementary Remote Party Key Agreement Certificate'); } } } const contentLen = parseEncodedLength(ctx, x, 'Content Length'); parsePayload(ctx, (0, util_1.getBytes)(x, contentLen), messageCode, craFlag); const signedDataEnd = x.index; if (x.index !== x.end) { (0, context_1.putSeparator)(ctx, 'Signature'); const signatureLen = parseEncodedLength(ctx, x, 'Signature Length'); if (signatureLen > 0) { const s = (0, util_1.getBytes)(x, signatureLen); const dataToSign = x.input.buffer.subarray(signedDataStart, signedDataEnd); const signature = s.input.buffer.subarray(s.index, s.end); (0, context_1.putBytes)(ctx, 'Signature', s); const pubKey = await ctx.lookupKey(cipherInfo.origSysTitle, 'DS', {}); const valid = (0, crypto_1.verify)('SHA256', dataToSign, { key: pubKey, dsaEncoding: 'ieee-p1363' }, signature); (0, context_1.putBytes)(ctx, 'Valid', (0, util_1.getBytes)(x, 0), valid ? 'true' : 'false'); } } return cipherInfo; } function parsePayload(ctx, x, messageCode, craFlag) { (0, context_1.putSeparator)(ctx, 'Payload'); if (messageCode === 0x0008) { // CS02a if (craFlag === 1) { asn1.parseProvideSecurityCredentialDetailsCommand(ctx, x); } else { asn1.parseProvideSecurityCredentialDetailsResponse(ctx, x); } } else if (messageCode >= 0x0100 && messageCode <= 0x0109) { // CS02b if (craFlag === 1) { asn1.parseUpdateSecurityCredentialsCommand(ctx, x); } else { asn1.parseUpdateSecurityCredentialsResponse(ctx, x); } } else if (messageCode === 0x00cb) { // CS02b alert asn1.parseUpdateSecurityCredentialsAlert(ctx, x); } else if (messageCode === 0x000a) { // CS02c if (craFlag === 1) { asn1.parseIssueSecurityCredentialsCommand(ctx, x); } else { asn1.parseIssueSecurityCredentialsResponse(ctx, x); } } else if (messageCode === 0x000b) { // CS02d if (craFlag === 1) { asn1.parseUpdateDeviceCertificateCommand(ctx, x); } else { asn1.parseUpdateDeviceCertificateResponse(ctx, x); } } else if (messageCode === 0x000c) { // CS02e if (craFlag === 1) { asn1.parseProvideDeviceCertificateCommand(ctx, x); } else { asn1.parseProvideDeviceCertificateResponse(ctx, x); } } else if (messageCode === 0x000d || messageCode === 0x00ab || messageCode === 0x000e || messageCode === 0x00af) { // CS03A1 || CS03A2 || CS03B || CS03C if (craFlag === 1) { asn1.parseJoinDeviceCommand(ctx, x); } else { asn1.parseJoindDeviceResponse(ctx, x); } } else if (messageCode === 0x000f || messageCode === 0x0010) { // CS04AC || CS04B if (craFlag === 1) { asn1.parseUnjoinDeviceCommand(ctx, x); } else { asn1.parseUnjoindDeviceResponse(ctx, x); } } else if (messageCode === 0x0012) { // CS06 if (craFlag === 1) { asn1.parseActivateFirmwareCommand(ctx, x); } else { asn1.parseActivateFirmwareResponse(ctx, x); } } else if (messageCode === 0x00ca) { // CS06 alert asn1.parseActivateFirmwareAlert(ctx, x); } else if (messageCode === 0x0013) { // CS07 if (craFlag === 1) { asn1.parseReadDeviceJoinDetailsCommand(ctx, x); } else { asn1.parseReadDeviceJoinDetailsResponse(ctx, x); } } else if (messageCode === 0x007f) { // GCS28 if (craFlag === 1) { asn1.parseSetTimeCommand(ctx, x); } else { asn1.parseSetTimeResponse(ctx, x); } } else if (messageCode === 0x008b) { // GCS53 gbz.parseGbzGcs53AlertPayload(ctx, x); } else if (messageCode === 0x008c) { // GCS59 if (craFlag === 1) { asn1.parseGpfDeviceLogRestoreCommand(ctx, x); } else { asn1.parseGpfDeviceLogRestoreResponse(ctx, x); } } else if (messageCode === 0x00b2) { // GCS62 asn1.parseGpfDeviceLogBackupAlert(ctx, x); } else if (messageCode === 0x00cc) { // Future Dated DLMS dlms.parseDlmsFutureDatedAlert(ctx, x); } else if (messageCode === 0x00cd) { // Future Dated GBZ gbz.parseGbzFutureDatedAlertPayload(ctx, x); } else if (messageCode === 0x00ce) { // FW Distribution Receipt Alert ESME dlms.parseDlmsFirmwareDistributionReceiptAlert(ctx, x); } else if (messageCode === 0x00cf) { // FW Distribution Receipt Alert GSME gbz.parseGbzFirmwareDistributionReceiptAlert(ctx, x); } else if (messageCode === 0x0061) { // ECS68 dlms.parseDlmsBillingDataLogAlert(ctx, x); } else if (messageCode === 0x00d5) { // 8F84 Alert dlms.parseFailureToDeliverRemotePartyToEsme(ctx, x); } else if (messageCode === 0x00f0) { // 81A0 Alert ESME dlms.parseDlmsMeterIntegrityIssueWarningAlert(ctx, x); } else if (messageCode === 0x00f2) { // 81A0 Alert GSME gbz.parseGbzMeterIntegrityIssueWarningAlert(ctx, x); } else if (x.input.byte(x.index) === 1 && x.input.byte(x.index + 1) === 9) { if (craFlag === 3) { gbz.parseGbzAlertPayload(ctx, x); } else { gbz.parseGbzPayload(ctx, x); } } else if (x.input.byte(x.index) === 0xd9) { dlms.parseDlmsAccessRequest(ctx, x); } else if (x.input.byte(x.index) === 0xda) { dlms.parseDlmsAccessResponse(ctx, x, messageCode); } else if (x.input.byte(x.index) === 0x0f) { dlms.parseDlmsDataNotificationGbcsAlert(ctx, x); } else if (messageCode === 0x0128) { // CCS08 alert asn1.parseFirmwareTransferAlert(ctx, x); } else if (messageCode === 0x0129) { // CS08 Read PPMID/HCALCS Firmware Version if (craFlag === 3) { asn1.parseReadPPMIDHCALCSFirmwareVersionAlert(ctx, x); } else if (craFlag === 2) { asn1.parseReadPPMIDHCALCSFirmwareVersionResponse(ctx, x); } else { asn1.parseReadPPMIDHCALCSFirmwareVersionCommand(ctx, x); } } else { (0, context_1.putBytes)(ctx, 'Payload', x); } (0, context_1.putUnparsedBytes)(x); } function parseEncodedLength(ctx, x, name) { const lenSz = (0, common_1.parseLength)(x, 0); (0, context_1.putBytes)(ctx, name, (0, util_1.getBytes)(x, lenSz.size), String(lenSz.length)); return lenSz.length; } async function parseGbcsMessage(text, lookupKey, acbEui) { const ctx = { lookupKey, acbEui, output: {}, current: [], decryptionList: [], }; let x = (0, util_1.parseHexString)(text); if (x.input.byte(0) !== 0xdd && x.input.byte(0) !== 0xdf) { // input could be base64 encoded const y = (0, util_1.parseBase64String)(text); if (y.input.byte(0) === 0xdd || y.input.byte(0) === 0xdf) { // assume base64 encoding if it starts with a known tag x = y; } } let cipherInfo; if (x.input.byte(0) === 0xdd) { if (x.input.byte(1) === 0x00) { cipherInfo = await parseGeneralCiphering(ctx, x); } else { throw new Error('GBT not supported'); } } else if (x.input.byte(0) === 0xdf) { cipherInfo = await parseGeneralSigning(ctx, x); } else { throw new Error('unknown frame format'); } if (ctx.decryptionList.length > 0) { await handleDecryptGbcsData(ctx, cipherInfo, lookupKey); } return ctx.output; } async function handleDecryptGbcsData(ctx, cipherInfo, lookupKey) { const pubKey = await lookupKey(cipherInfo.origSysTitle, 'KA', {}); const prvKey = await lookupKey(cipherInfo.supplimentryRemotePartyId ?? cipherInfo.recipSysTitle, 'KA', { privateKey: true, }); const aesKey = (0, crypto_2.deriveKeyFromPair)(prvKey, pubKey, cipherInfo, 'encryption'); for (let i = 0; i < ctx.decryptionList.length; i++) { (0, context_1.putSeparator)(ctx, `Decrypted Payload ${i}`); ctx.decryptionList[i](cipherInfo, aesKey); } } //# sourceMappingURL=parser.js.map