Skip to content

Commit b98b141

Browse files
authored
fix: Better support for install codes (including deconz) (#1243)
1 parent bd3f58e commit b98b141

File tree

12 files changed

+473
-232
lines changed

12 files changed

+473
-232
lines changed

src/adapter/deconz/adapter/deconzAdapter.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,9 +299,8 @@ class DeconzAdapter extends Adapter {
299299
}
300300
}
301301

302-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
303302
public async addInstallCode(ieeeAddress: string, key: Buffer): Promise<void> {
304-
return await Promise.reject(new Error('Add install code is not supported'));
303+
await this.driver.writeLinkKey(ieeeAddress, ZSpec.Utils.aes128MmoHash(key));
305304
}
306305

307306
// eslint-disable-next-line @typescript-eslint/no-unused-vars

src/adapter/deconz/driver/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const PARAM = {
1515
CHANNEL_MASK: 0x0a,
1616
APS_EXT_PAN_ID: 0x0b,
1717
NETWORK_KEY: 0x18,
18+
LINK_KEY: 0x19,
1819
CHANNEL: 0x1c,
1920
PERMIT_JOIN: 0x21,
2021
WATCHDOG_TTL: 0x26,

src/adapter/deconz/driver/driver.ts

Lines changed: 73 additions & 67 deletions
Large diffs are not rendered by default.

src/adapter/deconz/driver/writer.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
/* istanbul ignore file */
2-
/* eslint-disable */
32

43
import * as stream from 'stream';
54

6-
// @ts-ignore
75
import slip from 'slip';
86

97
import {logger} from '../../../utils/logger';

src/adapter/ember/adapter/emberAdapter.ts

Lines changed: 11 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ import * as ZdoTypes from '../../../zspec/zdo/definition/tstypes';
1616
import {DeviceJoinedPayload, DeviceLeavePayload, ZclPayload} from '../../events';
1717
import {
1818
EMBER_HIGH_RAM_CONCENTRATOR,
19-
EMBER_INSTALL_CODE_CRC_SIZE,
20-
EMBER_INSTALL_CODE_SIZES,
2119
EMBER_LOW_RAM_CONCENTRATOR,
2220
EMBER_MIN_BROADCAST_ADDRESS,
2321
INTERPAN_APS_FRAME_TYPE,
@@ -70,8 +68,8 @@ import {
7068
SecManContext,
7169
SecManKey,
7270
} from '../types';
73-
import {aesMmoHashInit, initNetworkCache, initSecurityManagerContext} from '../utils/initters';
74-
import {halCommonCrc16, highByte, highLowToInt, lowByte, lowHighBytes} from '../utils/math';
71+
import {initNetworkCache, initSecurityManagerContext} from '../utils/initters';
72+
import {lowHighBytes} from '../utils/math';
7573
import {FIXED_ENDPOINTS} from './endpoints';
7674
import {EmberOneWaitress, OneWaitressEvents} from './oneWaitress';
7775

@@ -1192,19 +1190,14 @@ export class EmberAdapter extends Adapter {
11921190
// Rather than give the real link key, the backup contains a hashed version of the key.
11931191
// This is done to prevent a compromise of the backup data from compromising the current link keys.
11941192
// This is per the Smart Energy spec.
1195-
const [hashStatus, hashedKey] = await this.emberAesHashSimple(plaintextKey.contents);
1196-
1197-
if (hashStatus === SLStatus.OK) {
1198-
keyList.push({
1199-
deviceEui64: context.eui64,
1200-
key: {contents: hashedKey},
1201-
outgoingFrameCounter: apsKeyMeta.outgoingFrameCounter,
1202-
incomingFrameCounter: apsKeyMeta.incomingFrameCounter,
1203-
});
1204-
} else {
1205-
// this should never happen?
1206-
logger.error(`[BACKUP] Failed to hash link key at index ${i} with status=${SLStatus[hashStatus]}. Omitting from backup.`, NS);
1207-
}
1193+
const hashedKey = ZSpec.Utils.aes128MmoHash(plaintextKey.contents);
1194+
1195+
keyList.push({
1196+
deviceEui64: context.eui64,
1197+
key: {contents: hashedKey},
1198+
outgoingFrameCounter: apsKeyMeta.outgoingFrameCounter,
1199+
incomingFrameCounter: apsKeyMeta.incomingFrameCounter,
1200+
});
12081201
}
12091202
}
12101203

@@ -1494,26 +1487,6 @@ export class EmberAdapter extends Adapter {
14941487
return status;
14951488
}
14961489

1497-
/**
1498-
* This is a convenience method when the hash data is less than 255
1499-
* bytes. It inits, updates, and finalizes the hash in one function call.
1500-
*
1501-
* @param data const uint8_t* The data to hash. Expected of valid length (as in, not larger alloc)
1502-
*
1503-
* @returns An ::SLStatus value indicating EMBER_SUCCESS if the hash was
1504-
* calculated successfully. EMBER_INVALID_CALL if the block size is not a
1505-
* multiple of 16 bytes, and EMBER_INDEX_OUT_OF_RANGE is returned when the
1506-
* data exceeds the maximum limits of the hash function.
1507-
* @returns result uint8_t* The location where the result of the hash will be written.
1508-
*/
1509-
private async emberAesHashSimple(data: Buffer): Promise<[SLStatus, result: Buffer]> {
1510-
const context = aesMmoHashInit();
1511-
1512-
const [status, reContext] = await this.ezsp.ezspAesMmoHash(context, true, data);
1513-
1514-
return [status, reContext?.result];
1515-
}
1516-
15171490
/**
15181491
* Set the trust center policy bitmask using decision.
15191492
* @param decision
@@ -1716,43 +1689,10 @@ export class EmberAdapter extends Adapter {
17161689

17171690
// queued
17181691
public async addInstallCode(ieeeAddress: string, key: Buffer): Promise<void> {
1719-
// codes with CRC, check CRC before sending to NCP, otherwise let NCP handle
1720-
if (EMBER_INSTALL_CODE_SIZES.indexOf(key.length) !== -1) {
1721-
// Reverse the bits in a byte (uint8_t)
1722-
const reverse = (b: number): number => {
1723-
return (((((b * 0x0802) & 0x22110) | ((b * 0x8020) & 0x88440)) * 0x10101) >> 16) & 0xff;
1724-
};
1725-
let crc = 0xffff; // uint16_t
1726-
1727-
// Compute the CRC and verify that it matches.
1728-
// The bit reversals, byte swap, and ones' complement are due to differences between halCommonCrc16 and the Smart Energy version.
1729-
for (let index = 0; index < key.length - EMBER_INSTALL_CODE_CRC_SIZE; index++) {
1730-
crc = halCommonCrc16(reverse(key[index]), crc);
1731-
}
1732-
1733-
crc = ~highLowToInt(reverse(lowByte(crc)), reverse(highByte(crc))) & 0xffff;
1734-
1735-
if (
1736-
key[key.length - EMBER_INSTALL_CODE_CRC_SIZE] !== lowByte(crc) ||
1737-
key[key.length - EMBER_INSTALL_CODE_CRC_SIZE + 1] !== highByte(crc)
1738-
) {
1739-
throw new Error(`[ADD INSTALL CODE] Failed for '${ieeeAddress}'; invalid code CRC.`);
1740-
} else {
1741-
logger.debug(`[ADD INSTALL CODE] CRC validated for '${ieeeAddress}'.`, NS);
1742-
}
1743-
}
1744-
17451692
return await this.queue.execute<void>(async () => {
1746-
// Compute the key from the install code and CRC.
1747-
const [aesStatus, keyContents] = await this.emberAesHashSimple(key);
1748-
1749-
if (aesStatus !== SLStatus.OK) {
1750-
throw new Error(`[ADD INSTALL CODE] Failed AES hash for '${ieeeAddress}' with status=${SLStatus[aesStatus]}.`);
1751-
}
1752-
17531693
// Add the key to the transient key table.
17541694
// This will be used while the DUT joins.
1755-
const impStatus = await this.ezsp.ezspImportTransientKey(ieeeAddress as EUI64, {contents: keyContents});
1695+
const impStatus = await this.ezsp.ezspImportTransientKey(ieeeAddress as EUI64, {contents: ZSpec.Utils.aes128MmoHash(key)});
17561696

17571697
if (impStatus == SLStatus.OK) {
17581698
logger.debug(`[ADD INSTALL CODE] Success for '${ieeeAddress}'.`, NS);

src/adapter/ember/consts.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -77,23 +77,6 @@ export const EMBER_HIGH_RAM_CONCENTRATOR = 0xfff9;
7777
/** The short address of the trust center. This address never changes dynamically. */
7878
export const EMBER_TRUST_CENTER_NODE_ID = 0x0000;
7979

80-
/** The size of the CRC that is appended to an installation code. */
81-
export const EMBER_INSTALL_CODE_CRC_SIZE = 2;
82-
83-
/** The number of sizes of acceptable installation codes used in Certificate Based Key Establishment (CBKE). */
84-
export const EMBER_NUM_INSTALL_CODE_SIZES = 4;
85-
86-
/**
87-
* Various sizes of valid installation codes that are stored in the manufacturing tokens.
88-
* Note that each size includes 2 bytes of CRC appended to the end of the installation code.
89-
*/
90-
export const EMBER_INSTALL_CODE_SIZES = [
91-
6 + EMBER_INSTALL_CODE_CRC_SIZE,
92-
8 + EMBER_INSTALL_CODE_CRC_SIZE,
93-
12 + EMBER_INSTALL_CODE_CRC_SIZE,
94-
16 + EMBER_INSTALL_CODE_CRC_SIZE,
95-
];
96-
9780
/**
9881
* Default value for context's PSA algorithm permission (CCM* with 4 byte tag).
9982
* Only used by NCPs with secure key storage; define is mirrored here to allow

src/controller/controller.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,16 @@ class Controller extends events.EventEmitter<ControllerEventMap> {
271271
// match valid else asserted above
272272
key = Buffer.from(key.match(/.{1,2}/g)!.map((d) => parseInt(d, 16)));
273273

274-
await this.adapter.addInstallCode(ieeeAddr, key);
274+
// will throw if code cannot be fixed and is invalid
275+
const [adjustedKey, adjusted] = ZSpec.Utils.checkInstallCode(key, true);
276+
277+
if (adjusted) {
278+
logger.info(`Install code was adjusted for reason '${adjusted}'.`, NS);
279+
}
280+
281+
logger.info(`Adding install code for ${ieeeAddr}.`, NS);
282+
283+
await this.adapter.addInstallCode(ieeeAddr, adjustedKey);
275284
}
276285

277286
public async permitJoin(time: number, device?: Device): Promise<void> {

src/zspec/consts.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,13 @@ export const PAN_ID_SIZE = 2;
7272
export const EXTENDED_PAN_ID_SIZE = 8;
7373
/** Size of an encryption key in bytes. */
7474
export const DEFAULT_ENCRYPTION_KEY_SIZE = 16;
75+
/** Size of a AES-128-MMO (Matyas-Meyer-Oseas) block in bytes. */
76+
export const AES_MMO_128_BLOCK_SIZE = 16;
77+
/**
78+
* Valid install code sizes, including `INSTALL_CODE_CRC_SIZE`.
79+
*
80+
* NOTE: 18 is now standard, first for iterations, order after is important (8 before 10)!
81+
*/
82+
export const INSTALL_CODE_SIZES: ReadonlyArray<number> = [18, 8, 10, 14];
83+
/** Size of the CRC appended to install codes. */
84+
export const INSTALL_CODE_CRC_SIZE = 2;

0 commit comments

Comments
 (0)