>> µTLS - DEFINING LIGHTWEIGHT SECURITY FOR IoT (PART 9)
While children of the world stay awake waiting for Santa, I work on
µTLS (micro TLS).
In my previous
entry
I made a promise that I would update my
weather station
to utilize µTLS (micro TLS) so that it could securely post weather
information from its location to my own server. I will soon be returning
home from my time in Europe, so it is the perfect time to relocate my sensor
array and give it a nice upgrade it at the same time. I have made some nice
structural improvements to the code - to make it easier to integrate into
your projects.
I have posted the µTLS (micro TLS) driver code below - but let's
take a closer look at it.
const uint8_t SERVER[] PROGMEM = { xxx, xxx, xxx, xxx };
#define SERVER_NAME "xxx.xxxxxxxx.xxx"
#define SERVER_URI "/xxx/index.php"
#define CLIENT_GUID "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
#define CLIENT_INTERVAL 60 // 1 minute
It is important to define information on the server where the client needs
to report to - a few basic requirements such as the IP address, hostname
and the URI where the requests are to be made. We also need to define a
GUID for the client and how often it needs to check into the server.
const uint8_t aes_shared_key[] PROGMEM =
{
0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX,
0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX, 0xXX // must exist on server
};
#include "microTLS-module-aes128.h"
#define SESS_SECURITY "aes-128"
#define PING_SECURITY "aes-128"
To keep the code simple; all we need to provide are two #define
constants that define the level of security we want the client to use, in
addition we simply need to #include that module for use. We have
two levels of security; one for key exchange and one for ongoing communication.
For the weather station; I decided to use aes-128 as the
exclusive security protocol - to be used for both session creation and
session updates. A pre-shared key is defined that will establish trust
with the server and exchange a new AES key for ongoing communication.
The TLS specification does state that using
pre-shared keys
is a permitted method of key exchange, so all good there.
typedef struct xGlobals
{
uint8_t aeskey[AES_KEYLEN];
// https://github.com/ControlEverythingCommunity/SI7020-A20
float humidity;
float temperature;
// https://github.com/ControlEverythingCommunity/CPS120
struct
{
double visible;
double ir;
double uv;
} light;
// https://github.com/ControlEverythingCommunity/SI1132
float pressure;
} xGlobals;
xGlobals gx;
We define some additional attributes in our xGlobals structure
to store the values that we read from our sensor array - these will be
used to populate a message in JSON that our server will receive and
store every time a session update is performed.
void microTLS_setup()
Our µTLS (micro TLS) driver is actually an extension to an existing
sketch - so we must allow for additional initialization modes to occur within
the program. We have a number of sensors, using the Wire interface that need
to be configured - this is where we do these operations. A Serial interface
is already created for debugging, so no additional system level calls are
required.
The brains of the operation happen within the following functions:
void microTLS_sess_prepare()
void microTLS_sess_process()
void microTLS_ping_prepare()
void microTLS_ping_process()
Using the defined security protocols - we must make sure that all the necessary
operations are performed so that the server received what it expects. This
involves deciding what message needs to be sent, generating an integrity
hash and encrypting the message as intended. The same operations in reverse
are needed when data is received from the server.
The good news is; we'll have templates for all circumstances, so you wont
need to worry about it.
// JSON message structure
// '{"temperature":-99.99,"humidity":99.99,"pressure":99.99,"light":999}'
const char JSON_TAG_TMP[] PROGMEM = "\"temperature\":";
const char JSON_TAG_HUM[] PROGMEM = "\"humidity\":";
const char JSON_TAG_PRS[] PROGMEM = "\"pressure\":";
const char JSON_TAG_LGT[] PROGMEM = "\"light\":";
// we need to read the sensors
microTLS_read_sensors();
Our server will be expecting a JSON buffer for the data from the sensor
array; an example of what it expects is shown. We need to define some
constants to assist in building the buffer - but more importantly, we need
to read the sensors to populate the xGlobals structure.
Once we have the sensor data - we need to build a JSON buffer containing
them. While some people would go with the option of using sprintf
to do this; I personally opted to build the string manually to avoid wasting
valuable RAM and program space. Nothing beats using pointers!
// we'll put our data at the end of scratch
len = 100; // should be plenty of space
p = &g.scratch.reserved[CLIENT_SCRATCH_LEN - len - 1];
// we need to simulate a sprintf() and build the JSON string
_p = (char *)p;
*_p++ = '{';
{
strcpy_P(_p, JSON_TAG_TMP); _p += strlen(_p);
{
// whole number
val = (int)gx.temperature;
u32toa(val, _p); _p += strlen(_p);
// remainder
*_p++ = '.';
val = (int)((gx.temperature * 100)) % 100;
if (val < 10) *_p++ = '0';
u32toa(val, _p); _p += strlen(_p);
}
*_p++ = ',';
strcpy_P(_p, JSON_TAG_HUM); _p += strlen(_p);
{
// whole number
val = (int)gx.humidity;
u32toa(val, _p); _p += strlen(_p);
// remainder
*_p++ = '.';
val = (int)((gx.humidity * 100)) % 100;
if (val < 10) *_p++ = '0';
u32toa(val, _p); _p += strlen(_p);
}
*_p++ = ',';
strcpy_P(_p, JSON_TAG_PRS); _p += strlen(_p);
{
// whole number
val = (int)gx.pressure;
u32toa(val, _p); _p += strlen(_p);
// remainder
*_p++ = '.';
val = (int)((gx.pressure * 100)) % 100;
if (val < 10) *_p++ = '0';
u32toa(val, _p); _p += strlen(_p);
}
*_p++ = ',';
strcpy_P(_p, JSON_TAG_LGT); _p += strlen(_p);
{
// whole number
val = (int)gx.light.ir;
u32toa(val, _p); _p += strlen(_p);
// remainder
*_p++ = '.';
val = (int)((gx.light.ir * 100)) % 100;
if (val < 10) *_p++ = '0';
u32toa(val, _p); _p += strlen(_p);
}
}
*_p++ = '}';
*_p = 0; // we must not forget the NULL terminator
len = strlen((char *)p);
µTLS (micro TLS) provides a few lightweight helper functions for
dealing with raw values - but after all of the above; the buffer pointed
to by the variable p should have the unencoded message we want
to send to the server. We then generate an integrity hash and encode it
as required.
When compiled for the Arduino UNO:
Sketch uses 26,954 bytes (83%) of program storage space.
Global variables use 1,202 bytes (58%) of dynamic memory,
leaving 846 bytes for local variables. Maximum is 2,048 bytes.
With some minor modifications to the main µTLS (micro TLS) sketch to
target the particle.io platform - we can also see the benefits of how
lightweight the library is as we also have plenty of program space and
RAM available for additional purposes:
Output of arm-none-eabi-size:
text data bss dec hex | Flash used 24604 / 110592 22.2 %
24492 112 2408 27012 6984 | RAM used 2520 / 20480 12.3 %
Of course there is still room for improvement to make integrating
µTLS (micro TLS) into existing micro-controller applications easier
- but with this example it should be obvious that it does not require a
lot of effort. Over time additional modules will be developed - we have only
just explored at the tip of the iceberg when it comes to its potential to
help solve the security crisis in IoT.
The weather stations µTLS (micro TLS) driver code (with sensorship) is
available: