>> µTLS - DEFINING LIGHTWEIGHT SECURITY FOR IoT (PART 3)
There comes a time when one must write code for micro-controllers, nothing
like good old C.
In the continuing effort to create
µTLS (micro TLS) -
it is finally time to build the foundations of our micro-controller
client. I've written a number of sketches for Arduino and similar devices
over the years so I will bring all my experiences into play to build a
benchmark platform for our modules. A key design component I wished to
integrate was easy to follow logic and code segmenting.
I went with a simple state engine for managing the main execution loop.
#define STATE_IDLE 0
#define STATE_SESSION 1
#define STATE_PING 2
int app_state;
uint32_t app_ts;
uint32_t app_ts_ping;
uint32_t app_ts_local;
While not all micro-controllers have a built in RTC (real time clock);
we will simulate one based on the response we get from our server and
also use the timestamp to decide when to change the application state.
In essence; we'll either be idling - in which case we could be reading
sensors et al, establishing a new session or pinging the server to
provide updates during a session.
void setup()
{
// initialize our app_ts_local
app_ts = 0;
app_ts_ping = 0; // we do not know these values at this point
app_ts_local = millis();
// first task is to initialize a session with server
app_state = STATE_SESSION;
}
Our setup routine simply initializes the state variables and tells
us to establish a session.
void loop()
{
uint32_t ts;
// update the app_ts, based on CPU milliseconds
ts = millis();
app_ts += (ts - app_ts_local) / 1000; // whole seconds
app_ts_local = ts - ((ts - app_ts_local) % 1000); // carry the modulus
switch (app_state)
{
case STATE_IDLE:
// is it time to ping the server again?
if (app_ts >= app_ts_ping) app_state = STATE_PING;
else delay(500); // just kill some time while we wait
break;
case STATE_SESSION:
session_create();
break;
case STATE_PING:
session_ping();
break;
}
}
I chose to use a
WiFiShield
for my sketch; however, you could use an EthernetShield if desired. There
are plenty of examples available on how to act as a HTTP client; so I
will not go into the details in this blog post, the code will be available
for you to review and see how to correctly deal with timeouts, disconnections
and other twiddly bits and pieces (you will thank me later).
Since we decided that to send binary data safely over an ASCII transmission
using JSON within the protocol requires the use of
Base64 -
I whipped together a nice and resource efficient variant for us to use. I
was able to isolate being able to encode and decode small chunks without
having to allocate memory, something that is a limited resource on these
devices.
void b64_encode_chunk(byte *src, byte cnt, char *dst);
void b64_encode(byte *src, int cnt, char *dst);
void b64_encode_stream(Stream *s, byte *src, int cnt);
int b64_encode_len(int len);
void b64_decode_chunk(char *src, byte cnt, byte *dst);
void b64_decode(char *src, int cnt, byte *dst);
void b64_decode_stream(Stream *s, char *src, int cnt);
void b64_decode_stream_hex(Stream *s, char *src, int cnt);
int b64_decode_len(byte *src, int len);
While there are plenty of libraries available to do things; you may
want to extract the bits and pieces you need so it doesn't bloat out
your sketch. In some cases, you may even need to take the more brutal
approach for dealing with stuff like JSON, over using a library.
const char KEY_SESSION[] = "\"session\":";
...
strcpy(needle, KEY_SESSION); p = strstr(buf, needle);
if (p != NULL)
{
// we need to make a duplicate of the string between the '"'
p += strlen(needle);
p = strchr(p, '"'); p++; // find the first '"', skip
*(strchr(p, '"')) = 0; // find the second '"', make \0
strcpy(app_session, p);
#ifdef ENABLE_DEBUG
Serial.print(F("** found session: "));
Serial.println(app_session);
#endif
}
Scared of C pointers?
Then micro-controller programming at this level probably isn't for you.
There are some nifty libraries around; like
arduino-json
but even the footprint is too large for our needs here. Since we control
both the server and client; we can take a few shortcuts and utilize basic
string searching using new lines as key delimiter detection within the
transmission of data between the server and client.
After piecing everything together (took me a while), my Arduino UNO could
talk to my server.
:: microTLS (none/none)
=======================
(c) RIoT International Pty Ltd
build: Dec 15 2016
:: attempting to connect to WiFi network
** connected to WiFi
SSID: RIoT-eu
IP Address: 192.168.1.196
NetMask: 255.255.255.0
Gateway: 192.168.1.1
app state: 1
current time: 0
session_ping: 0
:: attempting connection to server
** connected
-- request:
POST /xxx/index.php HTTP/1.1
Host: xxx.xxxxxxxx.xxx
User-Agent: Arduino/1.0
Connection: close
Content-type: application/x-www-form-urlencoded
Content-Length: 118
{
"guid": "ffa90e64-f3e2-4697-b98c-e3c13d1b2362",
"security": {
"protocol":"none",
"response":"none",
"protocol_sub":"none"
}
}
-- response:
HTTP/1.1 200 OK
Date: Thu, 15 Dec 2016 10:58:06 GMT
Server: Apache
Content-Length: 195
Connection: close
Content-Type: application/json
{
"session": "e91f5584152639c72d5358a05f9cd88758527740d09bb",
** found session: e91f5584152639c72d5358a05f9cd88758527740d09bb
"security": {
"response": "none",
"protocol": "none"
},
"data": {
"ts": "1481799488"
** found ts: 1481799488
}
}
:: done
app state: 0
current time: 1481799488
session_ping: 1481799488
app state: 2
current time: 1481799488
session_ping: 1481799488
:: attempting connection to server
** connected
-- request:
PUT /xxx/index.php HTTP/1.1
Host: xxx.xxxxxxxx.xxx
User-Agent: Arduino/1.0
Connection: close
Content-type: application/x-www-form-urlencoded
Content-Length: 173
{
"session": "e91f5584152639c72d5358a05f9cd88758527740d09bb",
"security": {
"protocol":"none",
"response":"none"
},
"data": {
"buffer":"c2VuZGluZyBhIG1lc3NhZ2Ugb3ZlciBtaWNyb1RMUw=="
}
}
-- response:
HTTP/1.1 200 OK
Date: Thu, 15 Dec 2016 10:58:12 GMT
Server: Apache
Content-Length: 227
Connection: close
Content-Type: application/json
sending a message over microTLS
{
"session": "e91f5584152639c72d5358a05f9cd88758527740d09bb",
"security": {
"response": "none",
"protocol": "none"
},
"data": {
"ts": "1481799494"
** found ts: 1481799494
}
}
:: done
app state: 0
current time: 1481799494
session_ping: 1481799794
For the sake of readability; I manually edited the data being sent up
(highlighted in bold) for the blog, the real messages are all on a
single line using as little data as possible. It should be clear in the
console log where our code was able to find various tokens in the JSON
(session, ts) - parsing them for storage in our application.
The final piece of the puzzle is to know when to synchronize next; we have
defined a constant of 300 seconds (5 minutes) from the last request.
We now now have a fully functional sketch deployed on the Arduino UNO that
can establish a HTTP connection to our server; do base64 encoding/decoding,
perform POST and PUT requests and brutally parse the JSON
responses. We must remember that it has absolutely no security yet.
So; how big is the application and how much memory does it require just
to do this?
Sketch uses 16,910 bytes (52%) of program storage space.
Global variables use 1,527 bytes (74%) of dynamic memory,
leaving 521 bytes for local variables. Maximum is 2,048 bytes.
There are a number of tricks; namely using PROGMEM to free
up dynamic memory:
Sketch uses 17,890 bytes (55%) of program storage space.
Global variables use 487 bytes (23%) of dynamic memory,
leaving 1,561 bytes for local variables. Maximum is 2,048 bytes.
Removing debugging (not needed for deployment) also gives more RAM and
removes some code:
Sketch uses 13,604 bytes (42%) of program storage space.
Global variables use 443 bytes (21%) of dynamic memory,
leaving 1,605 bytes for local variables. Maximum is 2,048 bytes.
On an Arduino UNO (and similar micro-controllers) this means there is
just over half the program storage space available and the ability to
use just over 1.5Kb for local variables. Taking the issue of CPU speed
out of the equation; the next question is what can we put in this
limited space that actually can allow for secure communication between
the micro-controller and the server?
Well, that is plenty of resources - we can definitely do a lot with that!
I accept the challenge!
In the next entry we'll look at doing just that - I have already written
a rot13 module; but that one doesn't really provide much
cryptographic value to any project. I have a few more modules in mind that
will be candidates for providing real security on micro-controllers
with limited resources. I will leave it up to you to tweak the Arduino
sketch to support rot13 - shouldn't take much.
The source code for the microTLS server, two modules and the Arduino client
are available below:
µTLS (micro TLS) SERVER (alpha version)
µTLS (micro TLS) CLIENT (alpha version)