From 46fa5d3a8b97caed5ae345dca790b98149376d03 Mon Sep 17 00:00:00 2001 From: Sander van de Graaf Date: Mon, 21 Dec 2015 23:00:12 +0100 Subject: [PATCH 01/17] Fixed compile errors --- src/common/AmazonDynamoDBClient.cpp | 5 ++--- src/common/AmazonDynamoDBClient.h | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/common/AmazonDynamoDBClient.cpp b/src/common/AmazonDynamoDBClient.cpp index f2c1430..b49579e 100755 --- a/src/common/AmazonDynamoDBClient.cpp +++ b/src/common/AmazonDynamoDBClient.cpp @@ -704,9 +704,9 @@ MinimalString AttributeValue::jsonSerialize() const { return map.jsonSerialize(); } -void AttributeValue::setSS(MinimalList SSS) { +void AttributeValue::setSS(MinimalList SS) { SSBeenSet = true; - this->SSS = SSS; + this->SS = SS; } void AttributeValue::setBS(MinimalList BS) { @@ -6747,4 +6747,3 @@ UpdateTableOutput AmazonDynamoDBClient::updateTable(UpdateTableInput updateTable } return updateTableOutput; } - diff --git a/src/common/AmazonDynamoDBClient.h b/src/common/AmazonDynamoDBClient.h index d5bcefa..f4fed23 100755 --- a/src/common/AmazonDynamoDBClient.h +++ b/src/common/AmazonDynamoDBClient.h @@ -153,7 +153,7 @@ class ProvisionedThroughputDescription{ /*

Represents the data for an attribute. You can set one, and only one, of the elements.

*/ class AttributeValue{ - MinimalList SSS; + MinimalList SS; MinimalList BS; MinimalString B; MinimalString S; From 02f70f3e53ca3f34153191ff1e6ee93c9b637bdb Mon Sep 17 00:00:00 2001 From: Sander van de Graaf Date: Thu, 24 Dec 2015 11:20:39 +0100 Subject: [PATCH 02/17] added initial esp8266 implementation --- src/common/AWSClient2.cpp | 28 +++-- src/common/AWSClient2.h | 23 ++-- src/common/AmazonIOTClient.cpp | 59 ++++++++++ src/common/AmazonIOTClient.h | 22 ++++ src/common/AmazonKinesisClient.cpp | 1 - src/common/ESP8266AWSImplementations.h | 1 + src/common/ESP8266AWSImplentations.cpp | 1 + src/esp8266/ESP8266AWSImplementations.h | 38 ++++++ src/esp8266/ESP8266AWSImplentations.cpp | 147 ++++++++++++++++++++++++ 9 files changed, 304 insertions(+), 16 deletions(-) create mode 100644 src/common/AmazonIOTClient.cpp create mode 100644 src/common/AmazonIOTClient.h create mode 120000 src/common/ESP8266AWSImplementations.h create mode 120000 src/common/ESP8266AWSImplentations.cpp create mode 100644 src/esp8266/ESP8266AWSImplementations.h create mode 100644 src/esp8266/ESP8266AWSImplentations.cpp diff --git a/src/common/AWSClient2.cpp b/src/common/AWSClient2.cpp index bb178a0..5a9a3ab 100644 --- a/src/common/AWSClient2.cpp +++ b/src/common/AWSClient2.cpp @@ -18,9 +18,9 @@ static const char* CANONICAL_FORM_POST_LINE = "POST\n/\n\n"; static const int CANONICAL_FORM_POST_LINE_LEN = 8; static const char* HTTPS_REQUEST_POST_LINE = - "POST https://%s.%s.%s/ HTTP/1.1\n"; + "POST https://%s/ HTTP/1.1\n"; static const int HTTPS_REQUEST_POST_LINE_LEN = 28; -static const char* HTTP_REQUEST_POST_LINE = "POST http://%s.%s.%s/ HTTP/1.1\n"; +static const char* HTTP_REQUEST_POST_LINE = "POST http://%s/ HTTP/1.1\n"; static const int HTTP_REQUEST_POST_LINE_LEN = 27; static const char* TO_SIGN_TEMPLATE = "AWS4-HMAC-SHA256\n%sT%sZ\n%s/%s/%s/aws4_request\n%s"; @@ -62,6 +62,11 @@ void AWSClient2::setAWSEndpoint(const char * awsEndpoint) { this->awsEndpoint = new char[len](); strcpy(this->awsEndpoint, awsEndpoint); } +void AWSClient2::setAWSDomain(const char * awsDomain) { + int len = strlen(awsDomain) + 1; + this->awsDomain = new char[len](); + strcpy(this->awsDomain, awsDomain); +} void AWSClient2::setAWSSecretKey(const char * awsSecKey) { int len = strlen(awsSecKey) + 1; this->awsSecKey = new char[len](); @@ -120,6 +125,16 @@ void AWSClient2::initSignedHeaders() { headerLens[headersCreated++] = len; } +char* AWSClient2::createHostString(void) { + if(awsDomain[0] != '\0') { + return awsDomain; + } else { + char* host = new char[200](); + sprintf(host, "%s.%s.%s", awsService, awsRegion, awsEndpoint); + return host; + } +} + char* AWSClient2::createStringToSign(void) { SHA256* sha256 = new SHA256(); char* hashed; @@ -251,8 +266,8 @@ char* AWSClient2::headersToRequest() { httpS ? HTTPS_REQUEST_POST_LINE : HTTP_REQUEST_POST_LINE; /* Calculate length of httpRequest string. */ - int httpRequestLen = postLineLen + strlen(awsService) + strlen(awsRegion) - + strlen(awsEndpoint); + char* host = createHostString(); + int httpRequestLen = postLineLen + strlen(host); for (int i = 0; i < headersCreated; i++) { /* +1 for newline. */ httpRequestLen += *(headerLens + i) + 1; @@ -263,8 +278,7 @@ char* AWSClient2::headersToRequest() { /* Create and write to the httpRequest string. */ char* httpRequest = new char[httpRequestLen + 1](); int httpRequestWritten = 0; - httpRequestWritten += sprintf(httpRequest + httpRequestWritten, postLine, - awsService, awsRegion, awsEndpoint); + httpRequestWritten += sprintf(httpRequest + httpRequestWritten, postLine, host); for (int i = 0; i < headersCreated; i++) { httpRequestWritten += sprintf(httpRequest + httpRequestWritten, "%s\n", *(headers + i)); @@ -280,7 +294,7 @@ char* AWSClient2::createRequest(MinimalString &reqPayload) { if (awsRegion == 0 || awsEndpoint == 0 || awsSecKey == 0 || awsKeyID == 0 || httpClient == 0 || dateTimeProvider == 0) return 0; - + createRequestInit(reqPayload); char* request = headersToRequest(); createRequestCleanup(); diff --git a/src/common/AWSClient2.h b/src/common/AWSClient2.h index c3d0828..d8998e5 100644 --- a/src/common/AWSClient2.h +++ b/src/common/AWSClient2.h @@ -12,13 +12,13 @@ #include "AWSFoundationalTypes.h" /* Total number of headers. */ -static const int HEADER_COUNT = 7; +static const int HEADER_COUNT2 = 7; /* Size of the awsDate string. */ -static const int AWS_DATE_LEN = 8; +static const int AWS_DATE_LEN2 = 8; /* Size of the awsTime string. */ -static const int AWS_TIME_LEN = 6; +static const int AWS_TIME_LEN2 = 6; /* Size of sha hashes and signatures in hexidecimal. */ -static const int HASH_HEX_LEN = 64; +static const int HASH_HEX_LEN2 = 64; /* Base class for an AWS Service Client. Creates http and https request in raw * http format or as a curl command. */ @@ -27,20 +27,22 @@ class AWSClient2 { char* awsRegion; /* Endpoint, eg. "amazonaws.com" in "kinesis.us-east-1.amazonaws.com". */ char* awsEndpoint; + /* Subdomain, eg. "A2MBBEONHC7LUH" in "A2MBBEONHC9LUG.iot.us-east-1.amazonaws.com". */ + char* awsDomain; /* The user's AWS Secret Key for accessing the AWS Resource. */ char* awsSecKey; /* The user's AWS Access Key ID for accessing the AWS Resource. */ char* awsKeyID; /* GMT date in yyyyMMdd format. */ - char awsDate[AWS_DATE_LEN + 1]; + char awsDate[AWS_DATE_LEN2 + 1]; /* GMT time in HHmmss format. */ - char awsTime[AWS_TIME_LEN + 1]; + char awsTime[AWS_TIME_LEN2 + 1]; /* Number of headers created. */ int headersCreated; /* Array of the created http headers. */ - char* headers[HEADER_COUNT]; + char* headers[HEADER_COUNT2]; /* Array of string lengths of the headers in the "headers" array. */ - int headerLens[HEADER_COUNT]; + int headerLens[HEADER_COUNT2]; /* The payload of the httprequest to be created */ MinimalString payload; @@ -76,6 +78,8 @@ class AWSClient2 { const char* awsService; /* Content type of payload, eg. "application/x-amz-json-1.1". */ const char* contentType; + // /* Generates the host based on subdomain, service, etc */ + // char* createHostString(void); /* Creates a raw http request, given the payload and current GMT date in * yyyyMMddHHmmss format. Should be exposed to user by extending class. * Returns 0 if client is unititialized. */ @@ -88,8 +92,11 @@ class AWSClient2 { public: /* Setters for values used by createRequest and createCurlRequest. Must * be set or create[Curl]Request will return null. */ + /* Generates the host based on subdomain, service, etc */ + char* createHostString(void); void setAWSRegion(const char * awsRegion); void setAWSEndpoint(const char * awsEndpoint); + void setAWSDomain(const char * awsDomain); void setAWSSecretKey(const char * awsSecKey); void setAWSKeyID(const char * awsKeyID); void setHttpClient(IHttpClient* httpClient); diff --git a/src/common/AmazonIOTClient.cpp b/src/common/AmazonIOTClient.cpp new file mode 100644 index 0000000..0041348 --- /dev/null +++ b/src/common/AmazonIOTClient.cpp @@ -0,0 +1,59 @@ +#include "AmazonIOTClient.h" +#include "AWSFoundationalTypes.h" +#include +#include "Utils.h" + +static const char* SERVICE = "iotdata"; +static const char* FORM_TYPE = "application/json"; +static const char* PAYLOAD_TEMPLATE = "%s"; +// int PAYLOAD_TEMPLATE_LENGTH = 2; +int IOT_EXTRACTED_TIMESTAMP_BUFFER_LENGTH = 17; +int IOT_FORMATTED_TIMESTAMP_BUFFER_LENGTH = 15; + +AmazonIOTClient::AmazonIOTClient() : AWSClient2() { + awsService = SERVICE; + httpS = true; +} + +char* AmazonIOTClient::update_shadow(MinimalString url, MinimalString shadow, ActionError& actionError) { + + actionError = NONE_ACTIONERROR; + contentType = FORM_TYPE; + + // char* request = createRequest(shadow, url); + // publishOutput.setResponse(request); + + // char* request = createRequest(url, shadow); + char* request = createRequest(shadow); + return request; + + // char* response = sendData(request); + // delete[] request; + // + // if (response == NULL) { + // actionError = CONNECTION_ACTIONERROR; + // return 999; + // } + // + // int httpStatusCode = findHttpStatusCode(response); + // + // if (httpStatusCode == 200) { + // return 1; + // } + // + // if (httpStatusCode == 403) { + // char* ts = strstr(response, "earlier than "); + // int pos = ts - response; + // + // char* newts = new char[IOT_EXTRACTED_TIMESTAMP_BUFFER_LENGTH](); + // strncpy(newts, response + pos + 31, IOT_EXTRACTED_TIMESTAMP_BUFFER_LENGTH - 1); + // newts[16] = '\0'; + // + // char* time = new char[IOT_FORMATTED_TIMESTAMP_BUFFER_LENGTH](); + // sprintf(time, "%.8s%.6s", newts, newts + 9); + // dateTimeProvider->sync(time); + // return 0; + // } + // + // return httpStatusCode; +} diff --git a/src/common/AmazonIOTClient.h b/src/common/AmazonIOTClient.h new file mode 100644 index 0000000..a90941b --- /dev/null +++ b/src/common/AmazonIOTClient.h @@ -0,0 +1,22 @@ +#ifndef AMAZONIOTCLIENT_H_ +#define AMAZONIOTCLIENT_H_ +#include "AWSClient2.h" + + +// class Shadow { +// MinimalString shadow; +// void reset(); +// public: +// void setShadow(shadow) const; +// }; + + + +class AmazonIOTClient : public AWSClient2 { +public: + AmazonIOTClient(); + + char* update_shadow(MinimalString url, MinimalString shadow, ActionError& actionError); +}; + +#endif /* AMAZONSNSCLIENT_H_ */ diff --git a/src/common/AmazonKinesisClient.cpp b/src/common/AmazonKinesisClient.cpp index 7cd876c..5c6973f 100644 --- a/src/common/AmazonKinesisClient.cpp +++ b/src/common/AmazonKinesisClient.cpp @@ -2354,4 +2354,3 @@ KinesisErrorCheckingOnlyOutput AmazonKinesisClient::splitShard(SplitShardInput s } return kinesisErrorCheckingOnlyOutput; } - diff --git a/src/common/ESP8266AWSImplementations.h b/src/common/ESP8266AWSImplementations.h new file mode 120000 index 0000000..c10521d --- /dev/null +++ b/src/common/ESP8266AWSImplementations.h @@ -0,0 +1 @@ +/Users/svdgraaf/Documents/Arduino/Libraries/aws-sdk-arduino/src/esp8266/ESP8266AWSImplementations.h \ No newline at end of file diff --git a/src/common/ESP8266AWSImplentations.cpp b/src/common/ESP8266AWSImplentations.cpp new file mode 120000 index 0000000..1e73c50 --- /dev/null +++ b/src/common/ESP8266AWSImplentations.cpp @@ -0,0 +1 @@ +/Users/svdgraaf/Documents/Arduino/Libraries/aws-sdk-arduino/src/esp8266/ESP8266AWSImplentations.cpp \ No newline at end of file diff --git a/src/esp8266/ESP8266AWSImplementations.h b/src/esp8266/ESP8266AWSImplementations.h new file mode 100644 index 0000000..47f3a68 --- /dev/null +++ b/src/esp8266/ESP8266AWSImplementations.h @@ -0,0 +1,38 @@ +#ifndef AWSESP2866IMPLEMENTATIONS_H_ +#define AWSESP2866IMPLEMENTATIONS_H_ +#include "DeviceIndependentInterfaces.h" +/* application.h is Esp8266's standard library. Define TCPClient. */ +//#include +#include + +/* HttpClient implementation to be used on the Esp8266 Core device. */ +class Esp8266HttpClient: public IHttpClient { + WiFiClientSecure client; + //TCPClient client; +public: + Esp8266HttpClient(); + /* Send http request and return the response. */ + char* send(const char *request, const char* serverUrl, int port); + /* Returns false. Client uses raw http/https. */ + bool usesCurl(void); +}; + +class Esp8266DateTimeProvider: public IDateTimeProvider { + /* The time as a cstring in yyyyMMddHHmmss format. Is written to within and + * returned by getDateTime(). */ + WiFiClient client2; + //char dateTime[15]; +public: + char dateTime[15]; + Esp8266DateTimeProvider(); + /* Retrieve the current GMT date and time in yyyyMMddHHmmss format. */ + const char* getDateTime(void); + /* Returns false because Esp8266 has it's own mechanism for syncing that does + * not require an argument. */ + bool syncTakesArg(void); + /* Synchronizes Esp8266's date and time with Esp8266's servers. The dateTime + * argument is ignored. */ + void sync(const char* dateTime); +}; + +#endif /* AWSESP2866IMPLEMENTATIONS_H_ */ diff --git a/src/esp8266/ESP8266AWSImplentations.cpp b/src/esp8266/ESP8266AWSImplentations.cpp new file mode 100644 index 0000000..a4e961b --- /dev/null +++ b/src/esp8266/ESP8266AWSImplentations.cpp @@ -0,0 +1,147 @@ +#include +/* application.h is Esp8266's standard library. Defines the Arduino String + * object, the Arduino delay() procedure, and the Esp8266 TCPClient. */ +//#include +#include "Esp8266AWSImplementations.h" +#include "DeviceIndependentInterfaces.h" +#include + +int delayTime = 500; +char* updateCurTime(void); + +Esp8266HttpClient::Esp8266HttpClient() { +} + +char* Esp8266HttpClient::send(const char* request, const char* serverUrl, int port) { + + /* Arduino String to build the response with. */ + String responseBuilder = ""; + if (client.connect(serverUrl, port)) { + /* Send the requests */ + client.println(request); + client.println(); + /* Read the request into responseBuilder. */ + delay(delayTime); + while (client.available()) { + char c = client.read(); + responseBuilder.concat(c); + } + client.stop(); + } else { + client.stop(); + /* Error connecting. */ + return 0; + } + /* Copy responseBuilder into char* */ + int len = responseBuilder.length(); + char* response = new char[len + 1](); + responseBuilder.toCharArray(response, len + 1); + return response; + return 0; +} + +bool Esp8266HttpClient::usesCurl() { + /* Does not use curl command. */ + return false; +} + +Esp8266DateTimeProvider::Esp8266DateTimeProvider() { + /* No need to sync, spark sychronizes time on startup. */ + //strcpy(dateTime, updateCurTime2(client2)); +} + +const char* Esp8266DateTimeProvider::getDateTime() { + // return "20151221232400"; + return updateCurTime(); +} +bool Esp8266DateTimeProvider::syncTakesArg(void) { + return true; +} + +void Esp8266DateTimeProvider::sync(const char* dateTime) { + /* Use Esp8266's servers to synchronize current time. */ + //Esp8266.syncTime(); + ///dateTime = updateCurTime(); + //strcpy(dateTime,tNow); +} + +//////////////////////////////////// +// convert month to digits +//////////////////////////////////// +String getMonth(String sM) { + if(sM=="Jan") return "01"; + if(sM=="Feb") return "02"; + if(sM=="Mar") return "03"; + if(sM=="Apr") return "04"; + if(sM=="May") return "05"; + if(sM=="Jun") return "06"; + if(sM=="Jul") return "07"; + if(sM=="Aug") return "08"; + if(sM=="Sep") return "09"; + if(sM=="Oct") return "10"; + if(sM=="Nov") return "11"; + if(sM=="Dec") return "12"; + return "01"; +} + +//////////////////////////////////// +// Scrape UTC Time from server +//////////////////////////////////// +char* updateCurTime(void) { + static int timeout_busy=0; + int ipos; + timeout_busy=0; //reset + + const char* timeServer = "aws.amazon.com"; + + // send a bad header on purpose, so we get a 400 with a DATE: timestamp + const char* timeServerGet = "GET example.com/ HTTP/1.1"; + String utctime; + String GmtDate; + static char dateStamp[20]; + static char chBuf[200]; + char utctimeraw[80]; + char* dpos; + + WiFiClient client2; + if (client2.connect(timeServer, 80)) { + //Send Request + client2.println(timeServerGet); + client2.println(); + while((!client2.available())&&(timeout_busy++<5000)){ + // Wait until the client sends some data + delay(1); + } + + // kill client if timeout + if(timeout_busy>=5000) { + client2.flush(); + client2.stop(); + Serial.println("timeout receiving timeserver data\n"); + return dateStamp; + } + + // read the http GET Response + String req2 = client2.readString(); + // Serial.print(req2); + + // close connection + delay(1); + client2.flush(); + client2.stop(); + + ipos = req2.indexOf("Date:"); + if(ipos>0) { + GmtDate = req2.substring(ipos,ipos+35); + // Serial.println(GmtDate); + utctime = GmtDate.substring(18,22) + getMonth(GmtDate.substring(14,17)) + GmtDate.substring(11,13) + GmtDate.substring(23,25) + GmtDate.substring(26,28) + GmtDate.substring(29,31); + // Serial.println(utctime.substring(0,14)); + utctime.substring(0,14).toCharArray(dateStamp, 20); + } + } + else { + Serial.println("did not connect to timeserver\n"); + } + timeout_busy=0; //reset timeout + return dateStamp; //Return latest or last good dateStamp +} From d09b5b58cc1d3327955d87240c0a3afdd21f5e90 Mon Sep 17 00:00:00 2001 From: Sander van de Graaf Date: Thu, 24 Dec 2015 15:04:39 +0100 Subject: [PATCH 03/17] put AWSClient2 back where it belongs, start implementing AWSv4 signatures --- src/common/AWSClient2.cpp | 26 +- src/common/AWSClient2.h | 23 +- src/common/AWSClient4.cpp | 350 ++++++++++++++++++++++++ src/common/AWSClient4.h | 110 ++++++++ src/common/AmazonIOTClient.cpp | 9 +- src/common/AmazonIOTClient.h | 8 +- src/esp8266/ESP8266AWSImplementations.h | 1 - src/esp8266/ESP8266AWSImplentations.cpp | 33 ++- 8 files changed, 503 insertions(+), 57 deletions(-) create mode 100644 src/common/AWSClient4.cpp create mode 100644 src/common/AWSClient4.h diff --git a/src/common/AWSClient2.cpp b/src/common/AWSClient2.cpp index 5a9a3ab..7353e58 100644 --- a/src/common/AWSClient2.cpp +++ b/src/common/AWSClient2.cpp @@ -18,9 +18,9 @@ static const char* CANONICAL_FORM_POST_LINE = "POST\n/\n\n"; static const int CANONICAL_FORM_POST_LINE_LEN = 8; static const char* HTTPS_REQUEST_POST_LINE = - "POST https://%s/ HTTP/1.1\n"; + "POST https://%s.%s.%s/ HTTP/1.1\n"; static const int HTTPS_REQUEST_POST_LINE_LEN = 28; -static const char* HTTP_REQUEST_POST_LINE = "POST http://%s/ HTTP/1.1\n"; +static const char* HTTP_REQUEST_POST_LINE = "POST http://%s.%s.%s/ HTTP/1.1\n"; static const int HTTP_REQUEST_POST_LINE_LEN = 27; static const char* TO_SIGN_TEMPLATE = "AWS4-HMAC-SHA256\n%sT%sZ\n%s/%s/%s/aws4_request\n%s"; @@ -62,11 +62,6 @@ void AWSClient2::setAWSEndpoint(const char * awsEndpoint) { this->awsEndpoint = new char[len](); strcpy(this->awsEndpoint, awsEndpoint); } -void AWSClient2::setAWSDomain(const char * awsDomain) { - int len = strlen(awsDomain) + 1; - this->awsDomain = new char[len](); - strcpy(this->awsDomain, awsDomain); -} void AWSClient2::setAWSSecretKey(const char * awsSecKey) { int len = strlen(awsSecKey) + 1; this->awsSecKey = new char[len](); @@ -125,16 +120,6 @@ void AWSClient2::initSignedHeaders() { headerLens[headersCreated++] = len; } -char* AWSClient2::createHostString(void) { - if(awsDomain[0] != '\0') { - return awsDomain; - } else { - char* host = new char[200](); - sprintf(host, "%s.%s.%s", awsService, awsRegion, awsEndpoint); - return host; - } -} - char* AWSClient2::createStringToSign(void) { SHA256* sha256 = new SHA256(); char* hashed; @@ -266,8 +251,8 @@ char* AWSClient2::headersToRequest() { httpS ? HTTPS_REQUEST_POST_LINE : HTTP_REQUEST_POST_LINE; /* Calculate length of httpRequest string. */ - char* host = createHostString(); - int httpRequestLen = postLineLen + strlen(host); + int httpRequestLen = postLineLen + strlen(awsService) + strlen(awsRegion) + + strlen(awsEndpoint); for (int i = 0; i < headersCreated; i++) { /* +1 for newline. */ httpRequestLen += *(headerLens + i) + 1; @@ -278,7 +263,8 @@ char* AWSClient2::headersToRequest() { /* Create and write to the httpRequest string. */ char* httpRequest = new char[httpRequestLen + 1](); int httpRequestWritten = 0; - httpRequestWritten += sprintf(httpRequest + httpRequestWritten, postLine, host); + httpRequestWritten += sprintf(httpRequest + httpRequestWritten, postLine, + awsService, awsRegion, awsEndpoint); for (int i = 0; i < headersCreated; i++) { httpRequestWritten += sprintf(httpRequest + httpRequestWritten, "%s\n", *(headers + i)); diff --git a/src/common/AWSClient2.h b/src/common/AWSClient2.h index d8998e5..c3d0828 100644 --- a/src/common/AWSClient2.h +++ b/src/common/AWSClient2.h @@ -12,13 +12,13 @@ #include "AWSFoundationalTypes.h" /* Total number of headers. */ -static const int HEADER_COUNT2 = 7; +static const int HEADER_COUNT = 7; /* Size of the awsDate string. */ -static const int AWS_DATE_LEN2 = 8; +static const int AWS_DATE_LEN = 8; /* Size of the awsTime string. */ -static const int AWS_TIME_LEN2 = 6; +static const int AWS_TIME_LEN = 6; /* Size of sha hashes and signatures in hexidecimal. */ -static const int HASH_HEX_LEN2 = 64; +static const int HASH_HEX_LEN = 64; /* Base class for an AWS Service Client. Creates http and https request in raw * http format or as a curl command. */ @@ -27,22 +27,20 @@ class AWSClient2 { char* awsRegion; /* Endpoint, eg. "amazonaws.com" in "kinesis.us-east-1.amazonaws.com". */ char* awsEndpoint; - /* Subdomain, eg. "A2MBBEONHC7LUH" in "A2MBBEONHC9LUG.iot.us-east-1.amazonaws.com". */ - char* awsDomain; /* The user's AWS Secret Key for accessing the AWS Resource. */ char* awsSecKey; /* The user's AWS Access Key ID for accessing the AWS Resource. */ char* awsKeyID; /* GMT date in yyyyMMdd format. */ - char awsDate[AWS_DATE_LEN2 + 1]; + char awsDate[AWS_DATE_LEN + 1]; /* GMT time in HHmmss format. */ - char awsTime[AWS_TIME_LEN2 + 1]; + char awsTime[AWS_TIME_LEN + 1]; /* Number of headers created. */ int headersCreated; /* Array of the created http headers. */ - char* headers[HEADER_COUNT2]; + char* headers[HEADER_COUNT]; /* Array of string lengths of the headers in the "headers" array. */ - int headerLens[HEADER_COUNT2]; + int headerLens[HEADER_COUNT]; /* The payload of the httprequest to be created */ MinimalString payload; @@ -78,8 +76,6 @@ class AWSClient2 { const char* awsService; /* Content type of payload, eg. "application/x-amz-json-1.1". */ const char* contentType; - // /* Generates the host based on subdomain, service, etc */ - // char* createHostString(void); /* Creates a raw http request, given the payload and current GMT date in * yyyyMMddHHmmss format. Should be exposed to user by extending class. * Returns 0 if client is unititialized. */ @@ -92,11 +88,8 @@ class AWSClient2 { public: /* Setters for values used by createRequest and createCurlRequest. Must * be set or create[Curl]Request will return null. */ - /* Generates the host based on subdomain, service, etc */ - char* createHostString(void); void setAWSRegion(const char * awsRegion); void setAWSEndpoint(const char * awsEndpoint); - void setAWSDomain(const char * awsDomain); void setAWSSecretKey(const char * awsSecKey); void setAWSKeyID(const char * awsKeyID); void setHttpClient(IHttpClient* httpClient); diff --git a/src/common/AWSClient4.cpp b/src/common/AWSClient4.cpp new file mode 100644 index 0000000..570e9ad --- /dev/null +++ b/src/common/AWSClient4.cpp @@ -0,0 +1,350 @@ +/* + * AWSClient.cpp + * + * See AWSClient.h for description. + * + */ + +#include "AWSClient4.h" +#include "Utils.h" +#include "DeviceIndependentInterfaces.h" +#include "AWSFoundationalTypes.h" +#include "sha256.h" +#include +#include +#include +#include + +// +// /* Constants string, formats, and lengths. */ +// static const char* CANONICAL_FORM_POST_LINE = "POST\n/\n\n"; +// static const int CANONICAL_FORM_POST_LINE_LEN = 8; +// static const char* HTTPS_REQUEST_POST_LINE = +// "POST https://%s/%s\n"; +// // static const char* HTTPS_REQUEST_POST_LINE = +// // "POST https://%s/%s HTTP/1.1\n"; +// static const int HTTPS_REQUEST_POST_LINE_LEN = 19; +// static const char* HTTP_REQUEST_POST_LINE = "POST http://%s/%s HTTP/1.1\n"; +// static const int HTTP_REQUEST_POST_LINE_LEN = 27; +// static const char* TO_SIGN_TEMPLATE = +// "AWS4-HMAC-SHA256\n%sT%sZ\n%s/%s/%s/aws4_request\n%s"; +// static const int TO_SIGN_TEMPLATE_LEN = 36; +// static const char* CONTENT_LENGTH_HEADER = "content-length:%d"; +// static const int CONTENT_LENGTH_HEADER_LEN = 15; +// static const char* HOST_HEADER = "host:%s"; +// static const int HOST_HEADER_LEN = 7; +// static const char* CONNECTION_HEADER = "Connection:close"; +// static const int CONNECTION_HEADER_LEN = 16; +// static const char* CONTENT_TYPE_HEADER = "content-type:%s"; +// static const int CONTENT_TYPE_HEADER_LEN = 13; +// static const char* X_AMZ_DATE_HEADER = "x-amz-date:%sT%sZ"; +// static const int X_AMZ_DATE_HEADER_LEN = 13; +// static const char* AUTHORIZATION_HEADER = +// "Authorization: AWS4-HMAC-SHA256 Credential=%s/%s/%s/%s/aws4_request, SignedHeaders=%s, Signature=%s"; +// static const int AUTHORIZATION_HEADER_LEN = 87; +// static const char* SIGNED_HEADERS = +// "content-length;content-type;host;x-amz-date"; +// static const int SIGNED_HEADERS_LEN = 43; +// +AWSClient4::AWSClient4() { + /* Null until set in init method. */ + awsRegion = 0; + awsEndpoint = 0; + awsSecKey = 0; + awsKeyID = 0; + httpClient = 0; + dateTimeProvider = 0; +} + +void AWSClient4::setAWSRegion(const char * awsRegion) { + int len = strlen(awsRegion) + 1; + this->awsRegion = new char[len](); + strcpy(this->awsRegion, awsRegion); +} +void AWSClient4::setAWSEndpoint(const char * awsEndpoint) { + int len = strlen(awsEndpoint) + 1; + this->awsEndpoint = new char[len](); + strcpy(this->awsEndpoint, awsEndpoint); +} +void AWSClient4::setAWSDomain(const char * awsDomain) { + int len = strlen(awsDomain) + 1; + this->awsDomain = new char[len](); + strcpy(this->awsDomain, awsDomain); +} +void AWSClient4::setAWSPath(const char * awsPath) { + int len = strlen(awsPath) + 1; + this->awsPath = new char[len](); + strcpy(this->awsPath, awsPath); +} +void AWSClient4::setAWSSecretKey(const char * awsSecKey) { + int len = strlen(awsSecKey) + 1; + this->awsSecKey = new char[len](); + strcpy(this->awsSecKey, awsSecKey); +} +void AWSClient4::setAWSKeyID(const char * awsKeyID) { + int len = strlen(awsKeyID) + 1; + this->awsKeyID = new char[len](); + strcpy(this->awsKeyID, awsKeyID); +} +void AWSClient4::setHttpClient(IHttpClient* httpClient) { + this->httpClient = httpClient; +} +void AWSClient4::setDateTimeProvider(IDateTimeProvider* dateTimeProvider) { + this->dateTimeProvider = dateTimeProvider; +} +// +// AWSClient4::~AWSClient4() { +// if (awsRegion != 0) +// delete[] awsRegion; +// if (awsEndpoint != 0) +// delete[] awsEndpoint; +// if (awsSecKey != 0) +// delete[] awsSecKey; +// if (awsKeyID != 0) +// delete[] awsKeyID; +// } +// +// void AWSClient4::initSignedHeaders() { +// /* For each of the formats for unsigned headers, determine the size of the +// * formatted string, allocate that much space in the next available element +// * in the headers array, create the string, and add it's length to the +// * headerLens array. */ +// +// int contentLen = payload.length(); +// int len = CONTENT_LENGTH_HEADER_LEN + digitCount(contentLen); +// headers[headersCreated] = new char[len + 1](); +// sprintf(headers[headersCreated], CONTENT_LENGTH_HEADER, contentLen); +// headerLens[headersCreated++] = len; +// +// len = CONTENT_TYPE_HEADER_LEN + strlen(contentType); +// headers[headersCreated] = new char[len + 1](); +// sprintf(headers[headersCreated], CONTENT_TYPE_HEADER, contentType); +// headerLens[headersCreated++] = len; +// +// char* host = createHostString(); +// len = HOST_HEADER_LEN + strlen(host); +// headers[headersCreated] = new char[len + 1](); +// +// sprintf(headers[headersCreated], HOST_HEADER, host); +// headerLens[headersCreated++] = len; +// +// len = X_AMZ_DATE_HEADER_LEN + AWS_DATE_LEN2 + AWS_TIME_LEN2; +// headers[headersCreated] = new char[len + 1](); +// sprintf(headers[headersCreated], X_AMZ_DATE_HEADER, awsDate, awsTime); +// headerLens[headersCreated++] = len; +// } +// +// char* AWSClient4::createHostString(void) { +// if(awsDomain[0] != '\0') { +// return awsDomain; +// } else { +// char* host = new char[200](); +// sprintf(host, "%s.%s.%s", awsService, awsRegion, awsEndpoint); +// return host; +// } +// } +// +// char* AWSClient4::createStringToSign(void) { +// SHA256* sha256 = new SHA256(); +// char* hashed; +// /* Calculate length of canonicalForm string. */ +// int canonicalFormLen = CANONICAL_FORM_POST_LINE_LEN; +// for (int i = 0; i < headersCreated; i++) { +// /* +1 for newlines */ +// canonicalFormLen += *(headerLens + i) + 1; +// } +// /* +2 for newlines. */ +// canonicalFormLen += SIGNED_HEADERS_LEN + HASH_HEX_LEN2 + 2; +// +// char* canonicalForm = new char[canonicalFormLen + 1](); +// +// /* Write the cannonicalForm string. */ +// int canonicalFormWritten = 0; +// canonicalFormWritten += strlen( +// strcpy(canonicalForm + canonicalFormWritten, +// CANONICAL_FORM_POST_LINE)); +// for (int i = 0; i < headersCreated; i++) { +// canonicalFormWritten += sprintf(canonicalForm + canonicalFormWritten, +// "%s\n", *(headers + i)); +// } +// canonicalFormWritten += sprintf(canonicalForm + canonicalFormWritten, +// "\n%s\n", SIGNED_HEADERS); +// hashed = (*sha256)(payload.getCStr(), payload.length()); +// strcpy(canonicalForm + canonicalFormWritten, hashed); +// delete[] hashed; +// canonicalFormWritten += HASH_HEX_LEN2; +// +// /* Hash the canonicalForm string. */ +// hashed = (*sha256)(canonicalForm, canonicalFormWritten); +// delete sha256; +// +// delete[] canonicalForm; +// +// /* Determine the size to the string to sign. */ +// int toSignLen = TO_SIGN_TEMPLATE_LEN + 2 * AWS_DATE_LEN2 + AWS_TIME_LEN2 +// + strlen(awsRegion) + strlen(awsService) + HASH_HEX_LEN2; +// +// /* Create and return the string to sign. */ +// char* toSign = new char[toSignLen + 1](); +// sprintf(toSign, TO_SIGN_TEMPLATE, awsDate, awsTime, awsDate, awsRegion, +// awsService, hashed); +// delete[] hashed; +// return toSign; +// +// } +// char* AWSClient4::createSignature(const char* toSign) { +// +// /* Allocate memory for the signature */ +// char* signature = new char[HASH_HEX_LEN2 + 1](); +// +// /* Create the signature key */ +// /* + 4 for "AWS4" */ +// int keyLen = strlen(awsSecKey) + 4; +// char* key = new char[keyLen + 1](); +// sprintf(key, "AWS4%s", awsSecKey); +// +// /* repeatedly apply hmac with the appropriate values. See +// * http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html +// * for algorithm. */ +// char* k1 = hmacSha256(key, keyLen, awsDate, strlen(awsDate)); +// delete[] key; +// char* k2 = hmacSha256(k1, SHA256_DEC_HASH_LEN, awsRegion, +// strlen(awsRegion)); +// delete[] k1; +// char* k3 = hmacSha256(k2, SHA256_DEC_HASH_LEN, awsService, +// strlen(awsService)); +// delete[] k2; +// char* k4 = hmacSha256(k3, SHA256_DEC_HASH_LEN, "aws4_request", 12); +// delete[] k3; +// char* k5 = hmacSha256(k4, SHA256_DEC_HASH_LEN, toSign, strlen(toSign)); +// delete[] k4; +// +// /* Convert the chars in hash to hex for signature. */ +// for (int i = 0; i < SHA256_DEC_HASH_LEN; ++i) { +// sprintf(signature + 2 * i, "%02lx", 0xff & (unsigned long) k5[i]); +// } +// delete[] k5; +// return signature; +// } +// +// void AWSClient4::initUnsignedHeaders(const char* signature) { +// int len = AUTHORIZATION_HEADER_LEN + strlen(awsKeyID) + AWS_DATE_LEN2 +// + strlen(awsRegion) + strlen(awsService) + SIGNED_HEADERS_LEN +// + HASH_HEX_LEN2; +// headers[headersCreated] = new char[len + 1](); +// sprintf(headers[headersCreated], AUTHORIZATION_HEADER, awsKeyID, awsDate, +// awsRegion, awsService, SIGNED_HEADERS, signature); +// headerLens[headersCreated++] = len; +// /* +// len = CONNECTION_HEADER_LEN; +// headers[headersCreated] = new char[len + 1](); +// strcpy(headers[headersCreated], CONNECTION_HEADER); +// headerLens[headersCreated++] = len; +// */ +// } +// +// void AWSClient4::createRequestInit(MinimalString &reqPayload) { +// //initialize object-scoped variables +// const char* dateTime = dateTimeProvider->getDateTime(); +// +// // @TODO: not sure yet why sprintf doesn't work here +// strncpy(awsDate, dateTime, 8); +// awsDate[9] = '\0'; +// strncpy(awsTime, dateTime + 8, 6); +// awsTime[7] = '\0'; +// +// Serial.println(awsDate); +// Serial.println(awsTime); +// +// payload = reqPayload; +// headersCreated = 0; +// +// //Create signature and headers +// initSignedHeaders(); +// char* toSign = createStringToSign(); +// char* signature = createSignature(toSign); +// delete[] toSign; +// initUnsignedHeaders(signature); +// delete[] signature; +// } +// +// void AWSClient4::createRequestCleanup() { +// /* Free each header */ +// for (int i = 0; i < headersCreated; i++) { +// delete[] headers[i]; +// } +// } +// +// char* AWSClient4::headersToRequest() { +// /* Determine whether to use https or http postLine values. */ +// int postLineLen = +// httpS ? HTTPS_REQUEST_POST_LINE_LEN : HTTP_REQUEST_POST_LINE_LEN; +// const char* postLine = +// httpS ? HTTPS_REQUEST_POST_LINE : HTTP_REQUEST_POST_LINE; +// +// /* Calculate length of httpRequest string. */ +// char* host = createHostString(); +// int httpRequestLen; +// +// // if a path is set, append it to the post header +// if(awsPath[0] != '\0') { +// httpRequestLen = postLineLen + strlen(host) + strlen(awsPath); +// } else { +// httpRequestLen = postLineLen + strlen(host); +// } +// +// for (int i = 0; i < headersCreated; i++) { +// /* +1 for newline. */ +// httpRequestLen += *(headerLens + i) + 1; +// } +// /* +1 for newline. */ +// httpRequestLen += payload.length() + 1; +// +// /* Create and write to the httpRequest string. */ +// char* httpRequest = new char[httpRequestLen + 1](); +// int httpRequestWritten = 0; +// +// // if a path is set, append it to the post header +// if(awsPath[0] != '\0') { +// httpRequestWritten += sprintf(httpRequest + httpRequestWritten, postLine, host, awsPath); +// } else { +// httpRequestWritten += sprintf(httpRequest + httpRequestWritten, postLine, host); +// } +// for (int i = 0; i < headersCreated; i++) { +// httpRequestWritten += sprintf(httpRequest + httpRequestWritten, "%s\n", +// *(headers + i)); +// } +// httpRequestWritten += sprintf(httpRequest + httpRequestWritten, "\n%s", +// payload.getCStr()); +// +// return httpRequest; +// } + + + + +char* AWSClient4::createRequest(MinimalString &reqPayload) { + /* Check that all values have been initialized. */ + if (awsRegion == 0 || awsEndpoint == 0 || awsSecKey == 0 || awsKeyID == 0 + || httpClient == 0 || dateTimeProvider == 0) + return 0; + + map headers; + headers['host'] = "bar"; + + return headers['host']; + + // createRequestInit(reqPayload); + // char* request = headersToRequest(); + // createRequestCleanup(); + + // return request; +} + +char* AWSClient4::sendData(const char* data) { + char* server = createHostString(); + int port = httpS ? 443 : 80; + char* response = httpClient->send(data, "A2MBBEONHC9LUG.iot.eu-west-1.amazonaws.com", port); + delete[] server; + return response; +} diff --git a/src/common/AWSClient4.h b/src/common/AWSClient4.h new file mode 100644 index 0000000..37c8299 --- /dev/null +++ b/src/common/AWSClient4.h @@ -0,0 +1,110 @@ +/* + * AWSClient2.h + * + * Base classes for services to create an HTTP request. + * + */ + +#ifndef AWSCLIENT4_H_ +#define AWSCLIENT4_H_ + +#include "DeviceIndependentInterfaces.h" +#include "AWSFoundationalTypes.h" + +/* Total number of headers. */ +static const int HEADER_COUNT4 = 7; +/* Size of the awsDate string. */ +static const int AWS_DATE_LEN4 = 8; +/* Size of the awsTime string. */ +static const int AWS_TIME_LEN4 = 6; +/* Size of sha hashes and signatures in hexidecimal. */ +static const int HASH_HEX_LEN4 = 64; + +/* Base class for an AWS Service Client. Creates http and https request in raw + * http format or as a curl command. */ +class AWSClient4 { + /* Name of region, eg. "us-east-1" in "kinesis.us-east-1.amazonaws.com". */ + char* awsRegion; + /* Endpoint, eg. "amazonaws.com" in "kinesis.us-east-1.amazonaws.com". */ + char* awsEndpoint; + /* Domain, optional, eg. "A2MBBEONHC9LUG.iot.us-east-1.amazonaws.com". */ + char* awsDomain; + /* Path, optional eg. "/things/foobar/shadow", eg for iot-data. */ + char* awsPath; + /* The user's AWS Secret Key for accessing the AWS Resource. */ + char* awsSecKey; + /* The user's AWS Access Key ID for accessing the AWS Resource. */ + char* awsKeyID; + /* GMT date in yyyyMMdd format. */ + char awsDate[AWS_DATE_LEN2 + 1]; + /* GMT time in HHmmss format. */ + char awsTime[AWS_TIME_LEN2 + 1]; + /* Number of headers created. */ + int headersCreated; + /* Array of the created http headers. */ + char* headers[HEADER_COUNT2]; + /* Array of string lengths of the headers in the "headers" array. */ + int headerLens[HEADER_COUNT2]; + /* The payload of the httprequest to be created */ + MinimalString payload; + + /* Add the headers that will be signed to the headers array. Called before + * createStringToSign. */ + void initSignedHeaders(); + /* Create the canonical request and the string to sign as described. Return + * value must be deleted by caller. */ + char* createStringToSign(void); + /* Given the string to sign, create the signature (a 64-char cstring). + * Return value must be deleted by caller. */ + char* createSignature(const char* toSign); + /* Add the headers that will not be signed to the headers array. Called + * after createSignature. */ + void initUnsignedHeaders(const char* signature); + /* Contains all of the work to be done before headersToRequest or + * headersToCurlRequest are called. Takes the payload to be sent and the + * GMT date in yyyyMMddHHmmss format. */ + void createRequestInit(MinimalString &reqPayload); + /* Clean up after headersToRequest or headersToCurlRequest are called. */ + void createRequestCleanup(); + /* Using the headers array, create a raw http request. */ + char* headersToRequest(void); + +protected: + /* Used to keep track of time. */ + IDateTimeProvider* dateTimeProvider; + /* Used to send http to the server. */ + IHttpClient* httpClient; + /* true if https is to be used, false if http is to be used. */ + bool httpS; + /* Name of service, eg. "kinesis" in "kinesis.us-east-1.amazonaws.com". */ + const char* awsService; + /* Content type of payload, eg. "application/x-amz-json-1.1". */ + const char* contentType; + // /* Generates the host based on subdomain, service, etc */ + // char* createHostString(void); + /* Creates a raw http request, given the payload and current GMT date in + * yyyyMMddHHmmss format. Should be exposed to user by extending class. + * Returns 0 if client is unititialized. */ + char* createRequest(MinimalString &payload); + /* Sends http data. Returns http response, or null on error. */ + char* sendData(const char* data); + /* Empty constructor. Must also be initialized with init. */ + AWSClient2(); + +public: + /* Setters for values used by createRequest and createCurlRequest. Must + * be set or create[Curl]Request will return null. */ + /* Generates the host based on subdomain, service, etc */ + char* createHostString(void); + void setAWSRegion(const char * awsRegion); + void setAWSEndpoint(const char * awsEndpoint); + void setAWSDomain(const char * awsDomain); + void setAWSPath(const char * awsPath); + void setAWSSecretKey(const char * awsSecKey); + void setAWSKeyID(const char * awsKeyID); + void setHttpClient(IHttpClient* httpClient); + void setDateTimeProvider(IDateTimeProvider* dateTimeProvider); + ~AWSClient2(void); +}; + +#endif /* AWSCLIENT4_H_ */ diff --git a/src/common/AmazonIOTClient.cpp b/src/common/AmazonIOTClient.cpp index 0041348..abd19e2 100644 --- a/src/common/AmazonIOTClient.cpp +++ b/src/common/AmazonIOTClient.cpp @@ -5,17 +5,17 @@ static const char* SERVICE = "iotdata"; static const char* FORM_TYPE = "application/json"; -static const char* PAYLOAD_TEMPLATE = "%s"; +// static const char* PAYLOAD_TEMPLATE = "%s"; // int PAYLOAD_TEMPLATE_LENGTH = 2; int IOT_EXTRACTED_TIMESTAMP_BUFFER_LENGTH = 17; int IOT_FORMATTED_TIMESTAMP_BUFFER_LENGTH = 15; -AmazonIOTClient::AmazonIOTClient() : AWSClient2() { +AmazonIOTClient::AmazonIOTClient() : AWSClient4() { awsService = SERVICE; httpS = true; } -char* AmazonIOTClient::update_shadow(MinimalString url, MinimalString shadow, ActionError& actionError) { +char* AmazonIOTClient::update_shadow(MinimalString shadow, ActionError& actionError) { actionError = NONE_ACTIONERROR; contentType = FORM_TYPE; @@ -25,9 +25,10 @@ char* AmazonIOTClient::update_shadow(MinimalString url, MinimalString shadow, Ac // char* request = createRequest(url, shadow); char* request = createRequest(shadow); + // char* response = sendData(request); return request; - // char* response = sendData(request); + // delete[] request; // // if (response == NULL) { diff --git a/src/common/AmazonIOTClient.h b/src/common/AmazonIOTClient.h index a90941b..292bcac 100644 --- a/src/common/AmazonIOTClient.h +++ b/src/common/AmazonIOTClient.h @@ -1,6 +1,6 @@ #ifndef AMAZONIOTCLIENT_H_ #define AMAZONIOTCLIENT_H_ -#include "AWSClient2.h" +#include "AWSClient4.h" // class Shadow { @@ -12,11 +12,11 @@ -class AmazonIOTClient : public AWSClient2 { +class AmazonIOTClient : public AWSClient4 { public: AmazonIOTClient(); - char* update_shadow(MinimalString url, MinimalString shadow, ActionError& actionError); + char* update_shadow(MinimalString shadow, ActionError& actionError); }; -#endif /* AMAZONSNSCLIENT_H_ */ +#endif /* AMAZONIOTCLIENT_H_ */ diff --git a/src/esp8266/ESP8266AWSImplementations.h b/src/esp8266/ESP8266AWSImplementations.h index 47f3a68..b69be6a 100644 --- a/src/esp8266/ESP8266AWSImplementations.h +++ b/src/esp8266/ESP8266AWSImplementations.h @@ -2,7 +2,6 @@ #define AWSESP2866IMPLEMENTATIONS_H_ #include "DeviceIndependentInterfaces.h" /* application.h is Esp8266's standard library. Define TCPClient. */ -//#include #include /* HttpClient implementation to be used on the Esp8266 Core device. */ diff --git a/src/esp8266/ESP8266AWSImplentations.cpp b/src/esp8266/ESP8266AWSImplentations.cpp index a4e961b..cf8fca7 100644 --- a/src/esp8266/ESP8266AWSImplentations.cpp +++ b/src/esp8266/ESP8266AWSImplentations.cpp @@ -1,9 +1,9 @@ #include /* application.h is Esp8266's standard library. Defines the Arduino String * object, the Arduino delay() procedure, and the Esp8266 TCPClient. */ -//#include #include "Esp8266AWSImplementations.h" #include "DeviceIndependentInterfaces.h" +#include #include int delayTime = 500; @@ -14,8 +14,15 @@ Esp8266HttpClient::Esp8266HttpClient() { char* Esp8266HttpClient::send(const char* request, const char* serverUrl, int port) { + WiFiClientSecure client; + Serial.println(serverUrl); + Serial.println(port); + Serial.println(request); + Serial.println(""); + Serial.println(""); + /* Arduino String to build the response with. */ - String responseBuilder = ""; + String responseBuilder = "foobar?"; if (client.connect(serverUrl, port)) { /* Send the requests */ client.println(request); @@ -30,14 +37,13 @@ char* Esp8266HttpClient::send(const char* request, const char* serverUrl, int po } else { client.stop(); /* Error connecting. */ - return 0; + return "can't setup SSL connection"; } /* Copy responseBuilder into char* */ int len = responseBuilder.length(); char* response = new char[len + 1](); responseBuilder.toCharArray(response, len + 1); return response; - return 0; } bool Esp8266HttpClient::usesCurl() { @@ -51,18 +57,15 @@ Esp8266DateTimeProvider::Esp8266DateTimeProvider() { } const char* Esp8266DateTimeProvider::getDateTime() { - // return "20151221232400"; - return updateCurTime(); + return "20151224120100"; + // return updateCurTime(); } bool Esp8266DateTimeProvider::syncTakesArg(void) { return true; } void Esp8266DateTimeProvider::sync(const char* dateTime) { - /* Use Esp8266's servers to synchronize current time. */ - //Esp8266.syncTime(); - ///dateTime = updateCurTime(); - //strcpy(dateTime,tNow); + // should have no need for an implementation } //////////////////////////////////// @@ -123,7 +126,11 @@ char* updateCurTime(void) { // read the http GET Response String req2 = client2.readString(); - // Serial.print(req2); + Serial.println(""); + Serial.println(""); + Serial.print(req2); + Serial.println(""); + Serial.println(""); // close connection delay(1); @@ -142,6 +149,6 @@ char* updateCurTime(void) { else { Serial.println("did not connect to timeserver\n"); } - timeout_busy=0; //reset timeout - return dateStamp; //Return latest or last good dateStamp + timeout_busy=0; // reset timeout + return dateStamp; // Return latest or last good dateStamp } From 2890f817ac002152288f6de083e7f981c78914ca Mon Sep 17 00:00:00 2001 From: Sander van de Graaf Date: Thu, 24 Dec 2015 15:10:06 +0100 Subject: [PATCH 04/17] Fixed bad headers --- src/common/AWSClient2.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/common/AWSClient2.h b/src/common/AWSClient2.h index c3d0828..37e2634 100644 --- a/src/common/AWSClient2.h +++ b/src/common/AWSClient2.h @@ -12,13 +12,13 @@ #include "AWSFoundationalTypes.h" /* Total number of headers. */ -static const int HEADER_COUNT = 7; +static const int HEADER_COUNT2 = 7; /* Size of the awsDate string. */ -static const int AWS_DATE_LEN = 8; +static const int AWS_DATE_LEN2 = 8; /* Size of the awsTime string. */ -static const int AWS_TIME_LEN = 6; +static const int AWS_TIME_LEN2 = 6; /* Size of sha hashes and signatures in hexidecimal. */ -static const int HASH_HEX_LEN = 64; +static const int HASH_HEX_LEN2 = 64; /* Base class for an AWS Service Client. Creates http and https request in raw * http format or as a curl command. */ @@ -32,15 +32,15 @@ class AWSClient2 { /* The user's AWS Access Key ID for accessing the AWS Resource. */ char* awsKeyID; /* GMT date in yyyyMMdd format. */ - char awsDate[AWS_DATE_LEN + 1]; + char awsDate[AWS_DATE_LEN2 + 1]; /* GMT time in HHmmss format. */ - char awsTime[AWS_TIME_LEN + 1]; + char awsTime[AWS_TIME_LEN2 + 1]; /* Number of headers created. */ int headersCreated; /* Array of the created http headers. */ - char* headers[HEADER_COUNT]; + char* headers[HEADER_COUNT2]; /* Array of string lengths of the headers in the "headers" array. */ - int headerLens[HEADER_COUNT]; + int headerLens[HEADER_COUNT2]; /* The payload of the httprequest to be created */ MinimalString payload; From 6c7d1e179f7efdbb51e9199dd126baedacd9999f Mon Sep 17 00:00:00 2001 From: Sander van de Graaf Date: Thu, 24 Dec 2015 15:10:15 +0100 Subject: [PATCH 05/17] fixed renaming --- src/common/AWSClient4.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/common/AWSClient4.h b/src/common/AWSClient4.h index 37c8299..cdad0bd 100644 --- a/src/common/AWSClient4.h +++ b/src/common/AWSClient4.h @@ -36,15 +36,15 @@ class AWSClient4 { /* The user's AWS Access Key ID for accessing the AWS Resource. */ char* awsKeyID; /* GMT date in yyyyMMdd format. */ - char awsDate[AWS_DATE_LEN2 + 1]; + char awsDate[AWS_DATE_LEN4 + 1]; /* GMT time in HHmmss format. */ - char awsTime[AWS_TIME_LEN2 + 1]; + char awsTime[AWS_TIME_LEN4 + 1]; /* Number of headers created. */ int headersCreated; /* Array of the created http headers. */ - char* headers[HEADER_COUNT2]; + char* headers[HEADER_COUNT4]; /* Array of string lengths of the headers in the "headers" array. */ - int headerLens[HEADER_COUNT2]; + int headerLens[HEADER_COUNT4]; /* The payload of the httprequest to be created */ MinimalString payload; @@ -89,7 +89,7 @@ class AWSClient4 { /* Sends http data. Returns http response, or null on error. */ char* sendData(const char* data); /* Empty constructor. Must also be initialized with init. */ - AWSClient2(); + AWSClient4(); public: /* Setters for values used by createRequest and createCurlRequest. Must @@ -104,7 +104,7 @@ class AWSClient4 { void setAWSKeyID(const char * awsKeyID); void setHttpClient(IHttpClient* httpClient); void setDateTimeProvider(IDateTimeProvider* dateTimeProvider); - ~AWSClient2(void); + ~AWSClient4(void); }; #endif /* AWSCLIENT4_H_ */ From ae3aceaa76daf0f07082e1a373d512fca0128d29 Mon Sep 17 00:00:00 2001 From: Sander van de Graaf Date: Sun, 27 Dec 2015 21:55:54 +0100 Subject: [PATCH 06/17] implementated necessary methods --- src/common/AWSClient4.cpp | 361 +++++++++--------------- src/common/AWSClient4.h | 58 ++-- src/common/AmazonIOTClient.cpp | 21 +- src/esp8266/ESP8266AWSImplentations.cpp | 18 +- 4 files changed, 177 insertions(+), 281 deletions(-) diff --git a/src/common/AWSClient4.cpp b/src/common/AWSClient4.cpp index 570e9ad..c40a2ef 100644 --- a/src/common/AWSClient4.cpp +++ b/src/common/AWSClient4.cpp @@ -2,6 +2,7 @@ * AWSClient.cpp * * See AWSClient.h for description. + * See http://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-header-based-auth.html * */ @@ -13,7 +14,6 @@ #include #include #include -#include // // /* Constants string, formats, and lengths. */ @@ -54,6 +54,8 @@ AWSClient4::AWSClient4() { awsKeyID = 0; httpClient = 0; dateTimeProvider = 0; + method = 0; + uri = 0; } void AWSClient4::setAWSRegion(const char * awsRegion) { @@ -92,235 +94,111 @@ void AWSClient4::setHttpClient(IHttpClient* httpClient) { void AWSClient4::setDateTimeProvider(IDateTimeProvider* dateTimeProvider) { this->dateTimeProvider = dateTimeProvider; } -// -// AWSClient4::~AWSClient4() { -// if (awsRegion != 0) -// delete[] awsRegion; -// if (awsEndpoint != 0) -// delete[] awsEndpoint; -// if (awsSecKey != 0) -// delete[] awsSecKey; -// if (awsKeyID != 0) -// delete[] awsKeyID; -// } -// -// void AWSClient4::initSignedHeaders() { -// /* For each of the formats for unsigned headers, determine the size of the -// * formatted string, allocate that much space in the next available element -// * in the headers array, create the string, and add it's length to the -// * headerLens array. */ -// -// int contentLen = payload.length(); -// int len = CONTENT_LENGTH_HEADER_LEN + digitCount(contentLen); -// headers[headersCreated] = new char[len + 1](); -// sprintf(headers[headersCreated], CONTENT_LENGTH_HEADER, contentLen); -// headerLens[headersCreated++] = len; -// -// len = CONTENT_TYPE_HEADER_LEN + strlen(contentType); -// headers[headersCreated] = new char[len + 1](); -// sprintf(headers[headersCreated], CONTENT_TYPE_HEADER, contentType); -// headerLens[headersCreated++] = len; -// -// char* host = createHostString(); -// len = HOST_HEADER_LEN + strlen(host); -// headers[headersCreated] = new char[len + 1](); -// -// sprintf(headers[headersCreated], HOST_HEADER, host); -// headerLens[headersCreated++] = len; -// -// len = X_AMZ_DATE_HEADER_LEN + AWS_DATE_LEN2 + AWS_TIME_LEN2; -// headers[headersCreated] = new char[len + 1](); -// sprintf(headers[headersCreated], X_AMZ_DATE_HEADER, awsDate, awsTime); -// headerLens[headersCreated++] = len; -// } -// -// char* AWSClient4::createHostString(void) { -// if(awsDomain[0] != '\0') { -// return awsDomain; -// } else { -// char* host = new char[200](); -// sprintf(host, "%s.%s.%s", awsService, awsRegion, awsEndpoint); -// return host; -// } -// } -// -// char* AWSClient4::createStringToSign(void) { -// SHA256* sha256 = new SHA256(); -// char* hashed; -// /* Calculate length of canonicalForm string. */ -// int canonicalFormLen = CANONICAL_FORM_POST_LINE_LEN; -// for (int i = 0; i < headersCreated; i++) { -// /* +1 for newlines */ -// canonicalFormLen += *(headerLens + i) + 1; -// } -// /* +2 for newlines. */ -// canonicalFormLen += SIGNED_HEADERS_LEN + HASH_HEX_LEN2 + 2; -// -// char* canonicalForm = new char[canonicalFormLen + 1](); -// -// /* Write the cannonicalForm string. */ -// int canonicalFormWritten = 0; -// canonicalFormWritten += strlen( -// strcpy(canonicalForm + canonicalFormWritten, -// CANONICAL_FORM_POST_LINE)); -// for (int i = 0; i < headersCreated; i++) { -// canonicalFormWritten += sprintf(canonicalForm + canonicalFormWritten, -// "%s\n", *(headers + i)); -// } -// canonicalFormWritten += sprintf(canonicalForm + canonicalFormWritten, -// "\n%s\n", SIGNED_HEADERS); -// hashed = (*sha256)(payload.getCStr(), payload.length()); -// strcpy(canonicalForm + canonicalFormWritten, hashed); -// delete[] hashed; -// canonicalFormWritten += HASH_HEX_LEN2; -// -// /* Hash the canonicalForm string. */ -// hashed = (*sha256)(canonicalForm, canonicalFormWritten); -// delete sha256; -// -// delete[] canonicalForm; -// -// /* Determine the size to the string to sign. */ -// int toSignLen = TO_SIGN_TEMPLATE_LEN + 2 * AWS_DATE_LEN2 + AWS_TIME_LEN2 -// + strlen(awsRegion) + strlen(awsService) + HASH_HEX_LEN2; -// -// /* Create and return the string to sign. */ -// char* toSign = new char[toSignLen + 1](); -// sprintf(toSign, TO_SIGN_TEMPLATE, awsDate, awsTime, awsDate, awsRegion, -// awsService, hashed); -// delete[] hashed; -// return toSign; -// -// } -// char* AWSClient4::createSignature(const char* toSign) { -// -// /* Allocate memory for the signature */ -// char* signature = new char[HASH_HEX_LEN2 + 1](); -// -// /* Create the signature key */ -// /* + 4 for "AWS4" */ -// int keyLen = strlen(awsSecKey) + 4; -// char* key = new char[keyLen + 1](); -// sprintf(key, "AWS4%s", awsSecKey); -// -// /* repeatedly apply hmac with the appropriate values. See -// * http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html -// * for algorithm. */ -// char* k1 = hmacSha256(key, keyLen, awsDate, strlen(awsDate)); -// delete[] key; -// char* k2 = hmacSha256(k1, SHA256_DEC_HASH_LEN, awsRegion, -// strlen(awsRegion)); -// delete[] k1; -// char* k3 = hmacSha256(k2, SHA256_DEC_HASH_LEN, awsService, -// strlen(awsService)); -// delete[] k2; -// char* k4 = hmacSha256(k3, SHA256_DEC_HASH_LEN, "aws4_request", 12); -// delete[] k3; -// char* k5 = hmacSha256(k4, SHA256_DEC_HASH_LEN, toSign, strlen(toSign)); -// delete[] k4; -// -// /* Convert the chars in hash to hex for signature. */ -// for (int i = 0; i < SHA256_DEC_HASH_LEN; ++i) { -// sprintf(signature + 2 * i, "%02lx", 0xff & (unsigned long) k5[i]); -// } -// delete[] k5; -// return signature; -// } -// -// void AWSClient4::initUnsignedHeaders(const char* signature) { -// int len = AUTHORIZATION_HEADER_LEN + strlen(awsKeyID) + AWS_DATE_LEN2 -// + strlen(awsRegion) + strlen(awsService) + SIGNED_HEADERS_LEN -// + HASH_HEX_LEN2; -// headers[headersCreated] = new char[len + 1](); -// sprintf(headers[headersCreated], AUTHORIZATION_HEADER, awsKeyID, awsDate, -// awsRegion, awsService, SIGNED_HEADERS, signature); -// headerLens[headersCreated++] = len; -// /* -// len = CONNECTION_HEADER_LEN; -// headers[headersCreated] = new char[len + 1](); -// strcpy(headers[headersCreated], CONNECTION_HEADER); -// headerLens[headersCreated++] = len; -// */ -// } -// -// void AWSClient4::createRequestInit(MinimalString &reqPayload) { -// //initialize object-scoped variables -// const char* dateTime = dateTimeProvider->getDateTime(); -// -// // @TODO: not sure yet why sprintf doesn't work here -// strncpy(awsDate, dateTime, 8); -// awsDate[9] = '\0'; -// strncpy(awsTime, dateTime + 8, 6); -// awsTime[7] = '\0'; -// -// Serial.println(awsDate); -// Serial.println(awsTime); -// -// payload = reqPayload; -// headersCreated = 0; -// -// //Create signature and headers -// initSignedHeaders(); -// char* toSign = createStringToSign(); -// char* signature = createSignature(toSign); -// delete[] toSign; -// initUnsignedHeaders(signature); -// delete[] signature; -// } -// -// void AWSClient4::createRequestCleanup() { -// /* Free each header */ -// for (int i = 0; i < headersCreated; i++) { -// delete[] headers[i]; -// } -// } -// -// char* AWSClient4::headersToRequest() { -// /* Determine whether to use https or http postLine values. */ -// int postLineLen = -// httpS ? HTTPS_REQUEST_POST_LINE_LEN : HTTP_REQUEST_POST_LINE_LEN; -// const char* postLine = -// httpS ? HTTPS_REQUEST_POST_LINE : HTTP_REQUEST_POST_LINE; -// -// /* Calculate length of httpRequest string. */ -// char* host = createHostString(); -// int httpRequestLen; -// -// // if a path is set, append it to the post header -// if(awsPath[0] != '\0') { -// httpRequestLen = postLineLen + strlen(host) + strlen(awsPath); -// } else { -// httpRequestLen = postLineLen + strlen(host); -// } -// -// for (int i = 0; i < headersCreated; i++) { -// /* +1 for newline. */ -// httpRequestLen += *(headerLens + i) + 1; -// } -// /* +1 for newline. */ -// httpRequestLen += payload.length() + 1; -// -// /* Create and write to the httpRequest string. */ -// char* httpRequest = new char[httpRequestLen + 1](); -// int httpRequestWritten = 0; -// -// // if a path is set, append it to the post header -// if(awsPath[0] != '\0') { -// httpRequestWritten += sprintf(httpRequest + httpRequestWritten, postLine, host, awsPath); -// } else { -// httpRequestWritten += sprintf(httpRequest + httpRequestWritten, postLine, host); -// } -// for (int i = 0; i < headersCreated; i++) { -// httpRequestWritten += sprintf(httpRequest + httpRequestWritten, "%s\n", -// *(headers + i)); -// } -// httpRequestWritten += sprintf(httpRequest + httpRequestWritten, "\n%s", -// payload.getCStr()); -// -// return httpRequest; -// } +AWSClient4::~AWSClient4() { + if (awsRegion != 0) + delete[] awsRegion; + if (awsEndpoint != 0) + delete[] awsEndpoint; + if (awsSecKey != 0) + delete[] awsSecKey; + if (awsKeyID != 0) + delete[] awsKeyID; +} + +char* AWSClient4::createHost() { + // return "example.com"; + return awsDomain; +} + +char* AWSClient4::createCanonicalHeaders() { + // headers, alphabetically sorted, lowercase, eg: key:value + // content-type:x + // host:host + // x-amz-content-sha256:hash + // x-amz-date:date + char canonical_headers[500] = ""; + sprintf(canonical_headers, "%scontent-type:%s\n", canonical_headers, contentType); + sprintf(canonical_headers, "%shost:%s\n", canonical_headers, createHost()); + sprintf(canonical_headers, "%sx-amz-content-sha256:%s\n", canonical_headers, payloadHash); + sprintf(canonical_headers, "%sx-amz-date:%sT%sZ\n", canonical_headers, awsDate, awsTime); + return canonical_headers; +} + +char* AWSClient4::createRequestHeaders(char* signature) { + char headers[1000] = ""; + sprintf(headers, "%sContent-Type: %s\n", headers, contentType); + sprintf(headers, "%sHost: %s\n", headers, createHost()); + sprintf(headers, "%sx-amz-content-sha256: %s\n", headers, payloadHash); + sprintf(headers, "%sx-amz-date: %sT%sZ\n", headers, awsDate, awsTime); + sprintf(headers, "%sAuthorization: AWS4-HMAC-SHA256 Credential=%s/%s/%s/%s/aws4_request,SignedHeaders=%s,Signature=%s\n", headers, awsKeyID, awsDate, awsRegion, awsService, signedHeaders, signature); + return headers; +} + +char* AWSClient4::createStringToSign(char* canonical_request) { + char string_to_sign[700] = "AWS4-HMAC-SHA256\n"; + sprintf(string_to_sign, "%s%sT%sZ\n", canonical_request, awsDate, awsTime); + sprintf(string_to_sign, "%s%s/%s/%s/aws4_request\n", canonical_request, awsDate, awsRegion, awsService); + + SHA256* sha256 = new SHA256(); + char* hashed = (*sha256)(canonical_request, strlen(canonical_request)); + delete sha256; + + sprintf(string_to_sign, "%s%s", canonical_request, hashed); + + return string_to_sign; +} + +char* AWSClient4::createCanonicalRequest() { + char canonical_request[700] = ""; + sprintf(canonical_request, "%s%s\n", canonical_request, method); // VERB + sprintf(canonical_request, "%s%s\n", canonical_request, uri); // URI + sprintf(canonical_request, "%s%s\n", canonical_request, queryString); // queryString + char* headers = createCanonicalHeaders(); + + sprintf(canonical_request, "%s%s", canonical_request, headers); // headers + sprintf(canonical_request, "%s%s\n", canonical_request, signedHeaders); // signed_headers + sprintf(canonical_request, "%s%s\n", canonical_request, payload.getCStr()); // payload + + return canonical_request; +} + + +char* AWSClient4::createSignature(const char* toSign) { + + /* Allocate memory for the signature */ + char* signature = new char[HASH_HEX_LEN4 + 1](); + + /* Create the signature key */ + /* + 4 for "AWS4" */ + int keyLen = strlen(awsSecKey) + 4; + char* key = new char[keyLen + 1](); + sprintf(key, "AWS4%s", awsSecKey); + + /* repeatedly apply hmac with the appropriate values. See + * http://docs.aws.amazon.com/general/latest/gr/sigv4-calculate-signature.html + * for algorithm. */ + char* k1 = hmacSha256(key, keyLen, awsDate, strlen(awsDate)); + delete[] key; + char* k2 = hmacSha256(k1, SHA256_DEC_HASH_LEN, awsRegion, + strlen(awsRegion)); + delete[] k1; + char* k3 = hmacSha256(k2, SHA256_DEC_HASH_LEN, awsService, + strlen(awsService)); + delete[] k2; + char* k4 = hmacSha256(k3, SHA256_DEC_HASH_LEN, "aws4_request", 12); + delete[] k3; + char* k5 = hmacSha256(k4, SHA256_DEC_HASH_LEN, toSign, strlen(toSign)); + delete[] k4; + + /* Convert the chars in hash to hex for signature. */ + for (int i = 0; i < SHA256_DEC_HASH_LEN; ++i) { + sprintf(signature + 2 * i, "%02lx", 0xff & (unsigned long) k5[i]); + } + delete[] k5; + return signature; +} char* AWSClient4::createRequest(MinimalString &reqPayload) { @@ -329,10 +207,31 @@ char* AWSClient4::createRequest(MinimalString &reqPayload) { || httpClient == 0 || dateTimeProvider == 0) return 0; - map headers; - headers['host'] = "bar"; + // set date and time + // @TODO: find out why sprintf doesn't work + const char* dateTime = dateTimeProvider->getDateTime(); + strncpy(awsDate, dateTime, 8); + awsDate[9] = '\0'; + strncpy(awsTime, dateTime + 8, 6); + awsTime[7] = '\0'; + + SHA256* sha256 = new SHA256(); + payloadHash = (*sha256)(reqPayload.getCStr(), reqPayload.length()); + delete sha256; + + payload = reqPayload; + + char *canonical_request = createCanonicalRequest(); + char *string_to_sign = createStringToSign(canonical_request); + char *signature = createSignature(string_to_sign); + + char *headers = createRequestHeaders(signature); + + char *host = createHost(); + char* request = new char[strlen(method) + strlen(host) + strlen(awsPath) + strlen(headers) + strlen(reqPayload.getCStr()) + 4](); + sprintf(request, "%s %s%s\n%s\n%s", method, createHost(), awsPath, headers, reqPayload.getCStr()); - return headers['host']; + return request; // createRequestInit(reqPayload); // char* request = headersToRequest(); diff --git a/src/common/AWSClient4.h b/src/common/AWSClient4.h index cdad0bd..184e055 100644 --- a/src/common/AWSClient4.h +++ b/src/common/AWSClient4.h @@ -39,37 +39,40 @@ class AWSClient4 { char awsDate[AWS_DATE_LEN4 + 1]; /* GMT time in HHmmss format. */ char awsTime[AWS_TIME_LEN4 + 1]; - /* Number of headers created. */ - int headersCreated; - /* Array of the created http headers. */ - char* headers[HEADER_COUNT4]; - /* Array of string lengths of the headers in the "headers" array. */ - int headerLens[HEADER_COUNT4]; + /* The payload of the httprequest to be created */ MinimalString payload; - /* Add the headers that will be signed to the headers array. Called before - * createStringToSign. */ - void initSignedHeaders(); - /* Create the canonical request and the string to sign as described. Return - * value must be deleted by caller. */ - char* createStringToSign(void); - /* Given the string to sign, create the signature (a 64-char cstring). - * Return value must be deleted by caller. */ + // /* Add the headers that will be signed to the headers array. Called before + // * createStringToSign. */ + // void initSignedHeaders(); + // /* Create the canonical request and the string to sign as described. Return + // * value must be deleted by caller. */ + char* createStringToSign(char* canonical_request); + // /* Given the string to sign, create the signature (a 64-char cstring). + // * Return value must be deleted by caller. */ char* createSignature(const char* toSign); - /* Add the headers that will not be signed to the headers array. Called - * after createSignature. */ - void initUnsignedHeaders(const char* signature); - /* Contains all of the work to be done before headersToRequest or - * headersToCurlRequest are called. Takes the payload to be sent and the - * GMT date in yyyyMMddHHmmss format. */ - void createRequestInit(MinimalString &reqPayload); - /* Clean up after headersToRequest or headersToCurlRequest are called. */ - void createRequestCleanup(); - /* Using the headers array, create a raw http request. */ - char* headersToRequest(void); + char* createRequestHeaders(char* signature); + // /* Add the headers that will not be signed to the headers array. Called + // * after createSignature. */ + // void initUnsignedHeaders(const char* signature); + // /* Contains all of the work to be done before headersToRequest or + // * headersToCurlRequest are called. Takes the payload to be sent and the + // * GMT date in yyyyMMddHHmmss format. */ + // void createRequestInit(MinimalString &reqPayload); + // /* Clean up after headersToRequest or headersToCurlRequest are called. */ + // void createRequestCleanup(); + // /* Using the headers array, create a raw http request. */ + // char* headersToRequest(void); protected: + char* method; + char* uri; + char* queryString; + char* headers; + char* signedHeaders; + char* payloadHash; + /* Used to keep track of time. */ IDateTimeProvider* dateTimeProvider; /* Used to send http to the server. */ @@ -81,11 +84,14 @@ class AWSClient4 { /* Content type of payload, eg. "application/x-amz-json-1.1". */ const char* contentType; // /* Generates the host based on subdomain, service, etc */ - // char* createHostString(void); + char* createHost(void); /* Creates a raw http request, given the payload and current GMT date in * yyyyMMddHHmmss format. Should be exposed to user by extending class. * Returns 0 if client is unititialized. */ char* createRequest(MinimalString &payload); + char* createCanonicalRequest(); + char* createCanonicalHeaders(); + /* Sends http data. Returns http response, or null on error. */ char* sendData(const char* data); /* Empty constructor. Must also be initialized with init. */ diff --git a/src/common/AmazonIOTClient.cpp b/src/common/AmazonIOTClient.cpp index abd19e2..b6dc529 100644 --- a/src/common/AmazonIOTClient.cpp +++ b/src/common/AmazonIOTClient.cpp @@ -3,27 +3,18 @@ #include #include "Utils.h" -static const char* SERVICE = "iotdata"; -static const char* FORM_TYPE = "application/json"; -// static const char* PAYLOAD_TEMPLATE = "%s"; -// int PAYLOAD_TEMPLATE_LENGTH = 2; -int IOT_EXTRACTED_TIMESTAMP_BUFFER_LENGTH = 17; -int IOT_FORMATTED_TIMESTAMP_BUFFER_LENGTH = 15; - AmazonIOTClient::AmazonIOTClient() : AWSClient4() { - awsService = SERVICE; - httpS = true; + this->awsService = "iotdata"; + this->contentType = "application/json"; + this->signedHeaders = "host;x-amz-content-sha256;x-amz-date"; + this->uri = "/"; + this->queryString = ""; } char* AmazonIOTClient::update_shadow(MinimalString shadow, ActionError& actionError) { - actionError = NONE_ACTIONERROR; - contentType = FORM_TYPE; - - // char* request = createRequest(shadow, url); - // publishOutput.setResponse(request); - // char* request = createRequest(url, shadow); + this->method = "POST"; char* request = createRequest(shadow); // char* response = sendData(request); return request; diff --git a/src/esp8266/ESP8266AWSImplentations.cpp b/src/esp8266/ESP8266AWSImplentations.cpp index cf8fca7..f755b0c 100644 --- a/src/esp8266/ESP8266AWSImplentations.cpp +++ b/src/esp8266/ESP8266AWSImplentations.cpp @@ -14,7 +14,7 @@ Esp8266HttpClient::Esp8266HttpClient() { char* Esp8266HttpClient::send(const char* request, const char* serverUrl, int port) { - WiFiClientSecure client; + // WiFiClientSecure client; Serial.println(serverUrl); Serial.println(port); Serial.println(request); @@ -22,7 +22,7 @@ char* Esp8266HttpClient::send(const char* request, const char* serverUrl, int po Serial.println(""); /* Arduino String to build the response with. */ - String responseBuilder = "foobar?"; + String responseBuilder = "Response: "; if (client.connect(serverUrl, port)) { /* Send the requests */ client.println(request); @@ -57,8 +57,8 @@ Esp8266DateTimeProvider::Esp8266DateTimeProvider() { } const char* Esp8266DateTimeProvider::getDateTime() { - return "20151224120100"; - // return updateCurTime(); + // return "20151224120100"; + return updateCurTime(); } bool Esp8266DateTimeProvider::syncTakesArg(void) { return true; @@ -126,11 +126,11 @@ char* updateCurTime(void) { // read the http GET Response String req2 = client2.readString(); - Serial.println(""); - Serial.println(""); - Serial.print(req2); - Serial.println(""); - Serial.println(""); + // Serial.println(""); + // Serial.println(""); + // Serial.print(req2); + // Serial.println(""); + // Serial.println(""); // close connection delay(1); From 22b21426b684eac1e52200994b7f90d98120028d Mon Sep 17 00:00:00 2001 From: Sander van de Graaf Date: Sun, 27 Dec 2015 22:36:33 +0100 Subject: [PATCH 07/17] made the post request headers valid --- src/common/AWSClient4.cpp | 14 ++++---------- src/common/AmazonIOTClient.cpp | 5 +++-- src/esp8266/ESP8266AWSImplentations.cpp | 7 +++++-- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/common/AWSClient4.cpp b/src/common/AWSClient4.cpp index c40a2ef..3d6c7f2 100644 --- a/src/common/AWSClient4.cpp +++ b/src/common/AWSClient4.cpp @@ -228,22 +228,16 @@ char* AWSClient4::createRequest(MinimalString &reqPayload) { char *headers = createRequestHeaders(signature); char *host = createHost(); - char* request = new char[strlen(method) + strlen(host) + strlen(awsPath) + strlen(headers) + strlen(reqPayload.getCStr()) + 4](); - sprintf(request, "%s %s%s\n%s\n%s", method, createHost(), awsPath, headers, reqPayload.getCStr()); + char* request = new char[strlen(method) + strlen(host) + strlen(awsPath) + strlen(headers) + strlen(reqPayload.getCStr()) + 12](); + sprintf(request, "%s %s HTTP/1.1\n%s\n%s", method, awsPath, headers, reqPayload.getCStr()); return request; - - // createRequestInit(reqPayload); - // char* request = headersToRequest(); - // createRequestCleanup(); - - // return request; } char* AWSClient4::sendData(const char* data) { - char* server = createHostString(); + char* server = createHost(); int port = httpS ? 443 : 80; - char* response = httpClient->send(data, "A2MBBEONHC9LUG.iot.eu-west-1.amazonaws.com", port); + char* response = httpClient->send(data, server, port); delete[] server; return response; } diff --git a/src/common/AmazonIOTClient.cpp b/src/common/AmazonIOTClient.cpp index b6dc529..ba48664 100644 --- a/src/common/AmazonIOTClient.cpp +++ b/src/common/AmazonIOTClient.cpp @@ -9,6 +9,7 @@ AmazonIOTClient::AmazonIOTClient() : AWSClient4() { this->signedHeaders = "host;x-amz-content-sha256;x-amz-date"; this->uri = "/"; this->queryString = ""; + this->httpS = true; } char* AmazonIOTClient::update_shadow(MinimalString shadow, ActionError& actionError) { @@ -16,8 +17,8 @@ char* AmazonIOTClient::update_shadow(MinimalString shadow, ActionError& actionEr this->method = "POST"; char* request = createRequest(shadow); - // char* response = sendData(request); - return request; + char* response = sendData(request); + return response; // delete[] request; diff --git a/src/esp8266/ESP8266AWSImplentations.cpp b/src/esp8266/ESP8266AWSImplentations.cpp index f755b0c..b0bafe2 100644 --- a/src/esp8266/ESP8266AWSImplentations.cpp +++ b/src/esp8266/ESP8266AWSImplentations.cpp @@ -25,14 +25,17 @@ char* Esp8266HttpClient::send(const char* request, const char* serverUrl, int po String responseBuilder = "Response: "; if (client.connect(serverUrl, port)) { /* Send the requests */ - client.println(request); - client.println(); + client.print(request); + client.print("\n"); /* Read the request into responseBuilder. */ delay(delayTime); + Serial.println("<"); while (client.available()) { char c = client.read(); responseBuilder.concat(c); + Serial.print("."); } + Serial.println(">"); client.stop(); } else { client.stop(); From 5a2a4d3789b5f3cbcff74183045e6c69d28804dc Mon Sep 17 00:00:00 2001 From: Sander van de Graaf Date: Mon, 28 Dec 2015 00:15:43 +0100 Subject: [PATCH 08/17] fixed response issues --- src/esp8266/ESP8266AWSImplementations.h | 4 +- src/esp8266/ESP8266AWSImplentations.cpp | 70 +++++++++++++------------ 2 files changed, 39 insertions(+), 35 deletions(-) diff --git a/src/esp8266/ESP8266AWSImplementations.h b/src/esp8266/ESP8266AWSImplementations.h index b69be6a..75260c2 100644 --- a/src/esp8266/ESP8266AWSImplementations.h +++ b/src/esp8266/ESP8266AWSImplementations.h @@ -6,7 +6,7 @@ /* HttpClient implementation to be used on the Esp8266 Core device. */ class Esp8266HttpClient: public IHttpClient { - WiFiClientSecure client; + WiFiClientSecure sclient; //TCPClient client; public: Esp8266HttpClient(); @@ -19,7 +19,7 @@ class Esp8266HttpClient: public IHttpClient { class Esp8266DateTimeProvider: public IDateTimeProvider { /* The time as a cstring in yyyyMMddHHmmss format. Is written to within and * returned by getDateTime(). */ - WiFiClient client2; + WiFiClient client; //char dateTime[15]; public: char dateTime[15]; diff --git a/src/esp8266/ESP8266AWSImplentations.cpp b/src/esp8266/ESP8266AWSImplentations.cpp index b0bafe2..ec36360 100644 --- a/src/esp8266/ESP8266AWSImplentations.cpp +++ b/src/esp8266/ESP8266AWSImplentations.cpp @@ -14,39 +14,43 @@ Esp8266HttpClient::Esp8266HttpClient() { char* Esp8266HttpClient::send(const char* request, const char* serverUrl, int port) { - // WiFiClientSecure client; + WiFiClientSecure sclient; Serial.println(serverUrl); Serial.println(port); Serial.println(request); Serial.println(""); Serial.println(""); - /* Arduino String to build the response with. */ - String responseBuilder = "Response: "; - if (client.connect(serverUrl, port)) { - /* Send the requests */ - client.print(request); - client.print("\n"); - /* Read the request into responseBuilder. */ - delay(delayTime); - Serial.println("<"); - while (client.available()) { - char c = client.read(); - responseBuilder.concat(c); - Serial.print("."); + // + String response = ""; + if (sclient.connect(serverUrl, port)) { + + // Send the request + sclient.print(request); + + // keep reading the response until it's finished + while(sclient.connected()) { + while(sclient.available()){ + char c = sclient.read(); + response.concat(c); + Serial.print('.'); + } } - Serial.println(">"); - client.stop(); + + // disconnect any open connections + sclient.stop(); + } else { - client.stop(); - /* Error connecting. */ + // connection was unsuccessful + sclient.stop(); return "can't setup SSL connection"; } - /* Copy responseBuilder into char* */ - int len = responseBuilder.length(); - char* response = new char[len + 1](); - responseBuilder.toCharArray(response, len + 1); - return response; + + // convert the string into a char and return + int len = response.length(); + char* response_char = new char[len + 1](); + response.toCharArray(response_char, len + 1); + return response_char; } bool Esp8266HttpClient::usesCurl() { @@ -109,26 +113,26 @@ char* updateCurTime(void) { char utctimeraw[80]; char* dpos; - WiFiClient client2; - if (client2.connect(timeServer, 80)) { + WiFiClient client; + if (client.connect(timeServer, 80)) { //Send Request - client2.println(timeServerGet); - client2.println(); - while((!client2.available())&&(timeout_busy++<5000)){ + client.println(timeServerGet); + client.println(); + while((!client.available())&&(timeout_busy++<5000)){ // Wait until the client sends some data delay(1); } // kill client if timeout if(timeout_busy>=5000) { - client2.flush(); - client2.stop(); + client.flush(); + client.stop(); Serial.println("timeout receiving timeserver data\n"); return dateStamp; } // read the http GET Response - String req2 = client2.readString(); + String req2 = client.readString(); // Serial.println(""); // Serial.println(""); // Serial.print(req2); @@ -137,8 +141,8 @@ char* updateCurTime(void) { // close connection delay(1); - client2.flush(); - client2.stop(); + client.flush(); + client.stop(); ipos = req2.indexOf("Date:"); if(ipos>0) { From 4a2a3c738c3db54429835fc59c924f02837ad314 Mon Sep 17 00:00:00 2001 From: Sander van de Graaf Date: Mon, 28 Dec 2015 00:16:00 +0100 Subject: [PATCH 09/17] made http request rfc-valid --- src/common/AWSClient4.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/common/AWSClient4.cpp b/src/common/AWSClient4.cpp index 3d6c7f2..52e8751 100644 --- a/src/common/AWSClient4.cpp +++ b/src/common/AWSClient4.cpp @@ -127,11 +127,13 @@ char* AWSClient4::createCanonicalHeaders() { char* AWSClient4::createRequestHeaders(char* signature) { char headers[1000] = ""; - sprintf(headers, "%sContent-Type: %s\n", headers, contentType); - sprintf(headers, "%sHost: %s\n", headers, createHost()); - sprintf(headers, "%sx-amz-content-sha256: %s\n", headers, payloadHash); - sprintf(headers, "%sx-amz-date: %sT%sZ\n", headers, awsDate, awsTime); - sprintf(headers, "%sAuthorization: AWS4-HMAC-SHA256 Credential=%s/%s/%s/%s/aws4_request,SignedHeaders=%s,Signature=%s\n", headers, awsKeyID, awsDate, awsRegion, awsService, signedHeaders, signature); + sprintf(headers, "%sContent-Type: %s\r\n", headers, contentType); + sprintf(headers, "%sConnection: close\r\n", headers); + sprintf(headers, "%sContent-Length: %d\r\n", headers, strlen(payload.getCStr())); + sprintf(headers, "%sHost: %s\r\n", headers, createHost()); + sprintf(headers, "%sx-amz-content-sha256: %s\r\n", headers, payloadHash); + sprintf(headers, "%sx-amz-date: %sT%sZ\r\n", headers, awsDate, awsTime); + sprintf(headers, "%sAuthorization: AWS4-HMAC-SHA256 Credential=%s/%s/%s/%s/aws4_request,SignedHeaders=%s,Signature=%s\r\n", headers, awsKeyID, awsDate, awsRegion, awsService, signedHeaders, signature); return headers; } @@ -228,8 +230,8 @@ char* AWSClient4::createRequest(MinimalString &reqPayload) { char *headers = createRequestHeaders(signature); char *host = createHost(); - char* request = new char[strlen(method) + strlen(host) + strlen(awsPath) + strlen(headers) + strlen(reqPayload.getCStr()) + 12](); - sprintf(request, "%s %s HTTP/1.1\n%s\n%s", method, awsPath, headers, reqPayload.getCStr()); + char* request = new char[strlen(method) + strlen(host) + strlen(awsPath) + strlen(headers) + strlen(reqPayload.getCStr()) + 16](); + sprintf(request, "%s %s HTTP/1.1\r\n%s\r\n%s\r\n\r\n", method, awsPath, headers, reqPayload.getCStr()); return request; } From add86a9b62287d66a04081c9b2299afe17b8ae31 Mon Sep 17 00:00:00 2001 From: Sander van de Graaf Date: Mon, 28 Dec 2015 00:26:22 +0100 Subject: [PATCH 10/17] added readme for esp8266 --- README.md | 70 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 63 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index fc55cb5..083ffe4 100644 --- a/README.md +++ b/README.md @@ -23,13 +23,13 @@ Happy experimenting! Trying the samples is a good way to get started with using the SDK. -Getting the samples working has the following steps: setting up the DynamoDB table, importing the SDK and copying over the sample, creating the `keys.h` and `keys.cpp` files, and setting up the hardware. These steps are outlined for the samples for both the Spark core and Intel Galileo, so be sure you are following only the directions corresponding to the device and sample you are using. +Getting the samples working has the following steps: setting up the DynamoDB table, importing the SDK and copying over the sample, creating the `keys.h` and `keys.cpp` files, and setting up the hardware. These steps are outlined for the samples for both the Spark core and Intel Galileo, so be sure you are following only the directions corresponding to the device and sample you are using. If you are using a device other than Spark or Galileo, you may want to read through these steps anyway before implementing the interfaces in `DeviceIndependentInterfaces.cpp`/`.h` for your device. ### Step 1: Setting up the DynamoDB Table -For either device you will need to set up a DynamoDB table with the same name, hash key, and range key as in the sample you are using. These values are defined as constants in the sample, i.e. `HASH_KEY_NAME` and `TABLE_NAME`. +For either device you will need to set up a DynamoDB table with the same name, hash key, and range key as in the sample you are using. These values are defined as constants in the sample, i.e. `HASH_KEY_NAME` and `TABLE_NAME`. You can follow the steps below to get the tables set up with the right values, chosing the right set of instructions based on which sample you are using. @@ -65,7 +65,7 @@ This step is different for the Spark Core and Intel Galileo. #### Connected Maraca Sample (Edison/SparkCore/MediaTek) -follow the step by step guide: http://bit.ly/aws-iot-hackseries +follow the step by step guide: http://bit.ly/aws-iot-hackseries #### Intel Galileo/Edison Sample @@ -80,11 +80,11 @@ Create a new sketch with the Arduino IDE and copy and paste the sample code into #### Spark IO Core Sample -This assumes you already have your Spark set up and are able to program it with Spark Build. If you do not, head over to [Spark's website](http://docs.spark.io/). +This assumes you already have your Spark set up and are able to program it with Spark Build. If you do not, head over to [Spark's website](http://docs.spark.io/). Open up the Spark Build web page and create a new app. Name it whatever you would like. -Copy the contents of the sample you are using into the `.ino` file of your new app. +Copy the contents of the sample you are using into the `.ino` file of your new app. Next you need to import the SDK. Because the Spark Build IDE isn't local to your machine, you can't just `cp` the files over. Instead use the "+" tab in the top right corner of the Spark Build page to create a new file for each `.cpp`/`.h` file in the `src/` directory, except `GalileoAWSImplementations` and `AmazonKinesisClient`. Then copy and paste the contents of each file. @@ -99,7 +99,7 @@ You will need to create and add `keys.h` and `keys.cpp` into the `AWSArduinoSDK` #ifndef KEYS_H_ #define KEYS_H_ -extern const char* awsKeyID; // Declare these variables to +extern const char* awsKeyID; // Declare these variables to extern const char* awsSecKey; // be accessible by the sketch #endif @@ -129,7 +129,7 @@ Both spark samples use just one button connected to the D2 pin. #### Intel Galileo -This sample uses five buttons and a RGB LED. +This sample uses five buttons and a RGB LED. The RGB LED has the red leg connected to pin 6, the green leg connected to pin 9, and the blue leg connected to pin 10. @@ -145,4 +145,60 @@ For Galileo/Edison, after the wiring is finished, you should be able to connect For Spark, after the wiring is finished, you should be able to connect it to your computer via USB, and *Flash* the code. Be sure to refer to the comments in the samples for help. +#### ESP8266 +You can use these libraries with the (https://github.com/esp8266/arduino)[Arduino ESP8266]:. + +``` +#include +#include +#include +#include "Esp8266AWSImplementations.h" +#include "AWSFoundationalTypes.h" +#include + + +Esp8266HttpClient httpClient; +Esp8266DateTimeProvider dateTimeProvider; + +AmazonIOTClient iotClient; +ActionError actionError; + +void setup() { + Serial.begin(115200); + delay(10); + + // Connect to WAP + Serial.print("Connecting to "); + Serial.println(ssid); + WiFi.begin(ssid, password); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + + iotClient.setAWSRegion("eu-west-1"); + iotClient.setAWSEndpoint("amazonaws.com"); + iotClient.setAWSDomain("foobar.iot.eu-west-1.amazonaws.com"); + iotClient.setAWSPath("/things/example-1/shadow"); + iotClient.setAWSKeyID("ID"); + iotClient.setAWSSecretKey("SECRET"); + iotClient.setHttpClient(&httpClient); + iotClient.setDateTimeProvider(&dateTimeProvider); +} + +void loop(){ + char* shadow = "{\"state\":{\"reported\": {\"foobar\": "bar"}}}"; + + char* result = iotClient.update_shadow(shadow, actionError); + Serial.print(result); + + delay(60000); +} + +``` From c876ef5e2d464da2e7c17c8acee04786a51d686d Mon Sep 17 00:00:00 2001 From: Sander van de Graaf Date: Mon, 28 Dec 2015 00:29:04 +0100 Subject: [PATCH 11/17] cleanup includes --- README.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 083ffe4..6d59f6f 100644 --- a/README.md +++ b/README.md @@ -147,16 +147,12 @@ For Spark, after the wiring is finished, you should be able to connect it to you #### ESP8266 -You can use these libraries with the (https://github.com/esp8266/arduino)[Arduino ESP8266]:. +You can use these libraries with the [Arduino ESP8266](https://github.com/esp8266/arduino):. ``` +#include #include -#include -#include #include "Esp8266AWSImplementations.h" -#include "AWSFoundationalTypes.h" -#include - Esp8266HttpClient httpClient; Esp8266DateTimeProvider dateTimeProvider; From 15a0d092520678cc5b8e77a4b104d5631e7eb7b2 Mon Sep 17 00:00:00 2001 From: Sander van de Graaf Date: Wed, 30 Dec 2015 01:04:37 +0100 Subject: [PATCH 12/17] Added stub s3 client so we can check the example signature calculation --- src/common/AmazonS3Client.cpp | 24 ++++++++++++++++++++++++ src/common/AmazonS3Client.h | 11 +++++++++++ 2 files changed, 35 insertions(+) create mode 100644 src/common/AmazonS3Client.cpp create mode 100644 src/common/AmazonS3Client.h diff --git a/src/common/AmazonS3Client.cpp b/src/common/AmazonS3Client.cpp new file mode 100644 index 0000000..d230f59 --- /dev/null +++ b/src/common/AmazonS3Client.cpp @@ -0,0 +1,24 @@ +#include "AmazonS3Client.h" +#include "AWSFoundationalTypes.h" +#include +#include "Utils.h" + +// stub for verifying if the signature actually works, needs to get some more love :) + +AmazonS3Client::AmazonS3Client() : AWSClient4() { + this->awsService = "s3"; + this->signedHeaders = "host;range;x-amz-content-sha256;x-amz-date"; + this->queryString = ""; + this->httpS = true; +} + +char* AmazonS3Client::get(MinimalString uri, ActionError& actionError) { + actionError = NONE_ACTIONERROR; + + this->method = "GET"; + setAWSPath(uri.getCStr()); + MinimalString foo = ""; + char* request = createRequest(foo); + // char* response = sendData(request); + return request; +} diff --git a/src/common/AmazonS3Client.h b/src/common/AmazonS3Client.h new file mode 100644 index 0000000..216aeff --- /dev/null +++ b/src/common/AmazonS3Client.h @@ -0,0 +1,11 @@ +#ifndef AMAZONS3CLIENT_H_ +#define AMAZONS3CLIENT_H_ +#include "AWSClient4.h" + +class AmazonS3Client : public AWSClient4 { +public: + AmazonS3Client(); + char* get(MinimalString uri, ActionError& actionError); +}; + +#endif /* AMAZONS3CLIENT_H_ */ From 6b7c355fa17358108a9f132cd2f9c54d2b9a2b56 Mon Sep 17 00:00:00 2001 From: Sander van de Graaf Date: Wed, 30 Dec 2015 01:05:39 +0100 Subject: [PATCH 13/17] cleanup --- src/esp8266/ESP8266AWSImplentations.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/esp8266/ESP8266AWSImplentations.cpp b/src/esp8266/ESP8266AWSImplentations.cpp index ec36360..5ef2221 100644 --- a/src/esp8266/ESP8266AWSImplentations.cpp +++ b/src/esp8266/ESP8266AWSImplentations.cpp @@ -59,12 +59,9 @@ bool Esp8266HttpClient::usesCurl() { } Esp8266DateTimeProvider::Esp8266DateTimeProvider() { - /* No need to sync, spark sychronizes time on startup. */ - //strcpy(dateTime, updateCurTime2(client2)); } const char* Esp8266DateTimeProvider::getDateTime() { - // return "20151224120100"; return updateCurTime(); } bool Esp8266DateTimeProvider::syncTakesArg(void) { From 553c9b3f26f929fee8abbfc3c21fb9c8d8f0192f Mon Sep 17 00:00:00 2001 From: Sander van de Graaf Date: Wed, 30 Dec 2015 01:06:03 +0100 Subject: [PATCH 14/17] made aws v4 signature validate (for iotclient) --- src/common/AWSClient4.cpp | 73 +++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 42 deletions(-) diff --git a/src/common/AWSClient4.cpp b/src/common/AWSClient4.cpp index 52e8751..0db830e 100644 --- a/src/common/AWSClient4.cpp +++ b/src/common/AWSClient4.cpp @@ -15,37 +15,6 @@ #include #include -// -// /* Constants string, formats, and lengths. */ -// static const char* CANONICAL_FORM_POST_LINE = "POST\n/\n\n"; -// static const int CANONICAL_FORM_POST_LINE_LEN = 8; -// static const char* HTTPS_REQUEST_POST_LINE = -// "POST https://%s/%s\n"; -// // static const char* HTTPS_REQUEST_POST_LINE = -// // "POST https://%s/%s HTTP/1.1\n"; -// static const int HTTPS_REQUEST_POST_LINE_LEN = 19; -// static const char* HTTP_REQUEST_POST_LINE = "POST http://%s/%s HTTP/1.1\n"; -// static const int HTTP_REQUEST_POST_LINE_LEN = 27; -// static const char* TO_SIGN_TEMPLATE = -// "AWS4-HMAC-SHA256\n%sT%sZ\n%s/%s/%s/aws4_request\n%s"; -// static const int TO_SIGN_TEMPLATE_LEN = 36; -// static const char* CONTENT_LENGTH_HEADER = "content-length:%d"; -// static const int CONTENT_LENGTH_HEADER_LEN = 15; -// static const char* HOST_HEADER = "host:%s"; -// static const int HOST_HEADER_LEN = 7; -// static const char* CONNECTION_HEADER = "Connection:close"; -// static const int CONNECTION_HEADER_LEN = 16; -// static const char* CONTENT_TYPE_HEADER = "content-type:%s"; -// static const int CONTENT_TYPE_HEADER_LEN = 13; -// static const char* X_AMZ_DATE_HEADER = "x-amz-date:%sT%sZ"; -// static const int X_AMZ_DATE_HEADER_LEN = 13; -// static const char* AUTHORIZATION_HEADER = -// "Authorization: AWS4-HMAC-SHA256 Credential=%s/%s/%s/%s/aws4_request, SignedHeaders=%s, Signature=%s"; -// static const int AUTHORIZATION_HEADER_LEN = 87; -// static const char* SIGNED_HEADERS = -// "content-length;content-type;host;x-amz-date"; -// static const int SIGNED_HEADERS_LEN = 43; -// AWSClient4::AWSClient4() { /* Null until set in init method. */ awsRegion = 0; @@ -120,8 +89,9 @@ char* AWSClient4::createCanonicalHeaders() { char canonical_headers[500] = ""; sprintf(canonical_headers, "%scontent-type:%s\n", canonical_headers, contentType); sprintf(canonical_headers, "%shost:%s\n", canonical_headers, createHost()); + // sprintf(canonical_headers, "%srange:bytes=0-9\n", canonical_headers); // s3 sprintf(canonical_headers, "%sx-amz-content-sha256:%s\n", canonical_headers, payloadHash); - sprintf(canonical_headers, "%sx-amz-date:%sT%sZ\n", canonical_headers, awsDate, awsTime); + sprintf(canonical_headers, "%sx-amz-date:%sT%sZ\n\n", canonical_headers, awsDate, awsTime); return canonical_headers; } @@ -138,30 +108,33 @@ char* AWSClient4::createRequestHeaders(char* signature) { } char* AWSClient4::createStringToSign(char* canonical_request) { - char string_to_sign[700] = "AWS4-HMAC-SHA256\n"; - sprintf(string_to_sign, "%s%sT%sZ\n", canonical_request, awsDate, awsTime); - sprintf(string_to_sign, "%s%s/%s/%s/aws4_request\n", canonical_request, awsDate, awsRegion, awsService); - + // return canonical_request; SHA256* sha256 = new SHA256(); char* hashed = (*sha256)(canonical_request, strlen(canonical_request)); delete sha256; + // return canonical_request; + + char string_to_sign[700] = ""; + sprintf(string_to_sign, "%sAWS4-HMAC-SHA256\n", string_to_sign); + sprintf(string_to_sign, "%s%sT%sZ\n", string_to_sign, awsDate, awsTime); + sprintf(string_to_sign, "%s%s/%s/%s/aws4_request\n", string_to_sign, awsDate, awsRegion, awsService); - sprintf(string_to_sign, "%s%s", canonical_request, hashed); + sprintf(string_to_sign, "%s%s", string_to_sign, hashed); return string_to_sign; } char* AWSClient4::createCanonicalRequest() { - char canonical_request[700] = ""; + char canonical_request[800] = ""; sprintf(canonical_request, "%s%s\n", canonical_request, method); // VERB - sprintf(canonical_request, "%s%s\n", canonical_request, uri); // URI + sprintf(canonical_request, "%s%s\n", canonical_request, awsPath); // URI sprintf(canonical_request, "%s%s\n", canonical_request, queryString); // queryString char* headers = createCanonicalHeaders(); sprintf(canonical_request, "%s%s", canonical_request, headers); // headers sprintf(canonical_request, "%s%s\n", canonical_request, signedHeaders); // signed_headers - sprintf(canonical_request, "%s%s\n", canonical_request, payload.getCStr()); // payload + sprintf(canonical_request, "%s%s", canonical_request, payloadHash); // payload return canonical_request; } @@ -223,13 +196,29 @@ char* AWSClient4::createRequest(MinimalString &reqPayload) { payload = reqPayload; - char *canonical_request = createCanonicalRequest(); - char *string_to_sign = createStringToSign(canonical_request); + // create the canonical request, we need to copy the results + // @TODO: figure out why the reference doesn't work + char *canonical_request_return = createCanonicalRequest(); + char canonical_request[1000]; + strcpy(canonical_request, canonical_request_return); + // return canonical_request; + + // create the signing string, we need to copy the results + // @TODO: figure out why the reference doesn't work + char *return_string_to_sign = createStringToSign(canonical_request); + char string_to_sign[500]; + strcpy(string_to_sign, return_string_to_sign); + + // create the signature char *signature = createSignature(string_to_sign); + // create the headers char *headers = createRequestHeaders(signature); + // get the host/domain char *host = createHost(); + + // create the request with all the vars char* request = new char[strlen(method) + strlen(host) + strlen(awsPath) + strlen(headers) + strlen(reqPayload.getCStr()) + 16](); sprintf(request, "%s %s HTTP/1.1\r\n%s\r\n%s\r\n\r\n", method, awsPath, headers, reqPayload.getCStr()); From 3b7d7ad54d686642f42bc301b007303199738ec5 Mon Sep 17 00:00:00 2001 From: Sander van de Graaf Date: Wed, 30 Dec 2015 01:06:18 +0100 Subject: [PATCH 15/17] cleanup, and use correct signing headers --- src/common/AmazonIOTClient.cpp | 33 ++------------------------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/src/common/AmazonIOTClient.cpp b/src/common/AmazonIOTClient.cpp index ba48664..0d85eca 100644 --- a/src/common/AmazonIOTClient.cpp +++ b/src/common/AmazonIOTClient.cpp @@ -6,7 +6,7 @@ AmazonIOTClient::AmazonIOTClient() : AWSClient4() { this->awsService = "iotdata"; this->contentType = "application/json"; - this->signedHeaders = "host;x-amz-content-sha256;x-amz-date"; + this->signedHeaders = "content-type;host;x-amz-content-sha256;x-amz-date"; this->uri = "/"; this->queryString = ""; this->httpS = true; @@ -17,36 +17,7 @@ char* AmazonIOTClient::update_shadow(MinimalString shadow, ActionError& actionEr this->method = "POST"; char* request = createRequest(shadow); + // return request; char* response = sendData(request); return response; - - - // delete[] request; - // - // if (response == NULL) { - // actionError = CONNECTION_ACTIONERROR; - // return 999; - // } - // - // int httpStatusCode = findHttpStatusCode(response); - // - // if (httpStatusCode == 200) { - // return 1; - // } - // - // if (httpStatusCode == 403) { - // char* ts = strstr(response, "earlier than "); - // int pos = ts - response; - // - // char* newts = new char[IOT_EXTRACTED_TIMESTAMP_BUFFER_LENGTH](); - // strncpy(newts, response + pos + 31, IOT_EXTRACTED_TIMESTAMP_BUFFER_LENGTH - 1); - // newts[16] = '\0'; - // - // char* time = new char[IOT_FORMATTED_TIMESTAMP_BUFFER_LENGTH](); - // sprintf(time, "%.8s%.6s", newts, newts + 9); - // dateTimeProvider->sync(time); - // return 0; - // } - // - // return httpStatusCode; } From 10692250294a906e45fee88e79ccef42c985b415 Mon Sep 17 00:00:00 2001 From: Sander van de Graaf Date: Wed, 30 Dec 2015 01:36:44 +0100 Subject: [PATCH 16/17] use the domain directly --- src/common/AWSClient4.cpp | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/common/AWSClient4.cpp b/src/common/AWSClient4.cpp index 0db830e..97e03a8 100644 --- a/src/common/AWSClient4.cpp +++ b/src/common/AWSClient4.cpp @@ -75,10 +75,6 @@ AWSClient4::~AWSClient4() { delete[] awsKeyID; } -char* AWSClient4::createHost() { - // return "example.com"; - return awsDomain; -} char* AWSClient4::createCanonicalHeaders() { // headers, alphabetically sorted, lowercase, eg: key:value @@ -88,7 +84,7 @@ char* AWSClient4::createCanonicalHeaders() { // x-amz-date:date char canonical_headers[500] = ""; sprintf(canonical_headers, "%scontent-type:%s\n", canonical_headers, contentType); - sprintf(canonical_headers, "%shost:%s\n", canonical_headers, createHost()); + sprintf(canonical_headers, "%shost:%s\n", canonical_headers, awsDomain); // sprintf(canonical_headers, "%srange:bytes=0-9\n", canonical_headers); // s3 sprintf(canonical_headers, "%sx-amz-content-sha256:%s\n", canonical_headers, payloadHash); sprintf(canonical_headers, "%sx-amz-date:%sT%sZ\n\n", canonical_headers, awsDate, awsTime); @@ -100,7 +96,7 @@ char* AWSClient4::createRequestHeaders(char* signature) { sprintf(headers, "%sContent-Type: %s\r\n", headers, contentType); sprintf(headers, "%sConnection: close\r\n", headers); sprintf(headers, "%sContent-Length: %d\r\n", headers, strlen(payload.getCStr())); - sprintf(headers, "%sHost: %s\r\n", headers, createHost()); + sprintf(headers, "%sHost: %s\r\n", headers, awsDomain); sprintf(headers, "%sx-amz-content-sha256: %s\r\n", headers, payloadHash); sprintf(headers, "%sx-amz-date: %sT%sZ\r\n", headers, awsDate, awsTime); sprintf(headers, "%sAuthorization: AWS4-HMAC-SHA256 Credential=%s/%s/%s/%s/aws4_request,SignedHeaders=%s,Signature=%s\r\n", headers, awsKeyID, awsDate, awsRegion, awsService, signedHeaders, signature); @@ -216,19 +212,19 @@ char* AWSClient4::createRequest(MinimalString &reqPayload) { char *headers = createRequestHeaders(signature); // get the host/domain - char *host = createHost(); + // char *host = createHost(); // create the request with all the vars - char* request = new char[strlen(method) + strlen(host) + strlen(awsPath) + strlen(headers) + strlen(reqPayload.getCStr()) + 16](); + char* request = new char[strlen(method) + strlen(awsDomain) + strlen(awsPath) + strlen(headers) + strlen(reqPayload.getCStr()) + 16](); sprintf(request, "%s %s HTTP/1.1\r\n%s\r\n%s\r\n\r\n", method, awsPath, headers, reqPayload.getCStr()); return request; } char* AWSClient4::sendData(const char* data) { - char* server = createHost(); + // char* server = createHost(); int port = httpS ? 443 : 80; - char* response = httpClient->send(data, server, port); - delete[] server; + char* response = httpClient->send(data, awsDomain, port); + // delete[] server; return response; } From 30cdd53167fa360e4e9205daf3d0a0717752b1ce Mon Sep 17 00:00:00 2001 From: Sander van de Graaf Date: Wed, 30 Dec 2015 01:36:52 +0100 Subject: [PATCH 17/17] fixed indenting --- src/esp8266/ESP8266AWSImplentations.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/esp8266/ESP8266AWSImplentations.cpp b/src/esp8266/ESP8266AWSImplentations.cpp index 5ef2221..607517b 100644 --- a/src/esp8266/ESP8266AWSImplentations.cpp +++ b/src/esp8266/ESP8266AWSImplentations.cpp @@ -35,10 +35,10 @@ char* Esp8266HttpClient::send(const char* request, const char* serverUrl, int po response.concat(c); Serial.print('.'); } - } - // disconnect any open connections - sclient.stop(); + // disconnect any open connections + sclient.stop(); + } } else { // connection was unsuccessful