Zigbee Certificate-Based Key Establishment (CBKE)

1. Introduction

The Zigbee Smart Energy (SE) profile[1] is a framework specifically designed for energy management applications. Devices following the SE profile can interoperate in energy management systems, providing utilities and consumers with tools to monitor and optimize energy usage. Because of the type of data and control within the SE network, application security is a key requirement. The application uses link keys, which are optional in the ZigBee and ZigBee Pro stack profiles but are required within an SE network.
This page details the requirements for SE devices and gives an example of how to use them.

2. Install Code

Installation Code or Install Code is one of the requirements used to establish the preconfigured link key of a device in an SE network.
During the manufacturing process, a random Installation Code is created for each of the SE devices. The associated preconfigured link key is derived using the Matyas-Meyer Oseas (MMO) hashing function and programmed in the device.
More information about the Installation Code can be found on the Zigbee Install Code wiki page.

For devices to join the network, the link keys must be added to the Trust Center using the following API:

 /* On Trust Center, add a TC Link Key derived from the given install code */
 ZbSecAddDeviceLinkKeyByInstallCode( stZigbeeAppInfo.pstZigbee, dlPartnerExtendedAdress, (uint8_t *)szLinkKeyInstallCode , ZB_SEC_KEYSIZE + 2 );

3. Zigbee Key Establishment (ZKE) cluster

ZKE cluster is a cluster for managing secure communications in ZigBee. It is used to establish a shared secret key between two devices, which can then be used to encrypt and decrypt messages exchanged between them.
The ZKE cluster uses a combination of asymmetric and symmetric key cryptography to establish and distribute keys. At the end of the process, both nodes will have a shared secret key. It is used to encrypt and decrypt messages exchanged between them.
The key agreement scheme is the process of establishing the shared secret key without sending it over the air. It is done between an Initiator, who starts the process, and a Responder.
Two types of key agreement exist:

  • Symmetric Key Key Establishment (SKKE)
  • Public Key Key Establishment (PKKE)

PKKE involves an exchange of public keys, which can be static (long-term) or ephemeral (temporary), to mutually authenticate devices and establish a shared secret key for secure communications.
The static public key of the device can be transported independently, so relying on implicit trust, or as part of an explicit certificate signed by a Certificate Authority (CA), which is called Certificate-Based Key Establishment (CBKE). If using CBKE, static public keys Su and Sv are exchanged and verified using the public key of the CA.
In Zigbee Smart Energy, CBKE is a critical component for ensuring device identity.

4. Certificate Authority

Certificates provide a mechanism for cryptographically binding a public key to the identity and characteristics of a device.

Public Key Certificate

Smart Energy 1.4 certificate fields are:

  • Type (type of certificate = 0, implicit no extensions)
  • Serial number (serial number of the certificate)
  • Curve definition (one-byte elliptic curve identifier; for the Crypto Suite 2 Cipher Suite this shall be 0x0D)
  • Hash algorithm (one-byte hash identifier; for the Crypto Suite 2 Cipher Suite, this shall be 0x08 indicating that AES-MMO is used)
  • Issuer
  • Valid from date (time from which the certificate is valid)
  • Valid time (the seconds from the ValidFrom time for which the certificate is considered valid)
  • Subject identifier (that is, the extended 64-bit IEEE 802.15.4 address of the device)
  • Key usage
  • Public key (37-byte compressed public key value from which the public key of the Subject is reconstructed)
Info white.png Information
Certicom's ZigBee Smart Energy certificate service provides ZigBee Smart Energy certificates to manufacturers whose products have been certified by the CSA.

5. Certificate-Based Key Establishment (CBKE)

CBKE is considered to be a variant of PKKE, distinguished by its use of digital certificates generated from the public keys of devices. Each device has a static public key, and the CA generates a digital certificate from it. The certificate is signed by the CA and is used to replace the static public key in the PKKE procedure.

The following figure illustrates the five steps of the CBKE process in a simplified manner:

General Exchange for CBKE
PKKE
puce1.png

Obtain certificate from CA

puce2.png

Exchange Static Certificates and Ephemeral Data

puce3.png

Generate Key Bitstream: KeyBitGen() uses the elliptic curve Menezes–Qu–Vanstone (ECMQV) key agreement protocol.
The generated Key Bitstream Z is used as input to other KeyDerivation Function KDF() for generating the MAC Key and Key Data. The Key Data is the shared secret (for instance, the new link key that is itself required). Au and Av are the 64-bit extended addresses of the device.

puce4.png

Derive the Message Authentication Code (MAC) Key and exchange it with the other device to verify that the two devices have the same Key Data.

puce5.png

Confirm the key using the MAC

Our stack supports two cryptographic key establishment suites (suite1 and suite2). These key establishment suites allow varying degrees of security in trade of Complexity. In this case, suite2 is more robust and secure. However, it is more complex than suite1.
Consequently, the SE devices (Router, End Device, or Sleepy End Device) and Coordinator must agree on the suite to be used. CBKE configurations are defined in the ZbStartupCbkeT fields of ZbStartupT.

The Keep Alive cluster is created automatically by the stack when CBKE is used. It is part of the mechanism by which smart energy devices detect loss of communication with the trust center. This loss of communication results in a sequence of retries, network rejoin attempts, and ultimately an attempt to join a new trust center (TCSO).

6. CBKE application

6.1. Application configuration

In a normal scenario, the Coordinator acts as a trust center and CBKE is activated by filling the CBKE startup configuration on both sides, initiator (such as end device or router) and responder (coordinator).
The CBKE startup configurations are filled in the struct ZbStartupCbkeT of struct ZbStartupT, used for startup. Some important fields of ZbStartupCbkeT are defined as follows:

/** CBKE configuration parameters for ZbStartup. This configuration is only
 * applicable if the 'suite_mask' is non-zero. */
struct ZbStartupCbkeT {
    uint8_t endpoint;
    /**< Endpoint to assign ZCL Key Exchange cluster. Default is ZB_ENDPOINT_CBKE_DEFAULT (240) */

    uint16_t deviceId;
    /**< Device Id to assign to the endpoint created for the ZCL Key Exchange cluster.
     * Default is ZCL_DEVICE_METER */

    uint16_t suite_mask;
    /**< The Key Exchange suite bitmask. E.g. ZCL_KEY_SUITE_CBKE2_ECMQV for CBKE version 2 (cbke_v2). */

    struct ZbZclCbkeInfoT cbke_v1;
    /**< CBKE version 1 certificate and security keys configuration.
     * Only applicable if ZCL_KEY_SUITE_CBKE_ECMQV is set in suite_mask. */

    struct ZbZclCbke2InfoT cbke_v2;
    /**< CBKE version 2 certificate and security keys configuration.
     * Only applicable if ZCL_KEY_SUITE_CBKE2_ECMQV is set in suite_mask. */

    /* Keep Alive Server or Client */    
   bool tc_keepalive_server_enable;    

   /**< If CBKE is enabled (suite_mask != 0), this flag determines whether to allocate the     
   * Trust Center Keep Alive Server (true) or Client (false). The Trust Center should    
   * set this flag to true, and all other joiners should set this flag to false. */
 
   /* Keep Alive Server attributes */    
   uint8_t tc_keepalive_base;    

  /**< Trust Center Keep Alive Server 'Base' attribute value in minutes.    
  * If zero, let the stack choose a default value. */    

   uint16_t tc_keepalive_jitter;    
  /**< Trust Center Keep Alive Server 'Jitter' attribute value in seconds.     
  * If zero, let the stack choose a default value. */
 
    bool (*tcso_callback)(enum ZbTcsoStatusT status, void *arg);   
  /**< Application callback that is called to notify of any Trust Center Swap Out (TCSO)   
  * events initiated by the Keep Alive Client cluster. If the status is set to   
  * ZB_TCSO_STATUS_DISCOVERY_UNDERWAY, the application can return false to this callback   
  * to halt the TCSO process from continuing. This allows the application to dictate when   
  * to start TCSO; for example, during the next wake-cycle if the device is sleepy.   
  * The Keep Alive Client is also halted in this case, but will restart after the application  
  * calls ZbStartupTcsoStart to perform TCSO, or it calls ZbZclKeepAliveClientStart to restart   
  * the Keep Alive mechanism. If the application returns true to this callback, then the  
  * TCSO process will proceed normally. */   

   void *tcso_arg; /** Application callback argument for 'tcso_callback' */
};


On the "Joiner" (initiator) side, the configurations are filled as follows:

 /* Default configuration for Smart Energy*/
  ZbStartupConfigGetProSeDefaults( pstConfig );

 /* Using the preconfigured Link Key derived from the given install code*/
  memcpy( pstConfig->security.preconfiguredLinkKey, stZigbeeAppInfo.szLinkKey, ZB_SEC_KEYSIZE );

 /* Update CBKE Certificate & Keys */
  pstConfig->security.cbke.deviceId = APP_ZIGBEE_DEVICE_ID;
  pstConfig->security.cbke.endpoint = ZB_ENDPOINT_CBKE_DEFAULT; //Optional
 
 /* Update Keep Alive Client*/
  pstConfig->security.cbke.tc_keepalive_server_enable = false;
  pstConfig->security.cbke.tc_keepalive_base          = ZCL_KEEPALIVE_SERVER_BASE_DEFAULT;   //Optional
  pstConfig->security.cbke.tc_keepalive_jitter        = ZCL_KEEPALIVE_SERVER_JITTER_DEFAULT; //Optional
  pstConfig->security.cbke.tcso_callback              = APP_ZIGBEE_tcso_cb;                  //Optional
  pstConfig->security.cbke.tcso_arg                   = NULL;                                //Optional

#ifdef APPLICATION_USE_CBKE2
  pstConfig->security.cbke.suite_mask = ZCL_KEY_SUITE_CBKE2_ECMQV;
  memcpy( pstConfig->security.cbke.cbke_v2.cert, szZibgeeCbkeCert2, CBKE2_CERTIFICATE_SIZE );
  memcpy( pstConfig->security.cbke.cbke_v2.keys.publicCaKey, szZibgeeCbkeCaPublic2, CBKE2_COMPRESSED_PUBLIC_KEY_SIZE );
  memcpy( pstConfig->security.cbke.cbke_v2.keys.privateKey, szZibgeeCbkePrivate2, CBKE2_PRIVATE_KEY_SIZE );

#else // APPLICATION_USE_CBKE
  pstConfig->security.cbke.suite_mask = ZCL_KEY_SUITE_CBKE_ECMQV;
  memcpy( pstConfig->security.cbke.cbke_v1.cert, szZibgeeCbkeCert1, CBKE_CERTIFICATE_SIZE );
  memcpy( pstConfig->security.cbke.cbke_v1.keys.publicCaKey, szZibgeeCbkeCaPublic1, CBKE_COMPRESSED_PUBLIC_KEY_SIZE );
  memcpy( pstConfig->security.cbke.cbke_v1.keys.privateKey, szZibgeeCbkePrivate1, CBKE_PRIVATE_KEY_SIZE );
#endif // APPLICATION_USE_CBKE2


Info white.png Information
  • All optional fields are filled in ZbStartupConfigGetProSeDefaults().
  • APPLICATION_USE_CBKE2 and APPLICATION_USE_CBKE macros are used for demonstration purposes.


On the Coordinator (responder) side, the configurations are filled as follows:

/* Default configuration for Smart Energy*/
  ZbStartupConfigGetProSeDefaults( pstConfig );

/* Update CBKE Certificate & Keys  */
  pstConfig->security.trustCenterAddress = dlZigbeeExtendedAdress;
  pstConfig->security.cbke.deviceId = APP_ZIGBEE_DEVICE_ID;
  pstConfig->security.cbke.endpoint = ZB_ENDPOINT_CBKE_DEFAULT;   //Optional
 
/* Update Keep Alive Server */
  pstConfig->security.cbke.tc_keepalive_base          = ZCL_KEEPALIVE_SERVER_BASE_DEFAULT;  //Optional
  pstConfig->security.cbke.tc_keepalive_jitter        = ZCL_KEEPALIVE_SERVER_JITTER_DEFAULT;//Optional
  pstConfig->security.cbke.tc_keepalive_server_enable = true;
  pstConfig->security.cbke.tcso_callback              = APP_ZIGBEE_tcso_cb;  //Optional
  pstConfig->security.cbke.tcso_arg                   = NULL;                //Optional
#ifdef APPLICATION_USE_CBKE2
  pstConfig->security.cbke.suite_mask = ZCL_KEY_SUITE_CBKE2_ECMQV;
  memcpy( pstConfig->security.cbke.cbke_v2.cert, szZibgeeCbkeCert2, CBKE2_CERTIFICATE_SIZE );
  memcpy( pstConfig->security.cbke.cbke_v2.keys.publicCaKey, szZibgeeCbkeCaPublic2, CBKE2_COMPRESSED_PUBLIC_KEY_SIZE );
  memcpy( pstConfig->security.cbke.cbke_v2.keys.privateKey, szZibgeeCbkePrivate2, CBKE2_PRIVATE_KEY_SIZE );
#else // APPLICATION_USE_CBKE
  pstConfig->security.cbke.suite_mask = ZCL_KEY_SUITE_CBKE_ECMQV;
  memcpy( pstConfig->security.cbke.cbke_v1.cert, szZibgeeCbkeCert1, CBKE_CERTIFICATE_SIZE );
  memcpy( pstConfig->security.cbke.cbke_v1.keys.publicCaKey, szZibgeeCbkeCaPublic1, CBKE_COMPRESSED_PUBLIC_KEY_SIZE );
  memcpy( pstConfig->security.cbke.cbke_v1.keys.privateKey, szZibgeeCbkePrivate1, CBKE_PRIVATE_KEY_SIZE );
#endif // APPLICATION_USE_CBKE2


Info white.png Information
Usage of CBKE suite 2 is more secure and recommended

6.2. Insights into the Keep-Alive Cluster and Trust Center Swap-Out mechanism

The Zigbee Smart Energy profile specification outlines the Keep-Alive cluster and Trust Center (TC) Swap-out feature, including the default values for the Keep-Alive attributes.

Keep-Alive mechanism: Smart Energy routers use a keep-alive mechanism with the TC to detect if the TC is still available. Refer to chapter 5.4.2.2.3.4 Keep-Alive Method of the Zigbee Smart Energy Standard[1].

Keep-Alive Cluster: this cluster supports commands and attributes that help determine if communication with the TC is still available. The presence of the cluster indicates support for the Keep-Alive mechanism.

TC Keep-Alive Base Attribute: this attribute represents the base time (in minutes) for calculating each interval to check for TC contact. A random value (jitter) is added to this base time. The default value is 10 minutes (0x0A).

TC Keep-Alive Jitter Attribute: this attribute defines the range (in seconds) for the random value added to the TC Keep-Alive Base attribute when calculating each interval. The default value is 500 seconds (0x012C).

TCSO cooldown value (arbitrary): this is a delay before restarting the Keep-Alive retransmissions after a TCSO failure. It is fixed and set to five minutes to not continually hammer the network with rejoin attempts.

Default value usage: after power-up or reboot, a client device must use these default times until it fetches the updated values from the cluster attributes.

Keep-Alive failure detection: if a device fails to read the Keep-Alive Cluster attributes for three consecutive attempts, it considers the TC inaccessible and initiates a search.

Example configuration: using the following settings:

  • TC Keep-Alive Base Attribute : 1 minute
  • TC Keep-Alive Jitter Attribute : 5 seconds

This leads to a TCSO callback notification every eight minutes.
8 minutes = (3 * (1 minutes) ) + 5 minutes, where the factor 3 represents the number of successive failures to read the Keep-Alive Cluster attributes after which the TC is declared inaccessible.

For debug purposes, speed up TCSO testing by using minimal values (like the example above) for the coordinator Keep-Alive server cluster allocation.

6.3. Application scenario

As demonstrated above, CBKE is used to generate the Trust Center link key, which is used for encrypting the messages between the Coordinator (Trust Center) and the Joiner. The full CBKE procedure can be summarized as follows:

Step 1
Generate the certificates by the Certification Authority, for both Coordinator and Joiner.

Connectivity CBKE Certificates.png


Step 2
Activate CBKE on both ends by filling the CBKE startup configurations.

Connectivity CBKE Activation.png


Step 3
Check and identify the CBKE endpoint.

Connectivity CBKE checkEp.png


Step 4
Start the key Establishment procedure.

Connectivity CBKE Key Establishment.png


Connectivity CBKE Key Establishment2.png


Step 5
Exchange the Message Authentication Codes (MACs) between the two sides for result verification.

Connectivity CBKE MAC Exchange.png



In the Zigbee CBKE procedure, there is atimeout constraint for each step. No Terminate Key Establishment must be sent to the partner of the device that has timed out the operation (see part C.3.1 Key Establishment Cluster of the Zigbee Smart Energy Standard[1]).

In this stack, the timeout is calculated as follows:

Timeout = timeout slack value ( 10 s)  + partner timeout value indication}}
/* The slack time is added to the remote side's advertised timeout values.
* It accounts for network latency, etc. */
#define ZCL_KEY_TIMEOUT_SLACK_SEC          10U

After completing the CBKE procedure, the user can verify its success by reading the attribute value of ZB_BDB_KE_Status:

enum ZbZclKeyStatusT * cbke_statue;
enum ZbStatusCodeT eStatus = ZbBdbGetIndex(stZigbeeAppInfo.pstZigbee,ZB_BDB_KE_Status, &cbke_statue, sizeof(cbke_statue), 0); 
if(ZB_STATUS_SUCCESS == eStatus) 
{  
  if (cbke_statue != ZCL_KEY_STATUS_SUCCESS)  
     {    
       LOG_ERROR_APP( "Error, CBKE Procedure failed (0x%02X)", cbke_statue );  
     }  
} 
else 
{  
  LOG_ERROR_APP( "Error, ZbBdbGetIndex failed (0x%02X)", eStatus ); 
}

6.4. Dynamic CBKE activation

The standard says a CBKE procedure is necessary after the joining process, but in some scenarios it is desirable for an SE end device to control when to trigger the CBKE procedure. For example, the SE device may update the CBKE certificates and other configurations after joining the network, especially if the certificates have expired or if the server requests a renewal.

This feature is called dynamic CBKE activation (DCA). DCA is supported by this Zigbee Stack, and detailed instructions can be found below.

6.4.1. How to use DCA

If the SE end device needs to skip the CBKE at startup, set the ZB_BDB_TCLinkKeyExchangeMethod and ZB_BDB_TrustCenterRequiresKeyExchange parameters to 0 to prevent the CBKE procedure from starting automatically.

  /*Don't do CBKE by default during joining process*/
  int Attribute_value = 0 ; 
  ZbBdbSetIndex(stZigbeeAppInfo.pstZigbee,ZB_BDB_TCLinkKeyExchangeMethod,&Attribute_value,1,0);
  ZbBdbSetIndex(stZigbeeAppInfo.pstZigbee,ZB_BDB_TrustCenterRequiresKeyExchange,&Attribute_value,1,0);

When the SE end device decides to activate CBKE, it must call the function ZbZclKeWithDevice. This can be initiated, for instance, by pressing a button to request the CBKE to start.

void APPE_CBKE_confirm(uint64_t partnerAddr, uint16_t keSuite, enum ZbZclKeyStatusT key_status, void *arg)
{   
  LOG_INFO_APP("CBKE Confirm (status = 0x%02x) , partnerAddr ( 0x%016" PRIX64 " )", key_status , partnerAddr);
}
...

void APPE_Button3Action( void )
{
  enum ZclStatusCodeT eStatus;
  bool aps_req_key = 0;
  /* First, verify if Appli has already Join a Network  */ 
  if ( APP_ZIGBEE_IsAppliJoinNetwork() != false )
  {
  /* ZbZclKeWithDevice peforms all the steps required for CBKE between devices, including a Trust Center.
   * Note, CBKE with a Trust Center is already handled by the ZbStartup code, including Trust Cetner Swap Out (TCSO)
   *
   * If aps_req_key is true, and partnerAddr is not the TC, then an APS Request Key is sent to the TC with the partner
   * address set to the partnerAddr paramter.
   *
   * The callback is called once KE is done, or there was an error, only if ZbZclKeWithDevice returns ZCL_STATUS_SUCCESS. */
    LOG_INFO_APP( "SW3 PUSHED : CBKE Key Establishment Procedure Requested !!");
    eStatus = ZbZclKeWithDevice(stZigbeeAppInfo.pstZigbee, dlPartnerExtendedAdress, aps_req_key, APPE_CBKE_confirm /*callback*/, NULL/*callback argument*/);
    if (eStatus != ZCL_STATUS_SUCCESS) {
       LOG_ERROR_APP( "Error, ZbZclKeWithDevice failed (0x%02X)", eStatus );
    }
  }
}


Info white.png Information
  • It is worth mentioning that the CBKE procedure must be done before exchanging any application-level messages requiring link key encryption between the nodes over the air. Otherwise, these messages are dropped and ignored.
  • The CBKE completion status can be checked using the status passed through the APPE_CBKE_confirm() callback function.

7. References