Since I wanted to make my code public anyway, this is a “minimal” example of my HTTPS S3 communication attempts. It has been tested in VScode, platformIO project (copy all content into main.cpp). It works if you add your wifi credentials. However, the request is outdated and my keys are not included - but for support it might help to understand what I am doing and help find the error.
PlatformIO.ini:
[env:firebeetle32]
platform = espressif32
board = firebeetle32
framework = arduino
EDIT: Here a “minimal” example.
- Parameters like date and time are hardcoded but are the same like above.
- Obviously, I cannot provide my keys. For the
sig_calc
I can provide the resulting hash from the first calculation where the AWS_SECRET_KEY
is needed: 43d0ce257da1febfc3e750bb7c6eeab529041a7b69cc420a97039ce7e4e97a87
- You need to be connected to WIFI - add your SSID and PASSWORD
- The error you get is now, that the request is outdated otherwise the calculation results are like exaplained above.
#include <Arduino.h>
#include "mbedtls/md.h"
#include "WiFiClientSecure.h"
#include "WiFi.h"
String generate_hmac_SHA256_hash(const char *hmac_key, const char *payload)
{
/* Instantiating hash context structure */
mbedtls_md_context_t ctx;
/* Defining hash type */
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
/* Byte buffer array for the hash result */
byte hash_result[32];
/* Initialize final hash string to be returned */
String hash_str = "";
/* Initialize the hash calculator structur */
mbedtls_md_init(&ctx);
/* Setup the hash type */
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 1);
/* Proivde the key used for hashing */
mbedtls_md_hmac_starts(&ctx, (const unsigned char *)hmac_key, strlen(hmac_key));
/* Provide the payload to be hashed */
mbedtls_md_hmac_update(&ctx, (const unsigned char *)payload, strlen(payload));
/* Perform the hash process */
mbedtls_md_hmac_finish(&ctx, hash_result);
/* Free memory allocated for has process */
mbedtls_md_free(&ctx);
/* Convert the bytes into a hexadecimal string */
for (int i = 0; i < sizeof(hash_result); i++)
{
/* Buffer for storing hex values */
char hex_buffer[3];
/* Converting the bytes and storing them in hex buffer */
sprintf(hex_buffer, "%02x", (int)hash_result[i]);
/* Concatenating the hex bytes to a string */
hash_str += String(hex_buffer);
}
return hash_str;
}
String generate_SHA256_hash(const char *payload)
{
/* Instantiating hash context structure */
mbedtls_md_context_t ctx;
/* Defining hash type */
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
/* Byte buffer array for the hash result */
byte hash_result[32];
/* Initialize final hash string to be returned */
String hash_str = "";
/* Initialize the hash calculator structur */
mbedtls_md_init(&ctx);
/* Setup the hash type */
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 0);
/* Start the message-digest computation */
mbedtls_md_starts(&ctx);
/* Provide the payload to be hashed */
mbedtls_md_update(&ctx, (const unsigned char *)payload, strlen(payload));
/* Perform the hash process */
mbedtls_md_finish(&ctx, hash_result);
/* Free memory allocated for has process */
mbedtls_md_free(&ctx);
/* Convert the bytes into a hexadecimal string */
for (int i = 0; i < sizeof(hash_result); i++)
{
/* Buffer for storing hex values */
char hex_buffer[3];
/* Converting the bytes and storing them in hex buffer */
sprintf(hex_buffer, "%02x", (int)hash_result[i]);
/* Concatenating the hex bytes to a string */
hash_str += String(hex_buffer);
}
return hash_str;
}
/* Step 1: Create a canonical request */
String canonical_request(String payload, String method, String object, String query, String content_type, String bucket_name, String service, String host, String date, String time)
{
String payload_hash = "";
if (payload == "")
{
payload_hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
}
else
{
payload_hash = generate_SHA256_hash(payload.c_str());
}
return method + "\n" +
"/" + object + "\n" +
query + "\n" +
"content-type:" + content_type + "\n" +
"host:" + bucket_name + "." + service + "." + host + "\n" +
"x-amz-content-sha256:" + payload_hash + "\n" +
"x-amz-date:" + date + "T" + time + "Z\n" +
"\n" +
"content-type;host;x-amz-content-sha256;x-amz-date\n" +
payload_hash;
}
/* Step 2: Create a canonical request hash */
String canoncial_request_hash(String canoncial_request)
{
return generate_SHA256_hash(canoncial_request.c_str());
}
/* Step 3: Create a string to sign */
String string_2_sign(String algorithm, String date, String time, String region, String service, String hashed_canonical_request)
{
return algorithm + "\n" +
date + "T" + time + "Z\n" +
date + "/" + region + "/" + service + "/aws4_request\n" +
hashed_canonical_request;
}
/* Step 4: Calculate the signature */
String signature_calculation(int length, String array[])
{
String signing_key = array[0];
for (int i = 1; i < length; i++)
{
Serial.println(signing_key.c_str());
Serial.println(array[i].c_str());
signing_key = generate_hmac_SHA256_hash(signing_key.c_str(), array[i].c_str());
}
return signing_key;
}
/* Step 5: Add the signature to the request */
String authorization_header(String algorithm, String aws_access_key, String date, String region, String service, String signature)
{
return algorithm + " Credential=" + aws_access_key + "/" + date + "/" + region + "/" + service + "/aws4_request," +
"SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date," + "Signature=" + signature;
}
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html
/* Create a sample request header */
String GET_header(String object, String content_type, String bucket, String service, String date, String time, String authorization_header)
{
return "GET /" + object + " HTTP/1.1\r\n" +
"Content-Type: " + content_type + "\r\n" +
"Host: " + bucket + "." + service + "." + "amazonaws.com\r\n" +
"Authorization: " + authorization_header + "\r\n" +
"x-amz-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\r\n" +
"x-amz-date: " + date + "T" + time + "Z" + "\r\n\r\n";
}
void setup()
{
WiFiClientSecure ota1_client;
String algorithm = "AWS4-HMAC-SHA256";
String region = "eu-central-1";
String method = "GET";
String service = "s3";
String bucket_name = "esp-data-exchange";
String aws_secret_key = "AWS_SECRET_KEY";
String aws_access_key = "AWS_ACCESS_KEY";
String object = "firmware.bin";
String content_type = "application/octet-stream";
String host = "amazonaws.com";
String payload = "";
String query = "";
String date = "20230310";
String time = "134304";
String date_time = "20230310T134304Z";
int length = 6;
Serial.begin(9600);
delay(5000);
WiFi.begin("SSID", "PASSWORD");
String can_req = canonical_request(payload, method, object, query, content_type, bucket_name, service, host, date, time);
String can_req_hash = canoncial_request_hash(can_req);
String str_2_sign = string_2_sign(algorithm, date, time, region, service, can_req_hash);
String hash_array[length] = {"AWS4" + aws_secret_key, date, region, service, "aws4_request", str_2_sign};
String sig_calc = signature_calculation(length, hash_array);
String auth_header = authorization_header(algorithm, aws_access_key, date, region, service, sig_calc);
String http_header = GET_header(object, content_type, bucket_name, service, date, time, auth_header);
String bucket_url = bucket_name + "." + service + "." + region + "." + host;
//ota1_client.setCACert(AWS_CERT_CA);
ota1_client.setInsecure();
ota1_client.connect(bucket_url.c_str(), 443);
Serial.println("Test");
ota1_client.print(http_header);
// Wait for the response
while (ota1_client.connected() && !ota1_client.available())
;
// Read and print the response
while (ota1_client.available())
{
String line = ota1_client.readStringUntil('\r');
Serial.print(line);
}
ota1_client.stop();
}
void loop()
{
// put your main code here, to run repeatedly:
}