amazon-web-servicesaws-iotprovisioning

Why do I not receive a response from $aws/certificates/create/json/accepted or rejected when I publish to topic $aws/certificates/create/json?


I have an esp32 device that has a certificate I generated in AWS to be used as a claim certificate. The esp32 connects to AWS fine and allows me to subscribe to the accepted and rejected topics. When I publish to the $aws/certificates/create/json topic a new certificate is generated in aws with pending activation but the esp32 receives no message back from AWS on either the accepted or rejected topic.

#include <ArduinoJson.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <PubSubClient.h>
#include <SPIFFS.h>  // For saving credentials
#include <Secrets.h>

// WiFi credentials
const char* ssid = "";
const char* password = "";

const char* awsCertTopic = "$aws/certificates/create/json";                                                     // MQTT topic for creating new Certificate
const char* awsCertAccepted = "$aws/certificates/create/json/accepted";                                         // MQTT topic for new Certificate Accepted
const char* awsCertRejected = "$aws/certificates/create/json/rejected";                                         // MQTT topic for new Certificate Rejected
const char* awsFleetTopic = "$aws/provisioning-templates/DrainAlert_FleetTemplate/provision/json";              // MQTT topic for fleet provisioning
const char* awsFleetAccepted = "$aws/provisioning-templates/DrainAlert_FleetTemplate/provision/json/accepted";  // MQTT topic for fleet provisioning Accepted
const char* awsFleetRejected = "$aws/provisioning-templates/DrainAlert_FleetTemplate/provision/json/rejected";  // MQTT topic for fleet provisioning Rejected
const char* awsTestTopic = "ji/tp";

// Time Sync details
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 0;
const int daylightOffset_sec = 3600;

// WiFiClientSecure for secure MQTT connection
WiFiClientSecure wifiClient;
PubSubClient mqttClient(wifiClient);

void setupTime() {
  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
  Serial.println("Waiting for NTP time sync...");
  while (!time(nullptr)) {
    delay(1000);
    Serial.print(".");
  }
  Serial.println("\nTime synchronized");
}

// Function to save the new certificate and private key to SPIFFS
void saveCredentials(const char* cert, const char* privateKey) {
  if (!SPIFFS.begin(true)) {
    Serial.println("Failed to mount file system");
    return;
  }

  // Save certificate
  File certFile = SPIFFS.open("/deviceCert.pem", FILE_WRITE);
  if (certFile) {
    certFile.print(cert);
    certFile.close();
    Serial.println("Saved new certificate");
  } else {
    Serial.println("Failed to open cert file for writing");
  }

  // Save private key
  File keyFile = SPIFFS.open("/privateKey.pem", FILE_WRITE);
  if (keyFile) {
    keyFile.print(privateKey);
    keyFile.close();
    Serial.println("Saved new private key");
  } else {
    Serial.println("Failed to open key file for writing");
  }

  SPIFFS.end();
}

// Callback function to handle MQTT messages
void mqttCallback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived on topic: ");
  Serial.println(topic);
  // Convert payload to a string
  String payloadStr = String((char*)payload).substring(0, length);
  Serial.print("Payload: " + payloadStr);

  // Handle the provisioning response
  if (strcmp(topic, "$aws/certificates/create/json/accepted") == 0) {
    Serial.println("Provisioning successful. Saving new credentials...");

    // Parse JSON to extract certificate and private key
    String newCert = extractCertFromPayload(payloadStr);
    String newPrivateKey = extractPrivateKeyFromPayload(payloadStr);

    // Save the new credentials
    saveCredentials(newCert.c_str(), newPrivateKey.c_str());
  }
}

String extractCertFromPayload(String payload) {
  StaticJsonDocument<1024> doc;
  deserializeJson(doc, payload);
  return doc["certificatePem"].as<String>();
}

String extractPrivateKeyFromPayload(String payload) {
  StaticJsonDocument<1024> doc;
  deserializeJson(doc, payload);
  return doc["privateKey"].as<String>();
}

void connectToWiFi() {
  Serial.print("Connecting to WiFi...");
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }
  Serial.println("Connected!");
}

void connectToMQTT() {
  wifiClient.setCACert(awsRootCA);
  wifiClient.setCertificate(claimCert);
  wifiClient.setPrivateKey(claimPrivateKey);

  mqttClient.setServer(awsEndpoint, awsPort);
  mqttClient.setCallback(mqttCallback);

  while (!mqttClient.connected()) {
    Serial.print("Connecting to AWS IoT...");
    if (mqttClient.connect("DnAtClient")) {
      Serial.println("Connected!");

      // Subscribe to provisioning response topics
      mqttClient.subscribe(awsCertAccepted);
      if (mqttClient.subscribe(awsCertAccepted)) {
        Serial.println("Successfully subscribed to awsCertificateAccepted topic");
      } else {
        Serial.println("Failed to subscribe to awsCertificateAccepted topic");
      }

      mqttClient.subscribe(awsCertRejected);
      if (mqttClient.subscribe(awsCertRejected)) {
        Serial.println("Successfully subscribed to awsCertificateRejected topic");
      } else {
        Serial.println("Failed to subscribe to awsCertificateRejected topic");
      }

      mqttClient.subscribe(awsFleetAccepted);
      if (mqttClient.subscribe(awsFleetAccepted)) {
        Serial.println("Successfully subscribed to awsFleetAccepted topic");
      } else {
        Serial.println("Failed to subscribe to awsFleetAccepted topic");
      }

      mqttClient.subscribe(awsFleetRejected);
      if (mqttClient.subscribe(awsFleetRejected)) {
        Serial.println("Successfully subscribed to awsFleetRejected topic");
      } else {
        Serial.println("Failed to subscribe to awsFleetRejected topic");
      }

      mqttClient.subscribe(awsTestTopic);
      if (mqttClient.subscribe(awsTestTopic)) {
        Serial.println("Successfully subscribed to awsTestTopic topic");
      } else {
        Serial.println("Failed to subscribe to awsTestTopic topic");
      }

    } else {
      Serial.print("Failed to connect, rc=");
      Serial.print(mqttClient.state());
      Serial.println(" try again in 5 seconds");
      delay(5000);
    }
  }
}

void triggerCertCreation() {
  String payload = "{}";  
  mqttClient.publish(awsCertTopic, payload.c_str());
  Serial.println("New Certificate Request Sent...");
}

void reconnect() {
  while (!mqttClient.connected()) {
    Serial.print("Attempting MQTT connection...");
    if (mqttClient.connect("ESP32Client")) {
      Serial.println("connected");

      mqttClient.subscribe(awsFleetAccepted);
      mqttClient.subscribe(awsFleetRejected);
    } else {
      Serial.print("failed, rc=");
      Serial.print(mqttClient.state());
      Serial.println(" try again in 5 seconds");
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(6, OUTPUT);
  pinMode(9, INPUT);
  connectToWiFi();
  delay(250);
  setupTime();
  delay(250);
  connectToMQTT();
  delay(1000);
  triggerCertCreation();
}

void loop() {
  if (!mqttClient.connected()) {
    reconnect();
  } else {
    digitalWrite(6, HIGH);
  }

  if (digitalRead(9) == LOW){
    Serial.println("Sending message to get Cert topic...  ");
    triggerCertCreation();

  }
  mqttClient.loop();
  delay(250);
}

Policy where My-ID is replaced with my account id without the greater/less than symbols.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "iot:Connect",
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "iot:Publish",
        "iot:Receive"
      ],
      "Resource": [
        "arn:aws:iot:us-east-1:<My-ID>:topic/$aws/certificates/create/json",
        "arn:aws:iot:us-east-1:<My-ID>:topic/$aws/provisioning-templates/DrainAlert_FleetTemplate/provision/json"
      ]
    },
    {
      "Effect": "Allow",
      "Action": "iot:Subscribe",
      "Resource": [
        "arn:aws:iot:us-east-1:<My-ID>:topicfilter/$aws/certificates/create/*",
        "arn:aws:iot:us-east-1:<My-ID>:topicfilter/$aws/provisioning-templates/DrainAlert_FleetTemplate/provision/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": "iot:Receive",
      "Resource": [
        "arn:aws:iot:us-east-1:<My-ID>:topic/$aws/certificates/create/json/accepted",
        "arn:aws:iot:us-east-1:<My-ID>:topic/$aws/certificates/create/json/rejected",
        "arn:aws:iot:us-east-1:<My-ID>:topic/$aws/provisioning-templates/DrainAlert_FleetTemplate/provision/json/accepted",
        "arn:aws:iot:us-east-1:<My-ID>:topic/$aws/provisioning-templates/DrainAlert_FleetTemplate/provision/json/rejected"
      ]
    }
  ]
}

I have tried not subscribing as the documentation says that it isn't necessary, but neither way will work.


Solution

  • I have a similar post on Reddit in which someone provided a link with example code specifically for esp32. I was missing setBufferSize in my callback function and this fixed my problem so now I receive new certificate information from AWS on ESP32. EXAMPLE CODE