Skip to content

Commit

Permalink
Merge pull request #3418 from manup/zgp_proxy
Browse files Browse the repository at this point in the history
Support ZGP proxies
  • Loading branch information
manup committed Oct 15, 2020
2 parents 6da43b2 + 1d5e17a commit b1bd861
Show file tree
Hide file tree
Showing 7 changed files with 257 additions and 138 deletions.
7 changes: 7 additions & 0 deletions de_web.pro
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ contains(QMAKE_SPEC_T,.*linux.*) {
DEFINES += HAS_SQLITE3
PKGCONFIG += sqlite3
}

packagesExist(openssl) {
DEFINES += HAS_OPENSSL
PKGCONFIG += openssl
}
}

unix:LIBS += -L../.. -ldeCONZ
Expand Down Expand Up @@ -96,6 +101,7 @@ HEADERS = bindings.h \
event.h \
gateway.h \
gateway_scanner.h \
green_power.h \
group.h \
group_info.h \
json.h \
Expand Down Expand Up @@ -130,6 +136,7 @@ SOURCES = authorisation.cpp \
firmware_update.cpp \
gateway.cpp \
gateway_scanner.cpp \
green_power.cpp \
group.cpp \
group_info.cpp \
gw_uuid.cpp \
Expand Down
14 changes: 6 additions & 8 deletions de_web_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1441,7 +1441,7 @@ void DeRestPluginPrivate::gpDataIndication(const deCONZ::GpDataIndication &ind)
// Maybe except for Hue Tap to keep compatibility? -- Maybe.

quint8 gpdDeviceId;
quint8 gpdKey[16];
GpKey_t gpdKey = { 0 };
quint32 gpdMIC = 0;
quint32 gpdOutgoingCounter = 0;
deCONZ::GPCommissioningOptions options;
Expand Down Expand Up @@ -1469,22 +1469,20 @@ void DeRestPluginPrivate::gpDataIndication(const deCONZ::GpDataIndication &ind)
for (int i = 0; i < 16; i++)
{
if (stream.atEnd()) { return; }
stream >> gpdKey[i];

stream >> gpdKey.at(i);
}

if (extOptions.bits.gpdKeyEncryption)
{
// TODO decrypt key
// When GPDkeyPresent sub-field is set
// to 0b1 and the GPDkeyEncryption sub-field is set to 0b1, both fields GPDkey and GPDkeyMIC are
// present; the field GPDkey contains the gpdSecurityKey, of the type as indicated in the gpdSecurityKey-
// Type, encrypted with the default TC-LK (see A.3.3.3.3) as described inA.3.7.1.2.3; and the GPDk-
// eyMIC field contains the MIC for the encrypted GPD key, calculated as described in A.3.7.1.2.3.
if (stream.atEnd()) { return; }

// (TC-LK), ‘ZigBeeAlliance09’.
gpdKey = GP_DecryptSecurityKey(ind.gpdSrcId(), gpdKey);

if (stream.atEnd()) { return; }
stream >> gpdMIC;
}
}
Expand All @@ -1510,12 +1508,12 @@ void DeRestPluginPrivate::gpDataIndication(const deCONZ::GpDataIndication &ind)

Sensor *sensor = getSensorNodeForFingerPrint(ind.gpdSrcId(), fp, QLatin1String("ZGPSwitch"));

if (searchSensorsState == SearchSensorsActive)
if (searchSensorsState == SearchSensorsActive && extOptions.bits.gpdKeyEncryption)
{
const QDateTime now = QDateTime::currentDateTime();
if (!sensor || !sensor->lastRx().isValid() || sensor->lastRx().secsTo(now) > 5)
{
sendGPPairing(ind.gpdSrcId(), 0xdd09, gpdDeviceId, gpdOutgoingCounter, gpdKey);
GP_SendPairing(ind.gpdSrcId(), 0xdd09, gpdDeviceId, gpdOutgoingCounter, gpdKey, apsCtrl, zclSeq++);
}
}

Expand Down
6 changes: 1 addition & 5 deletions de_web_plugin_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "resource.h"
#include "daylight.h"
#include "event.h"
#include "green_power.h"
#include "resource.h"
#include "rest_node_base.h"
#include "light_node.h"
Expand Down Expand Up @@ -183,7 +184,6 @@
#define MULTISTATE_INPUT_CLUSTER_ID 0x0012
#define OTAU_CLUSTER_ID 0x0019
#define POLL_CONTROL_CLUSTER_ID 0x0020
#define GREEN_POWER_CLUSTER_ID 0x0021
#define DOOR_LOCK_CLUSTER_ID 0x0101
#define WINDOW_COVERING_CLUSTER_ID 0x0102
#define THERMOSTAT_CLUSTER_ID 0x0201
Expand Down Expand Up @@ -214,8 +214,6 @@

#define IAS_ZONE_CLUSTER_ATTR_ZONE_STATUS_ID 0x0002

#define GREEN_POWER_ENDPOINT 0xf2

#define ONOFF_COMMAND_OFF 0x00
#define ONOFF_COMMAND_ON 0x01
#define ONOFF_COMMAND_TOGGLE 0x02
Expand Down Expand Up @@ -1133,8 +1131,6 @@ class DeRestPluginPrivate : public QObject
// Permit join
void initPermitJoin();
bool setPermitJoinDuration(uint8_t duration);
bool sendGPProxyCommissioningMode();
bool sendGPPairing(quint32 gpdSrcId, quint16 sinkGroupId, quint8 deviceId, quint32 frameCounter, const quint8 *key);

// Otau
void initOtau();
Expand Down
6 changes: 6 additions & 0 deletions general.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3323,6 +3323,12 @@ devices can operate on either battery or mains power, and can have a wide variet
<attribute id="0x0007" name="Active Functionality" type="bmp24" default="0xffffff" access="r" required="m" showas="hex">
<description>The optional GP functionality supported by this sink that is active.</description>
</attribute>
<attribute id="0x0020" name="Shared Security Key Type" type="bmp8" default="0x00" access="r" required="m" showas="hex">
</attribute>
<attribute id="0x0021" name="Shared Security Key" type="seckey" access="r" required="m" showas="hex">
</attribute>
<attribute id="0x0022" name="GP Link Key" type="seckey" access="r" required="m" showas="hex">
</attribute>
</server>
<client>
</client>
Expand Down
207 changes: 207 additions & 0 deletions green_power.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
#include <array>
#ifdef HAS_OPENSSL
#include <openssl/aes.h>
#include <openssl/evp.h>
#endif
#include <string>
#include "green_power.h"

// this code is based on
// https://github.com/Smanar/Zigbee_firmware/blob/master/Encryption.cpp

#define AES_KEYLENGTH 128
#define AES_BLOCK_SIZE 16

const unsigned char defaultTCLinkKey[] = { 0x5A, 0x69, 0x67, 0x42, 0x65, 0x65, 0x41, 0x6C, 0x6C, 0x69, 0x61, 0x6E, 0x63, 0x65, 0x30, 0x39 };

// From https://github.com/Koenkk/zigbee-herdsman/blob/master/src/controller/greenPower.ts
/*!
*/
GpKey_t GP_DecryptSecurityKey(quint32 sourceID, const GpKey_t &securityKey)
{
GpKey_t result = { 0 };

#ifdef HAS_OPENSSL

unsigned char nonce[13]; // u8 source address, u32 frame counter, u8 security control
unsigned char sourceIDInBytes[4];

sourceIDInBytes[0] = sourceID & 0x000000ff;
sourceIDInBytes[1] = (sourceID & 0x0000ff00) >> 8;
sourceIDInBytes[2] = (sourceID & 0x00ff0000) >> 16;
sourceIDInBytes[3] = (sourceID & 0xff000000) >> 24;

for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
nonce[4 * i + j] = sourceIDInBytes[j];
}
}

nonce[12] = 0x05;

// buffers for encryption and decryption
constexpr size_t encryptLength = ((GP_SECURITY_KEY_SIZE + AES_BLOCK_SIZE) / AES_BLOCK_SIZE) * AES_BLOCK_SIZE;
std::array<unsigned char, encryptLength> encryptBuf = { 0 };

EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
int outlen = 0;

/* Set cipher type and mode */
EVP_EncryptInit_ex(ctx, EVP_aes_128_ccm(), NULL, NULL, NULL);

/* Set nonce length if default 96 bits is not appropriate */
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, sizeof(nonce), NULL);

/* Initialise key and IV */
EVP_EncryptInit_ex(ctx, NULL, NULL, defaultTCLinkKey, nonce);

/* Encrypt plaintext: can only be called once */
EVP_EncryptUpdate(ctx, encryptBuf.data(), &outlen, securityKey.data(), static_cast<int>(securityKey.size()));
EVP_CIPHER_CTX_free(ctx);

std::copy(encryptBuf.begin(), encryptBuf.begin() + result.size(), result.begin());

#endif // HAS_OPENSSL

return result;
}

/*! Send Commissioning Mode command to GP proxy device.
*/
bool GP_SendProxyCommissioningMode(deCONZ::ApsController *apsCtrl, quint8 zclSeqNo)
{
deCONZ::ApsDataRequest req;

req.setDstAddressMode(deCONZ::ApsNwkAddress);
req.dstAddress().setNwk(deCONZ::BroadcastRouters);
req.setProfileId(GP_PROFILE_ID);
req.setClusterId(GREEN_POWER_CLUSTER_ID);
req.setDstEndpoint(GREEN_POWER_ENDPOINT);
req.setSrcEndpoint(GREEN_POWER_ENDPOINT);
req.setTxOptions(nullptr);
req.setRadius(0);

QDataStream stream(&req.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);

deCONZ::ZclFrame zclFrame;

zclFrame.setSequenceNumber(zclSeqNo);
zclFrame.setCommandId(0x02); // commissioning mode
zclFrame.setFrameControl(deCONZ::ZclFCClusterCommand |
deCONZ::ZclFCDirectionServerToClient |
deCONZ::ZclFCDisableDefaultResponse);

{ // payload
QDataStream stream(&zclFrame.payload(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);

quint8 options = 0x0b; // enter commissioning mode, exit on window expire
quint16 window = 40;
stream << options;
stream << window;
}

{ // ZCL frame

QDataStream stream(&req.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
zclFrame.writeToStream(stream);
}

// broadcast
if (apsCtrl->apsdeDataRequest(req) == deCONZ::Success)
{
DBG_Printf(DBG_INFO, "send GP proxy commissioning mode\n");
return true;
}

DBG_Printf(DBG_INFO, "send GP proxy commissioning mode failed\n");
return false;
}

/*! Send Pair command to GP proxy device.
*/
bool GP_SendPairing(quint32 gpdSrcId, quint16 sinkGroupId, quint8 deviceId, quint32 frameCounter, const GpKey_t &key, deCONZ::ApsController *apsCtrl, quint8 zclSeqNo)
{
deCONZ::ApsDataRequest req;

req.setDstAddressMode(deCONZ::ApsNwkAddress);
req.dstAddress().setNwk(deCONZ::BroadcastRouters);
req.setProfileId(GP_PROFILE_ID);
req.setClusterId(GREEN_POWER_CLUSTER_ID);
req.setDstEndpoint(GREEN_POWER_ENDPOINT);
req.setSrcEndpoint(GREEN_POWER_ENDPOINT);
req.setTxOptions(nullptr);
req.setRadius(0);

QDataStream stream(&req.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);

deCONZ::ZclFrame zclFrame;

zclFrame.setSequenceNumber(zclSeqNo);
zclFrame.setCommandId(0x01); // pairing
zclFrame.setFrameControl(deCONZ::ZclFCClusterCommand |
deCONZ::ZclFCDirectionServerToClient |
deCONZ::ZclFCDisableDefaultResponse);

{ // payload
QDataStream stream(&zclFrame.payload(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);

// 0..2: applicationID
// 3: add sink
// 4: remove gpd
// 5..6: communication mode
// 7: gpd fixed
quint8 options0 = 0xc8; // bits 0..7: add sink, enter commissioning mode, exit on window expire

// 0 / 8: gpd mac seq number capabilities
// 1..2 / 9..10: security level
// 3..5 / 11..13: security key type
// 6 / 14: frame counter present
// 7 / 15: gpd security key present

// The GPDsecurityFrameCounter field shall be present whenever the AddSink sub-field of the Options field is set to 0b1;

quint8 options1 = 0xe5;
// bits 8..15: security level 0b10 (Full (4B) frame counter and full (4B) MIC only)
// key type 0b100 (individual, out- of-the-box GPD key),
// frame counter present, security key present
quint8 options2 = 0x00;
stream << options0;
stream << options1;
stream << options2;
stream << gpdSrcId;
stream << sinkGroupId;
stream << deviceId;
stream << frameCounter;

{
for (size_t i = 0; i < 16; i++)
{
stream << key[i];
}
}
}

{ // ZCL frame

QDataStream stream(&req.asdu(), QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
zclFrame.writeToStream(stream);
}

// broadcast
if (apsCtrl->apsdeDataRequest(req) == deCONZ::Success)
{
DBG_Printf(DBG_INFO, "send GP pairing\n");
return true;
}

DBG_Printf(DBG_INFO, "send GP pairing\n");
return false;
}
28 changes: 28 additions & 0 deletions green_power.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (c) 2020 dresden elektronik ingenieurtechnik gmbh.
* All rights reserved.
*
* The software in this package is published under the terms of the BSD
* style license a copy of which has been included with this distribution in
* the LICENSE.txt file.
*
*/


#ifndef GREEN_POWER_H
#define GREEN_POWER_H

#include <array>
#include <deconz.h>

#define GREEN_POWER_CLUSTER_ID 0x0021
#define GREEN_POWER_ENDPOINT 0xf2
#define GP_SECURITY_KEY_SIZE 16

using GpKey_t = std::array<unsigned char, GP_SECURITY_KEY_SIZE>;

GpKey_t GP_DecryptSecurityKey(quint32 sourceID, const GpKey_t &securityKey);
bool GP_SendProxyCommissioningMode(deCONZ::ApsController *apsCtrl, quint8 zclSeqNo);
bool GP_SendPairing(quint32 gpdSrcId, quint16 sinkGroupId, quint8 deviceId, quint32 frameCounter, const GpKey_t &key, deCONZ::ApsController *apsCtrl, quint8 zclSeqNo);

#endif // GREEN_POWER_H

0 comments on commit b1bd861

Please sign in to comment.