Processing a C Float type in NodeJS

It is a long time since I’ve written something on the blog, but anyway, a quick note regarding to how to transform a C float type (float myvar;) to float in NodeJs (float32).

We might need this transformation when processing incoming float data from somewhere running a C based code to a NodeJs based backend. For example capturing IMU data in a bluetooth device running a C based firmware, then sending the data to a NodeJS backend.

Basically in C, a float type is a four byte sized variable that has a mantissa and an exponent.

So in C pseudo code we might have something like:

void do_something() {
float var = getFloatData();

send_data_to_backend( &var, sizeof(var));
...
...
}

Or even something like:

void do_something() {
float imu_gyrodata[3]; // Holds the X, Y and Z readings from a Gyro
get_IMU_GyroData( imu_gyrodata );

send_data_to_backend( imu_gyrodata, sizeof(imu_gyrodata));
...
...
}

So in the first case we send a single float, and on the last case an array of three floats.

For the NodeJs side, the following function retrieves back the floats directly into an array, either with one position, for the single var, or with three positions for the three floats array:

function bufferToFloat32Array(buffer) {
let arr = [];
const size = Float32Array.BYTES_PER_ELEMENT;
for (let i = 0; i < buffer.length / size; i++)
arr.push(buffer.readFloatLE(i * size));
return arr;
}

And that is it. A complete example is as following:

var fArray = [ 0x79, 0xe9, 0xf6, 0x42, 0x5b, 0xf3, 0x2f, 0xc2, 0x41, 0x42, 0x62, 0x42, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd9, 0x62, 0x02, 0x00 ];

function bufferToFloat32Array(buffer) {
let arr = [];
const size = Float32Array.BYTES_PER_ELEMENT;
for (let i = 0; i < buffer.length / size; i++)
arr.push(buffer.readFloatLE(i * size));
return arr;
}

console.log("Float array size is: ", Float32Array.BYTES_PER_ELEMENT );
var buffer = Buffer.from( fArray );
const results = bufferToFloat32Array( buffer );
console.log( results );

Which should print:

Float array size is:  4
[
123.45600128173828,
-43.98765182495117,
56.564701080322266,
3.363116314379561e-43,
0,
2.191308499557219e-40
]

Happy coding!

Using in NodeJs raw ECC public keys

A quick post about ECC Public keys that are in their native form of the raw X and Y curve coordinates, and how to use them in NodeJs. Such raw keys are normally outputted by hardware cryptographic devices such as the Microchip ATECC508 or ATECC608. These devices keep the private keys securely, without ever exposing them, and so all cryptographic operations that need to use the private keys need to go through the device.

Any operation that uses a private key for an operation such as signing or encrypting data,  we need the public key to do the other necessary operation, either for verifying if a signature for the data is valid or to decrypt the data. The public key can be obtained from the device at any time and it is a two 32 byte number, for a total of 64 bytes, where 32 bytes is for the X coordinate and the other 32 bytes for the Y coordinate. The device returns the two coordinates concatenated  as X+Y.

So, for the simplest case, we have a signature and a public key in raw ECC format, how do we verify the signature?

Since the Microchip ATECC508 and ATECC608 uses the ECC curve NIST P-256, we need first to instatiate the necessary libraries to handle such cryptographic material:

And at the beginning of our code:

npm i -save crypto elliptic

Notice that we’ve specifically choose the p256 curve for our ec variable, since it is the curve that we are using

var crypto = require("crypto");

var EC = require('elliptic').ec;
var ec = new EC('p256');

An example of a raw public key, already in hex format is:

var pubk = '29C67C7AC65D9C8E78FE82C2D8673DF03BBF0A04D0BD230FE745F5F2BAE7D368F4A4AA73EBFE11838F7189370BC16C256871428EA36952F61006F99178429ADD';

But we can’t use this directly since we need to convert to OpenSSL format. Since the key is not compressed, it has all the components X and Y, this is easily done by prefixing the public key with the 0x04 byte (check 2.2 on RFC5480), and them we can obtain the public key:

var ecpub = '04' + pubk;
var pkec = ec.keyFromPublic(ecpub, 'hex');

The key is now in DER format not compressed. So now, given a message hash and the public key and signature (ecsig) we can check for its validity:

if ( ec.verify( hash, ecsig, pkec ) ) {
    console.log("\nSignature is ok!");
} else {
    console.log("\nSorry, signature is not valid for the provided message hash");
}

JWT Tokens

These crypto devices also have the capability to create JWT tokens, and sign them (again with a private key that is never exposed. These JWT tokens can then be used to provide authentication and identity to any backend service. As the same with the public keys which are provided in raw X and Y coordinates, signatures are provided in raw R and S format. While NodeJS jsonwebtoken library can handle the JWT token, it needs the public key in PEM format, and not in raw X and Y format or DER format.

So we need to convert the raw Public key to PEM format. PEM format is made of a header with specifies some data about the key, namely what curve it belongs to and the raw X and Y key.

There are several ways to do the conversion, but I choose the easiest that is to prefix the raw X and Y values with the necessary header to obtain the key in PEM format.

To obtain the correct header to use we can generate a random ECC NIST P-256 key in PEM format, and extract the header.

openssl ecparam -name prime256v1 -genkey -noout -out tempkey.pem
openssl ec -in tempkey.pem -pubout -outform pem -out temppub.pem

With these two commands we have now a NIST P-256 public key on temppub.pem. We edit the file and remove the header and footer so it looks like this (x.pem file):

MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEkeBXnGHQ00vwtmTRdSDpPvFHJ+Fqv+Ean8bDg0qZf9mufgD9rpg+XfwIeaifGCpDX2LRW+A9hlZP9YeDsLJTbQ==

We can now convert it to hex and obtain the header by removing the last 65 bytes ( 0x04 + 64 key bytes):

base64 -d x.pem | xxd -i
  0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02,
  0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03,
  0x42, 0x00, 0x04, 0x91, 0xe0, 0x57, 0x9c, 0x61, 0xd0, 0xd3, 0x4b, 0xf0,
  0xb6, 0x64, 0xd1, 0x75, 0x20, 0xe9, 0x3e, 0xf1, 0x47, 0x27, 0xe1, 0x6a,
  0xbf, 0xe1, 0x1a, 0x9f, 0xc6, 0xc3, 0x83, 0x4a, 0x99, 0x7f, 0xd9, 0xae,
  0x7e, 0x00, 0xfd, 0xae, 0x98, 0x3e, 0x5d, 0xfc, 0x08, 0x79, 0xa8, 0x9f,
  0x18, 0x2a, 0x43, 0x5f, 0x62, 0xd1, 0x5b, 0xe0, 0x3d, 0x86, 0x56, 0x4f,
  0xf5, 0x87, 0x83, 0xb0, 0xb2, 0x53, 0x6d

Now in NodeJs:

var pubk_header = ‘3059301306072a8648ce3d020106082a8648ce3d030107034200’;
var key = Buffer.from( pubk_header + ’04’ + pubk, ‘hex’);

var pub_pem = “—–BEGIN PUBLIC KEY—–\n” + key.toString(‘base64’) + “\n—–END PUBLIC KEY—–“;

jwt.verify( jwttoken, pub_pem , function(err, decoded) {
   if (err) {
     console.log(‘Failed to verify token.’ );
     console.log(err);
   } else {
       console.log(“Token is valid!”);
       console.log(“Decoded token:”, decoded);
   }
 });

And that’s it. A bit of hackish, but it works fine. If using other curves other than NIST P-256, the appropriate curve and header must be used so that the validations work as expected.

ESP8266 and AES128 for end-to-end encryption

One of my older posts that has more hits is this one:ESP8266 – Logging data in a backend – AES and Crypto-JS where it’s explained how we can send data that is encrypted with AES128 from the ESP8266 to a backend server, either a NodeJS server or a Node-Red based service.
On the comments section I had a lot of questions and issues with the implementation, and so I’ve crafted a full implementation of the End-to-End encryption that works both ways.

The code is available at Github: AESCrypto_Test and implements the firmware for an ESP8266 based device, I’m using the Wemos D1, and two node programs: one is the server and other is the client.

The Node Server just starts and waits for incoming data from the ESP8266 and decrypts the incoming data and just shows it on the screen. The server is always running to receive requests at any time.
The Node client is run interactively by the user to send data encrypted data to the ESP8266. The ESP8266 then decrypts the data and can do whatever it needs to do. The decrypted data is output to the serial console as usual.

For this communication to happen both the ESP8266 and the Node client need to know the IP of each other, and so there is the need to change that on the code before things start to work ok.

In this example, the AES key being used is pre-shared, by another words, its known from the start by both the ESP8266 the Node Client and the Node Server. The initialization vector at the ESP8266 is random, and on the Node Client can be fixed (NOT SECURE!!) or random. Both cases are shown to show how it works.

Anyway this is just a sample code show how it works and the example can be used as stepping stone for implementing other things.

The key aspect on this code is nevertheless the use of a pre-shared key, that while it simplifies things up, is not really that secure, but anyway allows to see the concepts involved.

NodeJS BLE Applications using BLENO on Arch Linux

BLENO is a greate NodeJS based library for building applications that communicat with other devices (Smartphones, tables, sensor tags) using Bluetooth Low Energy (BLE).

This post is just to quickly document some requirements for successfully use the BLENO library, in my case, on Arch Linux running the latest Plasma (KDE) desktop.

The tools:

Most the information available on the internet for using and controlling the bluetooth adapter uses the now deprecated tools hcitool, hciconfig and so on. Check here the deprecated list of commands.

So we need to use the new tools from the latest Bluez (Bluetooth Linux implementation): btmgm, btinfo, …

Making Bleno examples work:

The simplest example to try out the BLENO library is the battery example located at: […]/bleno/examples/battery-service

First let’s check if our computer/laptop bluetooth adapter is available: Note that all commands must be ran as the root user:

root@pcortex:/opt/bleno/examples/battery-service# btinfo local
Bluetooth information utility ver 5.45
Failed to open HCI user channel
root@pcortex:/opt/bleno/examples/battery-service# 

This issue can be circumvented by stopping the higher level bluetooth stack:

root@pcortex:/opt/bleno/examples/battery-service# systemctl stop bluetooth
root@pcortex:/opt/bleno/examples/battery-service# btinfo local
Bluetooth information utility ver 5.45
HCI version: 6
HCI revision: 7869
LMP version: 6
LMP subversion: 64512
Manufacturer: 2
root@pcortex:/opt/bleno/examples/battery-service#

In case of previously disabling the Bluetooth through the graphical interface:

Disabling the Bluetooth here will have this behaviour (in this case the bluetooth service is still running):

root@pcortex:/opt/bleno/examples/battery-service# systemctl start bluetooth   (<- After this disable bluetooth on the graphical interface)
root@pcortex:/opt/bleno/examples/battery-service# btinfo local
Bluetooth information utility ver 5.45
Failed to open HCI user channel
root@pcortex:/opt/bleno/examples/battery-service# btmgmt power on
Set Powered for hci0 failed with status 0x12 (Blocked through rfkill)
root@pcortex:/opt/bleno/examples/battery-service# 

Even stopping the Bluetooth service keeps the BT adapter disabled:

root@pcortex:/opt/bleno/examples/battery-service# systemctl stop bluetooth
root@pcortex:/opt/bleno/examples/battery-service# btmgmt power on
Set Powered for hci0 failed with status 0x12 (Blocked through rfkill)
root@pcortex:/opt/bleno/examples/battery-service#

We can check this with the rfkill command:

root@pcortex:/opt/bleno/examples/battery-service# rfkill list
0: phy0: Wireless LAN
        Soft blocked: no
        Hard blocked: no
2: hci0: Bluetooth
        Soft blocked: yes
        Hard blocked: no
root@pcortex:/opt/bleno/examples/battery-service# 

We can unblock now the adapter:

root@pcortex:/opt/bleno/examples/battery-service# rfkill unblock 2
root@pcortex:/opt/bleno/examples/battery-service# rfkill list
0: phy0: Wireless LAN
        Soft blocked: no
        Hard blocked: no
2: hci0: Bluetooth
        Soft blocked: no
        Hard blocked: no
root@pcortex:/opt/bleno/examples/battery-service# btinfo local
Bluetooth information utility ver 5.45
HCI version: 6
HCI revision: 7869
LMP version: 6
LMP subversion: 64512
Manufacturer: 2
root@pcortex:/opt/bleno/examples/battery-service# btmgmt power on
hci0 class of device changed: 0x00010c
hci0 Set Powered complete, settings: powered bondable ssp br/edr le secure-conn 
root@pcortex:/opt/bleno/examples/battery-service# 

So why we are having all this work for making sure that the BT adapter is powered on AND the bluetooth stack is stopped (systemctl stop bluetooth).

The answer is quite simple. If we don’t do this the BLENO examples will seem to work (they start) but the BLE advertised services are the bluetooth Bluez services and not our code.

To explain, check the following behaviour where we start the BLENO Battery Service with the Bluetooth stack started:

root@halcyon:/opt/bleno/examples/battery-service# systemctl start bluetooth
root@halcyon:/opt/bleno/examples/battery-service# node main.js 
on -> stateChange: poweredOn
on -> advertisingStart: success
setServices: success

Using the Nordic nRF Connect Android App we can see the non working behaviour vs what we should expect from the Bleno Battery example:

BLE Scan Results

Pressing Connect we can see on Client that no service are provided. This is due to the fact that the desktop bluetooth is enabled):

Now let’s disable the bluetooth stack (which powers the BT adapter) and start again the Bleno Battery example:

root@pcortex:/opt/bleno/examples/battery-service# systemctl stop bluetooth
root@pcortex:/opt/bleno/examples/battery-service# node main.js 

Example hangs in here, because BT adapter is disabled/off

^Croot@pcortex:/opt/bleno/examples/battery-service# btmgmt power on
hci0 class of device changed: 0x00010c
hci0 Set Powered complete, settings: powered bondable ssp br/edr le secure-conn 
root@pcortex:/opt/bleno/examples/battery-service# node main.js 
on -> stateChange: poweredOn
on -> advertisingStart: success
setServices: success

And now if we scan again and connect to the Battery example with our mobile phone through the Nordic application we have:

It works now!

We can confirm that because on the file battery-service.js the service identifier is defined:

function BatteryService() {
  BatteryService.super_.call(this, {
      //uuid: '180F',
      uuid: 'ff51b30e-d7e2-4d93-8842-a7c4a57dfb07',
      characteristics: [
          new BatteryLevelCharacteristic()
      ]
  });
}

and it is the same detected by the Android application.

ESP8266 – Logging data in a backend – AES and Crypto-JS

After building, on the previous posts, the Node-Red based backend to support E2EE (End to End Encryption) so we can log data into a central server/database, from our devices securely, without using HTTPS, we need now to build the firmware for the ESP8266 that allows it to call our E2EE backend.

The firmware for the ESP8266 must gather the data that it wants to send, get or generate the current sequence number for the data (to avoid replay attacks), encrypt the data and send it to the backend.
On the backend we are using the Java script library for cryptographic functions Crypto-js, and specifically we are encrypting data with the encryption algorithm AES. So all we need is to encrypt our data with AES on the ESP8266, send it to the Node-Red Crypto-js backend, decrypt it and store it, easy right?

Not quite, let’s see why:

Crypto-js and AES:
We can see that on my Node-Red function code and testing programs I’m using something similar to the following code example:

var CryptoJS = require("crypto-js");
var message  = "Message to encrypt";
var AESKey   = '2B7E151628AED2A6ABF7158809CF4F3C';

// Encrypt
var ciphertext = CryptoJS.AES.encrypt(message, AESKey );

console.log("Cypher text in Base64: " ,  ciphertext.toString(CryptoJS.enc.base64) );

// Decrypt
var bytes  = CryptoJS.AES.decrypt(ciphertext.toString(), AESKey );
var plaintext = bytes.toString(CryptoJS.enc.Utf8);

console.log("Decrypted message UTF8 decoded: ", plaintext);

Several points regarding the above code need clarification:

The code variable AESKey the way it is used on the above example encrypt and decrypt functions isn’t really a key but a passphrase from where the real key and an initialization vector or salt value is generated (I’m using the names interchangeably, but they are not exactly the same thing except they are public viewable data that must change over time/calls).
The use for the generated key is self explanatory, but the initialization vector (IV) or salt value is used to allow that the encrypted data output to be not the same for the same initial message. While the key is kept constant and secret to both parties, the IV/salt changes between calls, which means that the above code, when run multiple times, will never produce the same output for the same initial message.

Still referring to the above code, the algorithm that generates the key from the passphrase is the PBKDF2 algorithm. More info at Crypto-js documentation. At the end of the encryption the output is a OpenSSL salted format that means that the output begins by the signature id: Salted_, followed by an eight byte salt value, and after the salt, the encrypted data.

So if we want use the API has above on the node-js/crypto-js side, we need to implement on the ESP8266 side both the AES and PBKDF2 algorithms.

I decided not to do that, first because finding a C/C++ implementation of the PBKDF2 algorithm that could be portable and worked on the ESP822 proved difficult, and second the work for porting it to the ESP8266 won’t be needed if I use a KEY/IV directly, and so I decided to use the more standard way of providing an AES key and an initialization vector for encrypting and decrypting data.

In the case of Node-JS and Crypto-JS when using an explicit key and IV the code looks like:

var CryptoJS = require("crypto-js");
var request = require('request');

// The AES encryption/decription key to be used.
var AESKey = '2B7E151628AED2A6ABF7158809CF4F3C';

// The JSON object that we want to encrypt and transmit
var msgObjs = {"data":{"value":300}, "SEQN":145 };

// Convert the JSON object to string
var message = JSON.stringify(msgObjs);

var iv = CryptoJS.enc.Hex.parse('0000000000000000');
var key= CryptoJS.enc.Hex.parse(AESKey);

// Encrypt
var ciphertext = CryptoJS.AES.encrypt(message, key , { iv: iv } );

//console.log("Cypher: ", ciphertext );
console.log("Cypher text: " ,  ciphertext.toString(CryptoJS.enc.base64) );
console.log(" ");

console.log("=============================================================================");
console.log(" ");
console.log("Let's do a sanity check: Let's decrypt: ");

// Decrypt
var bytes  = CryptoJS.AES.decrypt(ciphertext.toString(), key , { iv: iv} );
var plaintext = bytes.toString(CryptoJS.enc.Utf8);

console.log("Decrypted message UTF8 decoded: ", plaintext);

Now, with above code, where the IV is always initialized to the same value, in this case ‘0000000000000000’, we can see when running the above code several times that the output is always the same since the IV is kept constant. Also the encrypted output is now just the raw encrypted data and not the Openssl format.

So to make the above code secure we must randomize the IV value for producing an output that is always different, even from several program runs when encrypting the same source data.

As a final note, if we count the number of HEX characters on the Key string, we find out that they are 16 bytes, which gives a total of 128 key bits. So the above example is using AES128 encryption, and with default Crypto-js block mode and padding algorithms which are CBC (Chain block mode) and pkcs7.

Interfacing Crypto-js and the ESP8266:
Since we are using AES for encrypting data and decrypting data, we need first to have an AES library for the ESP8266. The AES library that I’m using is this one Spaniakos AES library for Arduino and RPi. This library uses AES128, CBC and pkcs7 padding, so it ticks all boxes for compatibility with Crypto-js…

I just added the code from the above library to my Sming project and also added this Base64 library so that I can encode to and from Base64.

The only remaining issue was to securely generate a truly random initialization vector. And while at first I’ve used some available libraries to generate pseudo-random numbers to real random numbers, I’ve found out that the ESP8266 seems to have already a random number generator that is undocumented: Random number generator

So to generate a random IV value is as easy as:

uint8_t getrnd() {
    uint8_t really_random = *(volatile uint8_t *)0x3FF20E44;
    return really_random;
}

// Generate a random initialization vector
void gen_iv(byte  *iv) {
    for (int i = 0 ; i < N_BLOCK ; i++ ) {
        iv[i]= (byte) getrnd();
    }

So our ESP8266 code is as follow:

Global variables declarations:
The N_Block defines the encryption block size, that for AES128 is 16 bytes.

#include "AES.h"
#include "base64.h"

// The AES library object.
AES aes;

// Our AES key. Note that is the same that is used on the Node-Js side but as hex bytes.
byte key[] = { 0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C };

// The unitialized Initialization vector
byte my_iv[N_BLOCK] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

// Our message to encrypt. Static for this example.
String msg = "{\"data\":{\"value\":300}, \"SEQN\":700 , \"msg\":\"IT WORKS!!\" }";


The example function ESP8266 Sming code is:

void testAES128()  {

    char b64data[200];
    byte cipher[1000];
    byte iv [N_BLOCK] ;
    
    Serial.println("Let's encrypt:");
    
    aes.set_key( key , sizeof(key));  // Get the globally defined key
    gen_iv( my_iv );                  // Generate a random IV
    
    // Print the IV
    base64_encode( b64data, (char *)my_iv, N_BLOCK);
    Serial.println(" IV b64: " + String(b64data));
       
    Serial.println(" Mensagem: " + msg );
 
    int b64len = base64_encode(b64data, (char *)msg.c_str(),msg.length());
    Serial.println (" Message in B64: " + String(b64data) );
    Serial.println (" The lenght is:  " + String(b64len) );
    
    // For sanity check purpose
    //base64_decode( decoded , b64data , b64len );
    //Serial.println("Decoded: " + String(decoded));
    
    // Encrypt! With AES128, our key and IV, CBC and pkcs7 padding    
    aes.do_aes_encrypt((byte *)b64data, b64len , cipher, key, 128, my_iv);
    
    Serial.println("Encryption done!");
    
    Serial.println("Cipher size: " + String(aes.get_size()));
    
    base64_encode(b64data, (char *)cipher, aes.get_size() );
    Serial.println ("Encrypted data in base64: " + String(b64data) );
      
    Serial.println("Done...");
}

When the above code/function is executed on the ESP8266 it outputs the following:

Let's encrypt:
 IV b64: cAFviaDMHejlteGn9/4eQQ==
 Mensagem: {"data":{"value":300}, "SEQN":700 , "msg":"IT WORKS!" }
 Message in B64: eyJkYXRhIjp7InZhbHVlIjozMDB9LCAiU0VRTiI6NzAwICwgIm1zZyI6IklUIFdPUktTISIgfQ==
 The lenght is:  76
Encryption done!
Cipher size: 80
Encrypted data in base64: /1aZRwVaw3jv+ct8HS4pCV5lThvTG70M90ARiyAsIDYMkfJE3w8F3bgxaOKVA0rX4m1Mq50VVN0u9gRw9F2gKE4r2OcY8oECv8bKT80F9pY=
Done...

And now we can feed the above Base64 IV and encrypted data to our decoding program in Node-Js using Crypto-JS:

var CryptoJS = require("crypto-js");
var request = require('request');

var esp8266_msg = '/1aZRwVaw3jv+ct8HS4pCV5lThvTG70M90ARiyAsIDYMkfJE3w8F3bgxaOKVA0rX4m1Mq50VVN0u9gRw9F2gKE4r2OcY8oECv8bKT80F9pY=';
var esp8266_iv  = 'cAFviaDMHejlteGn9/4eQQ==';

// The AES encryption/decryption key to be used.
var AESKey = '2B7E151628AED2A6ABF7158809CF4F3C';

var plain_iv =  new Buffer( esp8266_iv , 'base64').toString('hex');
var iv = CryptoJS.enc.Hex.parse( plain_iv );
var key= CryptoJS.enc.Hex.parse( AESKey );

console.log("Let's decrypt: ");

// Decrypt
var bytes  = CryptoJS.AES.decrypt( esp8266_msg, key , { iv: iv} );
var plaintext = bytes.toString(CryptoJS.enc.Base64);
var decoded_b64msg =  new Buffer(plaintext , 'base64').toString('ascii');
var decoded_msg =     new Buffer( decoded_b64msg , 'base64').toString('ascii');

console.log("Decrypted message: ", decoded_msg);

and the output is:

Decrypted message:  {"data":{"value":300}, "SEQN":700 , "msg":"IT WORKS!" }

So, as the message shows, it WORKS!. AES encryption on the ESP8266 to a Node-JS Crypto-JS based code where decryption occurs.

Final notes:
So all is needed now is to build on the ESP8266 side the message with the encrypted data and the IV, and send through plain HTTP a JSON object to the Node-Red back end.

{
  "msg":"/1aZRwVaw3jv+ct8HS4pCV5lThvTG70M90ARiyAsIDYMkfJE3w8F3bgxaOKVA0rX4m1Mq50VVN0u9gRw9F2gKE4r2OcY8oECv8bKT80F9pY=",
  "iv":"cAFviaDMHejlteGn9/4eQQ=="
}

On the Node-Red back end the decryption can now be done easily as was shown on the above testing code.

Further reading:

More information also available at: ESP8266 and AES128 for end-to-end encryption and Establishing secure ESP8266 and NodeJs communication by using Diffie-Hellman key exchange and Elliptic Curves

IoT device provisioning and tracking

If we are deploying several IoT devices, above a certain number it becomes a hard task to know which ones are alive and working, their location, and since they may have different functions/purposes, to keep track of their configuration. This last problem is exacerbated if we need to keep and maintain different code bases and firmwares for each device.

Device provisioning and some cleaver firmware code, can help to bring these issues under control. Device provisioning is nothing more than a fancy name for a central server that holds information for each device, it’s configuration and status and even might allow some form of monitoring. In advanced usage it might let an operator to push firmware or configuration updates to certain devices, without the need to physical get them.

So this post is about my simple device provisioning that allows to keep track of my ESP8266 devices, running the Sming firmware, and control, in this initial stage, some of it’s configuration.

The solution is based in three components:

  • NodeJs based REST server that holds information in a SQLite database
  • An Angular.Js based web application to managed the device configuration
  • Sming based firmware for the ESP8266

The REST server
The REST server is based in javascript running on a NodeJs server, which uses Express and Body-Parser to create a simple and quick REST server. The REST protocol uses the HTTP operations GET, POST and PUT to define operations like get data, create data or update data. Due to the Express modules, we can build a simple interface to a database that translates REST operations to database operations. The Body-Parser module allows to communicate purely using JSON, and so takes care of all the details of coding and decoding JSON data communicated over HTTP.
Finally there is one important thing, at least for the browser front-end that is the CORS protection. CORS, Cross Origin Resource Sharing is a security feature that browsers use to not connect to anything else other than the originating domain of the page that are showing. To allow our REST server to be called by a running application on a browser (in our case, our Angular.Js frontend), the server must explicitly allow this. So we also need a CORS module for our REST server.
Finally I’ve chosen a SQLITE database due to it’s simpler configuration. No need to setup servers…
To allow our REST server to run we need to install locally to our application the folowing modules: npm install express node-sqlite3 body-parser cors

// Define and call the packages necessary for the building the REST API.
var sqlite3    = require('sqlite3').verbose();
var db         = new sqlite3.Database('data/restDB.sqlite');
var express    = require('express');
var app        = express();
var bodyParser = require('body-parser');
var cors       = require('cors');

After the module instantiation, and note that the database is created in the data subdirectory, we can create if needed our tables and fill them up with default data if needed:

db.serialize(function() {  // All database operations will be executed in series.
    // some code here    
    db.run("CREATE TABLE IF NOT EXISTS devices ( deviceid TEXT , name TEXT , lastseen TEXT , ssid TEXT, ipaddr TEXT, cfgsn INTEGER, flags INTEGER, data TEXT, datec TEXT )");
    // some more code here
});

The db.serialize makes the database functions to run in series, one after each other.
The db.run will execute a SQL command, and in the above case the creation of the device provisioning table.

The schema is the following:

  • deviceid -> the device id as sent by the device. It should be unique. I use the MAC address of the ESP8266 that should be unique.
  • name -> a human friendly name for the device. It can be anything.
  • lastseen -> The device should periodically call the REST server to let us know that’s is alive and connected. The lastseen field is the last time that information was received.
  • ssid -> Shows the WiFi SSID to which the device is connected.
  • ipaddr -> Shows the device IPv4 address in use. Might not be reachable if the device is behind a NAT router.
  • cfsn -> Config serial number. To let the device know if the configuration it holds is older than the configuration on the table row.
  • flags -> For future use, but the idea is to allow pagination, and command/control/data separation.
  • data -> Configuration data for the device. In my case a JSON array of properties and values user defined.
  • datec -> The date that the record was create, which means when the device was first seen.

Now knowing what information we are using, we can build the REST server:

//Configure Express
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(cors());

var router = express.Router();

// Root URI.
router.get('/', function(req, res) {
        res.json( {message: 'REST API' });
});

///// OUR CODE here

// Our base url is /api
app.use('/api', router);
app.listen(3000);

console.log("========================================");
console.log("Server started at http://localhost:3000/");

Excluding the ///// OUR CODE here this is the standard skeleton for the REST server that runs at port 3000 and hears for REST request on the /api base url.

For testing I recommend the FireFox plugin HTTPRequester that allows to simulate all the REST calls.

The devices REST interface

For the device provisioning I’ve implemented the REST end point devices which mean that we should call our REST server with the following URL’s: http://rest_server:3000/api/devices and http://rest_server:3000/api/devices/deviceid where deviceid is the device generated id.

For the first URL, so far I only implement the GET verb, which returns all registered devices:

router.route('/devices')
        .get ( function(req, res) {
           console.log("Devices list");
           db.all("SELECT ROWID, deviceid, name, lastseen, ssid, ipaddr, cfgsn, flags, data, datec  FROM devices",
              function( err , row ) {
                if (err) {
                  res.json( { status:'NOK', message: 'ERROR selecting data'} );
                } else {            
                  console.log('API: GET devices -> (' + row.length + ') records -> ' + JSON.stringify(row));
                   res.json( row );
                }                  
        });
      })

The function db.all returns an empty array if no results, or an array with row set. The row set is returned directly as a JSON array object to the calling application/device.

Selection_237

For the last URL, the implemented verbs are GET, to get the latest configuration and update the lastseen field, POST to register the device and/or returning the configuration, and PUT to update device information (used by the front end):

router.route('/devices/:id')
    .get( function(req, res) {
        var currdate = new Date();
        db.run("UPDATE devices SET lastseen='" + currdate + "' WHERE deviceid='" + req.params.id + "'");

        db.get("SELECT cfgsn, data FROM devices WHERE deviceid='"+req.params.id+"'",
          function( err , row ) {
            if (err) {    
              res.json( {status: 'NOK', message: 'Device get information failed' } );
            } else {
              if ( (typeof row == 'undefined') ) { 
                res.json( {status: 'NOK', message: 'Device NOT Registered' } );
              } else {
                res.json( row );
              }
            }
        });
    })    
    .put( function(req, res) {
        db.run("UPDATE devices SET name='"+req.body.name+"', cfgsn="+req.body.cfgsn.toString()+", data='"+req.body.data+"' WHERE deviceid='"+req.params.id+"'");  
        res.json( {status: 'OK', message: 'Device Updated' } );
    })    
    .post( function(req, res) {
        console.log("DEVICES -> POST Device request received: " + req.params.id);
        console.log("Body: " + JSON.stringify(req.body) );
        console.log("IP address: " + req.body.ipaddr );
        console.log("SSID: " + req.body.ssid );

        db.get("SELECT ROWID , deviceid, name, lastseen, ssid, ipaddr, cfgsn, flags, data, datec FROM devices WHERE deviceid='"+req.params.id+"'",
          function( err , row ) {
            if (err) {
              res.json( {status: 'NOK', message: 'Device Registration failed' } );
            } else {
              if ( (typeof row == 'undefined') ) {    // Device is new... if row is undefined (no results)
                var deviceid  = req.params.id;
                var name      = req.params.id;
                var lastseen  = new Date();
                var ssid      = req.body.ssid;
                var ipaddr    = req.body.ipaddr;
                var cfgsn     = "1";
                var flags     = "0";
                var data      = "";
                var datec     = lastseen;
                db.run("INSERT INTO devices ('deviceid','name','lastseen','ssid','ipaddr','cfgsn','flags','data','datec') VALUES ('" + deviceid + "', '" + name + "', '" + lastseen + "', '" + ssid + "', '" + ipaddr + "', " + cfgsn + ", " + flags + ", '" + data + "', '" + datec + "')");
                res.json( {status: 'OK', message: 'Device Registed' } );
             } else {
                res.json( row ); // Device already registered
             }
          }
       });
    })

The above REST verbs, namely the POST and PUT need a request body, to pass data from the source to the database.

In case of the POST verb the, body should be the following JSON: { “ipaddr”:”x.x.x.x”, “ssid”:”wifissid” }. For example:

Selection_238

We save the above code on a file, let it be restserver.js for example, and start our server with node restserver.js.

On the front end then we can have now a list of devices that are registered:

Selection_239

And selecting the device we can change the human readable name, and add configuration properties:

Selection_240

The front end, based on Angular Js isn’t completly ready, since it’s main purpose is to graph data from Sparkfuns Phant server.

Sming ESP8266 Code

To make this complete, we only need to start coding the ESP8266. I’ve abandoned NodeMcu and Lua language, due to several factors. Sming is a great firmware and already has support for a lot of devices and also includes a JSON library.

The sample Sming code is as simple as this:

#include <user_config.h>
#include <SmingCore/SmingCore.h>

#ifndef WIFI_SSID
    #define WIFI_SSID "defineme" // Put you SSID and Password here
    #define WIFI_PWD "passwd"
#endif

HttpClient provServer;  // Provisioning server client
String deviceid;

void onProvSent(HttpClient &client, bool successful )
{
    char cbuf[2048];
    StaticJsonBuffer<2048> jsonBuffer;
        
    if (successful)
        Serial.println("Registration request sent sucessufully");
    else
        Serial.println("Registration request has failed");
        
    String response = client.getResponseString();
    Serial.println("Server response: '" + response + "'"); 
        response.toCharArray( cbuf , 2048 );      
        JsonObject& config = jsonBuffer.parseObject( cbuf );
        
        if ( !config.success() ) {
            Serial.println("Error decoding JSON.");
        } else {
            Serial.println("Received config: " + config.toJsonString() );
        
            const char* name = config["name"];
            const char* datec= config["datec"];
            
            Serial.println ("Device Name: " + String(name) );
            Serial.println ("Date: " + String(datec) );
            // Process the JSON Array with the configuration data...

        }
}

void registerDevice ()
{
        String devIP;
        StaticJsonBuffer<256> jsonBuffer;
                
        devIP = WifiStation.getIP().toString();
        
        Serial.println("Sending device ID " + deviceid + " to provisioning server..." );
        Serial.println("Device IP : " + devIP );      
        JsonObject& config = jsonBuffer.createObject();
 
        config.addCopy("ipaddr", devIP );
        config.add("ssid"  , WIFI_SSID );
        
        provServer.setRequestContentType("application/json");
        provServer.setPostBody(config.toJsonString());
        provServer.downloadString("http://192.168.1.17:3000/api/devices/" + deviceid , onProvSent );
}

// Will be called when WiFi station was connected to AP
void connectOk()
{
    Serial.println("I'm CONNECTED");
        
        // Let's get the Mac address, that should be unique...
        deviceid = WifiStation.getMAC();
        deviceid.toUpperCase();
        Serial.println("Device ID: " + deviceid );
        
        // Register Device on the provisioning Server
        registerDevice();

    // Start send data loop
    // procTimer.initializeMs(25 * 1000, sendData).start(); // every 25 seconds
}

// Will be called when WiFi station timeout was reached
void connectFail()
{
    Serial.println("I'm NOT CONNECTED. Need help :(");

    // Start soft access point
    WifiAccessPoint.enable(true);
    WifiAccessPoint.config("CONFIG ME PLEEEEASE...", "", AUTH_OPEN);

    // .. some you code for configuration ..
}

void init()
{
    Serial.begin(SERIAL_BAUD_RATE); // 115200 by default
    Serial.systemDebugOutput(false); // Disable debug output to serial

    WifiStation.config(WIFI_SSID, WIFI_PWD);
    WifiStation.enable(true);
    WifiAccessPoint.enable(false);

    // Run our method when station was connected to AP (or not connected)
    WifiStation.waitConnection(connectOk, 20, connectFail); // We recommend 20+ seconds for connection timeout at start
}

And that’s it. After startup, the device after connecting to WiFi, makes a request to the provisioning server. If the request is from a new device it registers, if not it receives it’s configuration data. From this point we can do anything, namely pooling periodically the provisioning server to check for new configuration (by comparing the cfgsn hold by the device with the received one).