Introduction
Z-Stack is a component of the SimpleLink CC13xx / CC26xx Software Development Kit and is a complete Zigbee 3.0 software solution.
The intention of this lab is to help developers use Z-Stack security features to protect centralized networks and their applications from attack. Topics that we will cover in this lab include:
- Using install codes
- Implementing a deny/allow list
- Updating security keys
The Zigbee 3.0 specification includes several security updates which are summarized in SWRA615. Please refer to this White Paper for security features which are not extensively covered in this lab. Other security types, such as those used by distributed networks, TouchLink, and Green Power, are outside the scope of this lab. More information regarding these modes are provided in the Z-Stack Overview section of the TI Z-Stack User's Guide.
Note: This lab is intended to be an informative guide concerning security development and may not offer the optimal solution for any given application.
Technical support
For any questions you may have, please refer to the TI Zigbee & Thread E2E Forum.
Prerequisites
Background
- Familiarity with the Zigbee 3.0 specification
- More information about the changes in Zigbee 3.0 compared to older specifications can be found in SWRA615
- Basic CCS knowledge
- Some basic familiarity with embedded programming.
Software
- Code Composer Studio (latest version) with support for CC13xx/CC26xx devices
- SimpleLink CC13xx / CC26xx SDK (latest)
- Uniflash
- Ubiqua Protocol Analyzer (optional for packet sniffing)
- Wireshark (optional for packet sniffing)
- TI Packet Sniffer (optional for packet sniffing with CC2531EMK)
- TI Packet Sniffer 2 (optional for packet sniffing with LaunchPads)
Hardware
- 2 x Zigbee-capable SimpleLink LaunchPads for running sample applications, review the SDK Release Notes for more information.
- 1 x LaunchPad or CC2531EMK for packet sniffing (optional)
- Micro USB Cables
- 1 x LaunchPad or CC2531EMK for packet sniffing (optional)
- Micro USB Cables
Recommended Reading
- Z-Stack Quick Start Guide
- TI Z-Stack User's Guide
- TI Z-Stack API Guide
- What's New in Zigbee 3.0 White Paper
Part 1: Using Install Codes
Purpose
Install codes enhance network security by avoiding APS encryption of the NWK key using the Global Trust Center Link Key (TCLK) typically required for joining devices. Using the key derived by the install code, which is shared between the Trust Center and joining device, ensures that no globally-known key is used to encrypt data over-the-air. In this part of the lab, we are going to set a shared install code between two devices to demonstrate this functionality.
Task 0: Erase the Flash Memory on your LaunchPads
Make sure that the flash memory has been completely erased on each of your LaunchPads before flashing any Zigbee projects. This can be done in Uniflash, here:
Task 1: Setting the Install Code of a Switch Router
We will start by loading the out-of-box ZR (Zigbee Router) switch project. In CCS, go to
File > Import > C/C++ > CCS Projects
and underSelect search-directory:
select the following search path:C:\ti\simplelink_cc13xx_cc26xx_sdk_<version>\examples\rtos\<LaunchPad variant>\zstack\zr_sw
Create a
static uint8_t installCode[INSTALL_CODE_LEN + INSTALL_CODE_CRC_LEN]
locally inside ofzcl_samplesw.c
, a 16-byte sequence followed by the proper 2-byte CRC. If the CRC is not pre-calculated then a default value should be provided so that it may be overwritten by an API procedure to follow. This example will use a value of{0x83,0xFE,0xD3,0x40,0x7A,0x93,0x97,0x23,0xA5,0xC6,0x39,0xB2,0x69,0x16,0xD5,0x05,0xC3,0xB5};
which produces the following APS LNK key once passed through a MMO hash function:66B6900981E1EE3CA4206B6B861C02BB
./********************************************************************* * LOCAL VARIABLES */ static uint8_t installCode[INSTALL_CODE_LEN + INSTALL_CODE_CRC_LEN] = {0x83,0xFE,0xD3,0x40,0x7A,0x93,0x97,0x23,0xA5,0xC6,0x39,0xB2,0x69,0x16,0xD5,0x05,0xC3,0xB5};
zcl_samplesw.c
Add the following code to the end of
zclSampleSw_Init
:if(BDB_DEFAULT_JOIN_USES_INSTALL_CODE_KEY == TRUE) { zstack_bdbSetActiveCentralizedLinkKeyReq_t zstack_bdbSetActiveCentralizedLinkKeyReq; memset(&zstack_bdbSetActiveCentralizedLinkKeyReq,0, sizeof(zstack_bdbSetActiveCentralizedLinkKeyReq_t)); zstack_bdbSetActiveCentralizedLinkKeyReq.zstack_CentralizedLinkKeyModes = zstack_UseInstallCode; zstack_bdbSetActiveCentralizedLinkKeyReq.pKey = OsalPort_malloc(INSTALL_CODE_LEN + INSTALL_CODE_CRC_LEN); OsalPort_memcpy(zstack_bdbSetActiveCentralizedLinkKeyReq.pKey,installCode,INSTALL_CODE_LEN + INSTALL_CODE_CRC_LEN); Zstackapi_bdbSetActiveCentralizedLinkKeyReq(appServiceTaskId, &zstack_bdbSetActiveCentralizedLinkKeyReq); OsalPort_free(zstack_bdbSetActiveCentralizedLinkKeyReq.pKey); }
zcl_samplesw.c
The
Zstackapi_bdbSetActiveCentralizedLinkKeyReq
API sets the local device's centralized link key to the one generated after passing the install code and CRC through a MMO hash function. If no CRC has been provided then it must be generated by placing the following at the beginning of the "if" statement above (for the example it is already calculated as 0xB5C3):zstack_bdbGenerateInstallCodeCRCReq_t zstack_bdbGenerateInstallCodeCRCReq; zstack_bdbGenerateInstallCodeCRCRsp_t zstack_bdbGenerateInstallCodeCRCRsp; OsalPort_memcpy(zstack_bdbGenerateInstallCodeCRCReq.installCode,installCode,INSTALL_CODE_LEN); Zstackapi_bdbGenerateInstallCodeCRCReq(appServiceTaskId,&zstack_bdbGenerateInstallCodeCRCReq, &zstack_bdbGenerateInstallCodeCRCRsp); installCode[INSTALL_CODE_LEN] = zstack_bdbGenerateInstallCodeCRCRsp.CRC & 0xFF; installCode[INSTALL_CODE_LEN + 1] = zstack_bdbGenerateInstallCodeCRCRsp.CRC >> 8;
zcl_samplesw.c
Set
BDB_DEFAULT_JOIN_USES_INSTALL_CODE_KEY
toTRUE
inbdb_interface.h
so that it is understood that install codes are used for joining.Debug the project inside CCS and open the Expressions and Memory Browser windows. Run the project and view the
zstack_user0Cfg
in the Expressions window to find the extended IEEE address of the ZR switch device. Alternatively, this value can be located in theFCFG1_BASE + EXTADDR_OFFSET
area of the Memory Browser defined asFCFG1_MAC_15_4_0
. The upper 32 bits represent the manufacturer-specific code (i.e. Texas Instruments) whereas the lower 32 bits are the unique device identification numbers.
You can optionally view the Device Info row of the Common User Interface, the MAC Address tab on Flash Programmer 2, or the Settings & Utilities tab of Uniflash to get the IEEE Address. This value will be used for the Trust Center in the following task.
Task 2: Setting the Install Code and Target Address of a Light Coordinator
Repeat Step 1 of Task 1, except with the zc_light (Zigbee Coordinator light) project:
C:\ti\simplelink_cc13xx_26xx_sdk_<version>\examples\rtos\<LaunchPad variant>\zstack\zc_light
Repeat Step 2 of Task 1, except in
zcl_samplelight.c
. Also add astatic uint8_t installCodeAddr[Z_EXTADDR_LEN]
variable to store the IEEE Address, which we discovered while debugging the ZR Switch project, in little endian format (least significant byte first). The example uses{0xCB,0x57,0x9C,0x18,0x00,0x4B,0x12,0x00};
but will of course vary since this address is unique to a particular device./********************************************************************* * LOCAL VARIABLES */ static uint8_t installCode[INSTALL_CODE_LEN + INSTALL_CODE_CRC_LEN] = {0x83,0xFE,0xD3,0x40,0x7A,0x93,0x97,0x23,0xA5,0xC6,0x39,0xB2,0x69,0x16,0xD5,0x05,0xC3,0xB5}; static uint8_t installCodeAddr[Z_EXTADDR_LEN] = {0x02, 0x23, 0xf4, 0x14, 0x00, 0x4b, 0x12, 0x00};
zcl_samplelight.c
Inside the
BDB_COMMISSIONING_FORMATION
case ofzclSampleLight_ProcessCommissioningStatus
, add the following:if(BDB_DEFAULT_JOIN_USES_INSTALL_CODE_KEY) { zstack_bdbAddInstallCodeReq_t zstack_bdbAddInstallCodeReq; memset(&zstack_bdbAddInstallCodeReq,0,sizeof(zstack_bdbAddInstallCodeReq_t)); OsalPort_memcpy(zstack_bdbAddInstallCodeReq.pExt, installCodeAddr, Z_EXTADDR_LEN); OsalPort_memcpy(zstack_bdbAddInstallCodeReq.pInstallCode, installCode, INSTALL_CODE_LEN + INSTALL_CODE_CRC_LEN); Zstackapi_bdbAddInstallCodeReq(appServiceTaskId, &zstack_bdbAddInstallCodeReq); }
zcl_samplelight.c
This is performed once during network formation by the Coordinator to ensure that the Trust Center adds all default install codes and extended addresses. If multiple joining devices are supported, this code block should be replicated for each unique IEEE Address and varying install codes (if desired).
As before, set
BDB_DEFAULT_JOIN_USES_INSTALL_CODE_KEY
toTRUE
inbdb_interface.h
.Debug and run the ZC Light project. If you have multiple LaunchPads connected to your PC, CCS may prompt you to select one of the connected LaunchPads via the box below:
The selected LaunchPad's serial number will be assigned to this project workspace so future program launches will automatically attempt to select this LaunchPad first if it is plugged in. More information about debugging with multiple probes can be found here.
Task 3: Commissioning and Viewing Results
If using a packet sniffer (optional), the link key created by the install code must be provided to the software program as an Application or Trust Center Link Key or else the APS packets will be encrypted and illegible. If using the example from Task 1 then the key from Step 2 can be directly inserted.
Once both projects are actively running, press BTN-1 on the ZC Light followed by BTN-1 on the ZR Switch to start the commissioning process. If the devices form the network and join successfully then pressing BTN-2 of the ZR Switch should be able to turn on LED-1 of the ZC Light.
Inability to provide the correct install codes or IEEE Addresses will result in failure for the devices to communicate past Association, at which point the joining device will attempt
BDBC_REC_SAME_NETWORK_RETRY_ATTEMPS + 1
times (defined inbdb_interface.h
) before giving up.
Part 2: Implementing a Deny/Allow List
Purpose
Denylists use network parameters to filter out known unwanted devices or leave a specifically undesired network. On the other side, allowlists can ensure that only certain devices or networks are allowed. The following tasks provide an example of how these can be implemented for a Zigbee application.
Task 1: Rejecting Joining Device Extended Addresses on a Coordinator
Open the previously used ZC light project, reset
BDB_DEFAULT_JOIN_USES_INSTALL_CODE_KEY
toFALSE
inbdb_interface.h
but addDENY_LIST
to theProject > Properties > CCS Build > ARM Compiler > Predefined Symbols
list so that the feature can easily be toggled on or off.On the previously used ZR switch project, reset
BDB_DEFAULT_JOIN_USES_INSTALL_CODE_KEY
toFALSE
inbdb_interface.h
.Define the extended address of a device that we do not want to join our network in
zcl_samplelight.c
. For this example, we will use the address of the ZR Switch from Part 1./********************************************************************* * LOCAL VARIABLES */ static uint8_t denyListAddr[Z_EXTADDR_LEN] = {0xCB,0x57,0x9C,0x18,0x00,0x4B,0x12,0x00};
zcl_samplelight.c
We will then verify that each joining device does not contain this unique identifier. This is best handled by the
zstackmsg_CmdIDs_ZDO_DEVICE_ANNOUNCE
callback which notifies the application whenever a device has joined the network. First, the callback must be registered inside theSetupZStackCallbacks
function:static void SetupZStackCallbacks(void) { zstack_devZDOCBReq_t zdoCBReq = {0}; // Register for Callbacks, turn on: // Device State Change, // ZDO Match Descriptor Response, zdoCBReq.has_devStateChange = true; zdoCBReq.devStateChange = true; zdoCBReq.has_matchDescRsp = true; zdoCBReq.matchDescRsp = true; zdoCBReq.has_ieeeAddrRsp = true; zdoCBReq.ieeeAddrRsp = true; #ifdef DENY_LIST zdoCBReq.has_deviceAnnounce = true; zdoCBReq.deviceAnnounce = true; #endif //DENY_LIST #if defined Z_POWER_TEST #if defined (POWER_TEST_POLL_DATA) zdoCBReq.has_deviceAnnounce = true; zdoCBReq.deviceAnnounce = true; #endif #endif // Z_POWER_TEST (void)Zstackapi_DevZDOCBReq(appServiceTaskId, &zdoCBReq); }
zcl_samplelight.c
Logic inside the
zstackmsg_CmdIDs_ZDO_DEVICE_ANNOUNCE
case ofzclSampleLight_processZStackMsgs
will process the joining device'sextendedAddr
, compare withdenyListAddr
, and form aZstackapi_ZdoMgmtLeaveReq
to send over-the-air if verified as an unwanted device.zstack_zdoMgmtLeaveReq
options are set so that the joining device removes its children and does not attempt to rejoin the network.case zstackmsg_CmdIDs_ZDO_DEVICE_ANNOUNCE: #if defined (Z_POWER_TEST) #if defined (POWER_TEST_POLL_DATA) { zstackmsg_zdoDeviceAnnounceInd_t *pInd; pInd = (zstackmsg_zdoDeviceAnnounceInd_t*)pMsg; // save the short address of the ZED to send ZCL test data powerTestZEDAddr = pInd->req.srcAddr; // start periodic timer for sending ZCL data to zed OsalPortTimers_startReloadTimer(appServiceTaskId, SAMPLEAPP_POWER_TEST_ZCL_DATA_EVT, Z_POWER_TEST_DATA_TX_INTERVAL); } #endif #else { zstackmsg_zdoDeviceAnnounceInd_t *pInd; pInd = (zstackmsg_zdoDeviceAnnounceInd_t*)pMsg; if(OsalPort_memcmp(pInd->req.devExtAddr,denyListAddr,Z_EXTADDR_LEN) == TRUE) { zstack_zdoMgmtLeaveReq_t zstack_zdoMgmtLeaveReq; OsalPort_memcpy(zstack_zdoMgmtLeaveReq.deviceAddress, pInd->req.devExtAddr, Z_EXTADDR_LEN); zstack_zdoMgmtLeaveReq.nwkAddr = pInd->req.devAddr; zstack_zdoMgmtLeaveReq.options.rejoin = FALSE; zstack_zdoMgmtLeaveReq.options.removeChildren = TRUE; Zstackapi_ZdoMgmtLeaveReq(appServiceTaskId, &zstack_zdoMgmtLeaveReq); } } #endif break;
zcl_samplelight.c
After erasing previous flash memory, programming the device, and running the project in the CCS debugger, a joining device with the IEEE address
00:12:4B:00:14:F4:36:01
will be sent a Management Leave Request. All other devices will be allowed to join the network successfully. Multiple extended addresses can be added to the denylist and processed inside ofzstackmsg_CmdIDs_ZDO_DEVICE_ANNOUNCE
using a for loop.
Task 2: Filtering Available Networks on a Router
Re-program the ZC LaunchPad without the denylist feature if it will interfere with this portion of the lab (erasing all flash is not needed, since the previous PANID will be used, which was stored in flash). Then open the ZR switch project, confirm
BDB_DEFAULT_JOIN_USES_INSTALL_CODE_KEY
isFALSE
inbdb_interface.h
, and addDENY_LIST
to theProject > Properties > CCS Build > ARM Compiler > Predefined Symbols
list.Define the Pan ID of the network that you do not want to join in
zcl_samplesw.c
, for examplestatic uint16_t denyListAddr = 0x5C4F;
This time we will use the
zstackmsg_CmdIDs_BDB_FILTER_NWK_DESCRIPTOR_IND
case ofzclSampleSw_processZStackMsgs
to determine if we want to join the available networks that were found based on the Network Descriptors provided.case zstackmsg_CmdIDs_BDB_FILTER_NWK_DESCRIPTOR_IND: { /* User logic to remove networks that do not want to join * Networks to be removed can be released with Zstackapi_bdbNwkDescFreeReq */ #ifdef DENY_LIST zstackmsg_bdbFilterNwkDescriptorInd_t *pInd = (zstackmsg_bdbFilterNwkDescriptorInd_t*)pMsg; zstack_bdbNwkDescFreeReq_t zstack_bdbNwkDescFreeReq; uint8_t i = 0; for (i = 0; i < pInd->bdbFilterNetworkDesc.count; i++) { if (pInd->bdbFilterNetworkDesc.pBDBListNwk->panId == denyListAddr) { zstack_bdbNwkDescFreeReq.nodeDescToRemove = pInd->bdbFilterNetworkDesc.pBDBListNwk; Zstackapi_bdbNwkDescFreeReq(appServiceTaskId, &zstack_bdbNwkDescFreeReq); } pInd->bdbFilterNetworkDesc.pBDBListNwk++; } #endif //DENY_LIST Zstackapi_bdbFilterNwkDescComplete(appServiceTaskId); } break;
zcl_samplesw.c
If the
panId
of apBDBListNwk
is matched todenyListAddr
then the correspondingnetworkDesc_t
is processed through theZstackapi_bdbNwkDescFreeReq
API to release it from the list of networks found during the discovery of suitable networks to join. Multiple descriptors can be removed beforeZstackapi_bdbFilterNwkDescComplete
indicates that all remaining networks found can attempt joining. An Association Request will not be sent to the denylisted networks who indicate that they are open and permitted to join. As before, this same implementation can be used as an allowlist if only the defined pan ID's are not filtered out.
Part 3: Updating Network Security Keys
Default network keys are determined by the DEFAULT_KEY
macro in zstack_config.h
, where an initial value of {0}
allows for a random assignment. But some applications may desire to further implement security policies by updating the network key, for example at regular periodic intervals as controlled by the Trust Center. This can be accomplished through use of the Zstackapi_secNwkKey*
APIs. The example below uses the ZC Light project to update the NWK key of all devices on the network when the LaunchPad's BTN-2 button is pressed. It first reads its own active key with Zstackapi_secNwkKeyGetReq
(solely for debugging purposes) and then sets a random alternate key through Zstackapi_secNwkKeySetReq
. After broadcasting a Zstackapi_secNwkKeyUpdateReq
to update all network device's alternate key, Zstackapi_secNwkKeySwitchReq
will send an over-the-air message commanding them to use this alternate key as the active key. All NWK keys used are stored and maintained in the Trust Center.
Warning
Be sure to clean and rebuild both projects with DENY_LIST
disabled and BDB_DEFAULT_JOIN_USES_INSTALL_CODE_KEY
set to FALSE
.
//Button 2
if(key == CONFIG_BTN_RIGHT)
{
static uint8_t seqNum = 0;
zstack_secNwkKeyGetReq_t zstack_secNwkKeyGetReq;
zstack_secNwkKeyGetRsp_t zstack_secNwkKeyGetRsp;
zstack_secNwkKeySetReq_t zstack_secNwkKeySetReq;
zstack_secNwkKeyUpdateReq_t zstack_secNwkKeyUpdateReq;
zstack_secNwkKeySwitchReq_t zstack_secNwkKeySwitchReq;
seqNum++;
zstack_secNwkKeyGetReq.activeKey = TRUE;
Zstackapi_secNwkKeyGetReq(appServiceTaskId, &zstack_secNwkKeyGetReq, &zstack_secNwkKeyGetRsp);
zstack_secNwkKeySetReq.activeKey = FALSE;
zstack_secNwkKeySetReq.seqNum = seqNum;
zstack_secNwkKeySetReq.has_key = FALSE;
Zstackapi_secNwkKeySetReq(appServiceTaskId, &zstack_secNwkKeySetReq);
zstack_secNwkKeyUpdateReq.dstAddr = 0xFFFF;
zstack_secNwkKeyUpdateReq.seqNum = seqNum;
Zstackapi_secNwkKeyUpdateReq(appServiceTaskId, &zstack_secNwkKeyUpdateReq);
zstack_secNwkKeySwitchReq.dstAddr = 0xFFFF;
zstack_secNwkKeySwitchReq.seqNum = seqNum;
Zstackapi_secNwkKeySwitchReq(appServiceTaskId, &zstack_secNwkKeySwitchReq);
}
zclSampleLight_processKey in zcl_samplelight.c
Communication afterwards will continue like normal but using the new network key. Performing the update will also reset the network frame counter, which is mandated to be persistent across factory new resets as a security means towards preventing replay attacks. Sniffer devices which were active during this process will store the new keys, but all other devices will be unable to decipher any NWK-encrypted packets that follow. The example uses a random key through zstack_secNwkKeySetReq.has_key = FALSE;
, however a custom key can be used by setting this option to TRUE
and populating the key
field with a 16-byte value. Specific devices can solely have their NWK keys affected by modifying dstAddr
with the targeted network address.
Next Recommended Zigbee SimpleLink Academy Labs
This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.