ESP8266 + AWS API gateway signed requests

One of my recent projects required an ESP8266 device to interface with AWS.  I tried AWS IoT first, but it wasn't what I wanted.  Instead, I decided to call a simple API hosted in AWS.  This sounds like a simple thing to do, but like with many things on Arduino, that's only almost true.

Available SDKs

Amazon released an experimental SDK for Arduino in 2015 which unfortunately has not been maintained.  There have been a decent number of forks, including the one that the AWS IoT websockets is based on: aws-sdk-arduino.  So that's the one I tried to use to do signed API requests to API gateway.

Needless to say, it didn't work.  My ESP kept crashing, and after minimizing my code to figure out why, I was finally forced to look into the SDK code.  And then I saw this:

Bad C code

Now, if you're a C programmer, this should immediately set off red flags.  It's allocating memory on the stack and returning a pointer to the memory.  This is a classic beginner C mistake, you can read more about it here.

ESPAWSClient

Rather than try to fix this everywhere in the existing SDK, I decided to start fresh.  I didn't need all the extra functionality of DynamoDB and Kinesis and the cruft that comes along with it in the other SDK.  The original SDK even still has code for AWS v2 signatures, which are obsolete.  Alas, I released ESPAWSClient on Mar 9, 2018.

Example usage

Suppose you wanted to do a signed POST to an AWS gateway at https://ivsxai2qus.execute-api.us-east-1.amazonaws.com/

const char *host = "ivsxai2qus";
const char *service = "execute-api";
const char *key = "YOUR_AWS_IAM_KEY";
const char *host = "YOUR_AWS_IAM_SECRET";
const char *uri = "/YOUR_POST_URI";
const char *payload = "{\"key\":true}";

ESPAWSClient aws = ESPAWSClient(service, key, secret, host);
AWSResponse resp = aws.doPost(uri, payload);
if (resp.status != 200) {
    Serial.printf("Error: %s", resp.body.c_str());
    Serial.println();
}

Custom Domain

If you have configured a custom domain for your AWS endpoint, you can specify it with the setCustomFQDN method.

ESPAWSClient aws = ESPAWSClient(service, key, secret, host);
aws.setCustomFQDN("api.domain.com");

Verify SSL fingerprint

You can force SSL fingerprint verification by using the setFingerPrint method.

ESPAWSClient aws = ESPAWSClient(service, key, secret, host);
aws.setFingerPrint("CC AA 48...");

Verify Certs / Client certificates

The ESPAWSClient class inherits from the WiFiClientSecure class, so any methods there can be called as well.

ESPAWSClient aws = ESPAWSClient(service, key, secret, host);
aws.setCACert(...);

Caveats

Time

You must have the proper system time set on your device. The library uses the gettimeofday internally to get the time. The AWS signed requests are only valid for a certain amount of time, so if AWS will reject your signatures if your time is wrong. Note that many Arduino NTP libraries do not call settimeofday to set the system time. See the example sketch for a full example with NTP.

Memory

The ESP8266 typically only has 80K of RAM total. AWS uses somewhat large certs and TLS 1.2. Just doing the connection requires around 20K of available HEAP. More if you add in certificate validation. If you're adding this to an existing Arduino program, check you're not running out of RAM.

Hope it's helpful!

This article was updated on April 13, 2018