The Connect stack supports sending encrypted messages using a pre-shared (AES-128) key, which must be common for the whole network (hereinafter referred to as network key). However, sometimes it is not feasible to pre-share the key (for example burning the key to the device at production) or apply the key manually (like typing in on some kind of console as the UART of the device). Fortunately, it is possible to create a shared key (hereinafter referred to as shared key) between two devices using the Diffie-Hellman algorithm while any third party will not have the information to recover the key from the data sent over the communication medium. This key however unique between every two parties, so the current implementation will use this shared key to securely distribute the network key to other devices. Note, that the method used in this example does not protect against physical access to the devices or against man-in-the-middle attack (MITM).
The provided method applicable for any Connect based application with any EFR32 device which supports Connect, this article uses Connect (SoC): Empty Example. In this example, we will use BRD4255A radio boards.
Open the Connect (SoC): Empty Example through Simplicity Studio project wizard.
In AppBuilder enable mbedTLS plugin to be able to use mbedTLS API functions:
Also, enable Security AES to enable security for Connect messages:
The complete example enables several plugins and options for working with CLI which are not detailed here as their description is out of the scope of this document, but the attached files configured properly.
When the setting is done in AppBuilder and the files are generated, the next step is implementing the code in the
flex-callbacks.c. Just as the settings in AppBuilder, the complete code is not described in this document please refer to the resources described in prerequisites.
Although the same code runs on both (each) devices, one of the devices will act as the server and the others will be clients (if a device becomes a server or a client depends on the executed CLI commands only).
There are only four functions implemented in the current application to support ECDH key exchange:
static bool seedRandomNumber(void)
static void clearEcdhContext(void)
static bool setupEcdhPeer(mbedtls_ecdh_context *peerCtx, unsigned char *ecpPointX, unsigned char *ecpPointY)
static bool generatePeerKeySecret(mbedtls_ecdh_context *peerCtx, unsigned char *ecpPointX,unsigned char *ecpPointY)
These functions are built on mbedTLS API calls. For reference of these API calls navigate to mbedTLS documentation.
The functions above are ported from the ARM mbedTLS
ecdh_curve25519.c example code.
Besides the functions above the are several CLI related functions that help to configure the devices and allow them to set up the network, generating keys, sending messages. These functions are not detailed here, please refer to the provided source code.
Completing the steps above, compile and run the firmware.
If everything went well, a CLI interface should be available where the user can issue the necessary commands.
Private and public keys are generated once, at startup (in
emberAfMainInitCallback()) and will be reused for subsequent generations of shared keys.
Assume there are two devices a server (node ID will be
1) and a client (node ID will be
2). Any of the devices can act as a server or act as a client. First, issue the
leave command on both parties - that's not mandatory (especially for the first use when none of the addresses are commissioned), but it makes sure that the network state is reset, no short addresses are assigned.
The next step is to choose a network key (this will be used as the common key in the network for Connect encrypted messages). The key can be an arbitrary chosen 16-byte long value.
set-network-key does not apply the key, it just stores in a variable which will be set to the default value (all
0x00s) on device restart. This command must be executed on the server.
Choose a node ID for the server:
Then choose a node ID for the client:
Send the server's public key for the client:
Send the client's public key for the server:
At this point, all the necessary information is available on both sides to create the shared secret (and the shared key), but no third party who may observe the communication can recover the shared secret from the messages exchanged.
Generate the shared key (issue the command on both server and client):
For convenience there is a CLI command to show the shared key, to check whether the same key generated on both sides:
To send the network key to the client, issue the
send-network-key on the server. The network key will be AES encrypted using the shared key. Connect's message encryption will not be used here (security bit is not set) - since the network key is not known at this point, so the message will be sent unencrypted but the payload which transfers the key will be protected by encrypting the payload with the previously generated shared key.
To check whether the network keys match issue the following command on both sides:
To apply the network key (write the key to the non-volatile memory)
apply-network-key on the server and on the client as well.
To enable secure communication between the parties (using Connect's security feature), issue the following command on both devices (if the parameter is
0, the security feature will be turned off).
From this point, secure communication is possible between the devices, the command below will send the text
Hi! from the client to the server but obviously the opposite direction should work as well.
send-message 1 "Hi!"
In normal use cases, it is not necessary to write the key on the server and the clients at every restart. The usual workflow is that the network key is stored in the non-volatile space of the server - in the code memory for example and apply once (at first power up or initiated by a CLI command, etc.). Although, Connect's
emberSetSecurityKey() API function stores the network key in a non-volatile space, there is no API call to read it back - thus the application must store the network key in a retrievable form to be able to send it to the clients.
The security can be enabled at the beginning of the process above, due to the fact that enabling security for the messages is only makes sense for sent messages, received messages are passed to the application if the network keys match (the device can decode the message) or if the message is unencrypted. If the encryption status is not checked by the application for the incoming messages malicious devices can trick the devices in the network.
The above method for transferring the network key works with multiple client devices, but in that the key sharing process must be repeated between the server and every client one by one.
Although the Connect does not support ECDH key exchange at the plugin level, the way to implement is fairly easy and straightforward.