In part I I described how to create the Rails application but now lets focus on the Arduino. First you need to find a library for you GSM/GPRS shield you are using. I have the Seeedstudio GPRS Shield even though I do not have version 2.0 no good library existed for it. The libraries Seeedstudio list on their wiki and product pages all work great but they have which I consider a major flaw; they do NOT use the same methods etc as the official Arduino GSM library does. Thus if someone has developed a client using that shield you would need to re-write the code if you have another GSM shield. Both provide same functionality but through a different API. NOT a good solution in my eyes.
The official GSM library is written to be extended with support for other shields and I started to look into it. After examining the code for a while I noticed it is not written very well. The library as such is large and uses a lot of unnecessary RAM. My intentions is to create a client that will be RAM efficient to run on a Duemilanove which has only 2 kb RAM. Thus I started to write yet another GSM library for the Seeedstudio Shield. The result is the GSMGPRS_Shield library.
Anyway that was a sidetrack from the main, to create a reasonably secure Arduino client. Now we got a library to send and receive information to a HTTP server! So the next step is to find a good REST client. I found a number of different libraries that could be used either just for HTTP communication or both HTTP and REST.
HttpClient which is mainly developed for the Spark and not the best on a vanilla Arduino. Then there is the HTTPClient by Interactive Matter. Yet another HttpClient by Adrian McEwen. And uHTTP though all of these are based on the EthernetClient class and thus has to be re-written if going to be used with any GSM library. So much for code reusability.
There also exists a number of REST client libraries, like spark-restclient which is of course developed for the Spark. Another one is arduino-restclient but again both are built on the EthernetClient class.
After a bit of investigation I decided to use arduino-restclient but modify it to work with my GSMGPRS_Shield library. My fork is available here. My modified version uses the F() macro to put almost all strings into PROGMEM to save ram and cleaned up some other parts as well.
Next I needed MD5, SHA-1 and Base64 encoding since those are used in a request that is going to be received by API_Auth that we used on the rails side. MD5 support is provided by ArduinoMD5. Adafruit has created a stripped down SHA-1 library that I use. For Base64 encoding I used arduino-base64 but created a fork of it since it stored it’s whole Base64 alphabet in RAM instead of PROGMEM.
Then finally it was more or less just to set all this together and create the client. I have put the Arduino sketch on Github, ArduinoAuthenticatedClient.
A quick walkthrough of the code:
Change the secret key to the one generated by your rails application and make sure you set the ID in authHeaderStr to the correct id. In my example the id is 3. The numbers after the id and the : is where the authentication string will be copied.
const char PROGMEM secretKey[] = "wJ+LuCJKuFOcEdV0rdbp0BtkLdLm/EvH31KwiILc/xC35zgDd+KMBTuvmpH9uhNtOvfTr8rl7j6CHVSM8BB7XA==";
char *authHeaderStr = "Authorization: APIAuth 3:123456789012345678901234567=";
In general the code is a quite straight forward HTTP/REST client. The interesting part is the getHMACSignature_P function that concatenates the four different strings that are used to create the signature. But first we create the MD5 hash of the requests body and Base64 encode that:
unsigned char* hash=MD5::make_hash(body);
base64_encode(b64, (char *)hash, 16);
strncpy(&contentMD5Str[CONTENT_MD5_STR_CONTENT_START], b64, 22);
free(hash);
rest.setHeader(contentMD5Str);
Then we use that as one of the four strings to create the HMAC signature, which we also Base64 encode before setting the authentication header:
base64_encode(b64,
(char *)getHMACSignature_P((uint8_t *)secretKey, sizeof(secretKey)-1,
&contentTypeHeaderStr[CONTENT_TYPE_STR_CONTENT_START],
&contentMD5Str[CONTENT_MD5_STR_CONTENT_START],
resourcePath,
&dateStr[DATE_STR_CONTENT_START]),
20);
strncpy(&authHeaderStr[AUTH_HEADER_STR_CONTENT_START], b64, 27);
rest.setHeader(authHeaderStr);
Two comments:
- I use my web server here at YellowOrb as host because I needed some public machine. I setup up an SSH tunnel from that to my local machine which runs the Rails application.
- The time stamp of the request is important since the receiving Rails application will not accept the request if it is to old. Thus, change the char *dateStr ="Date: Thu, 13 Nov 2014 14:18:11 GMT"; to something appropriate.