Как сделать MQTT с TLS для IoT Hub через Ethernet (не WiFi)

Контекст

Я хочу выполнить публикацию D2C в Microsoft Azure IoT Hub с использованием MQTT через Ethernet (а не через WiFi).

Прошивка работает на моей специальной печатной плате на основе ESP32, к которой через SPI подключен модуль Ethernet (также известный как Wiz5500) (выбор микросхемы — GPIO_NUM_5).

Прошивка работает под управлением Arduino.

Центру Интернета вещей требуется, чтобы MQTT использовал определенный порт (он же 8883), а также использовал TLS (фактически mTLS).

Описание кода

Взяв образец с сайта asksensors.com, я собрал весь код так, чтобы он был самодостаточным, чтобы вам было легко им пользоваться (его можно взять в конце поста).

В коде я удалил ссылки на мой настоящий поддомен Центра Интернета вещей (заменив его на XXXX) и идентификатор устройства (заменив на YYYY).

Пароль MQTT сделан постоянным, я, конечно, использую динамическую генерацию ключа SAS, но для демонстрации я упростил константу, срок действия которой истекает к моменту, когда вы это читаете).

Вопросы и проблемы, требующие устранения

Конечно, код не работает (я имею в виду часть TLS), так как я не предоставил ключ.

Поэтому мои вопросы:

  • а) что указать в константе my_key;
  • б) и как его создать?

И вообще, можно ли это сделать, чтобы он работал через Ethernet?

Текущие журналы

Журналы следующие:

ets Jul 29 2019 12:21:46

rst:0x1 (POWERON_RESET),boot:0x17 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0018,len:4
load:0x3fff001c,len:1216
ho 0 tail 12 room 4
load:0x40078000,len:10944
load:0x40080400,len:6388
entry 0x400806b4
*****************************************************
* IoT hub over MQTT port 8883
********** connecting to Ethernet : DHCP ok
********** Attempting MQTT connection…
(SSLClient)(SSL_ERROR)(m_run_until): SSL internals timed out! This could be an internal error, bad data sent from the server, or data being discarded due to a buffer overflow. If you are using Ethernet, did you modify the library properly (see README)?
(SSLClient)(SSL_ERROR)(connected): Not connected because write error is set
(SSLClient)(SSL_ERROR)(m_print_ssl_error): SSL_BR_WRITE_ERROR
(SSLClient)(SSL_ERROR)(m_start_ssl): Failed to initlalize the SSL layer
(SSLClient)(SSL_ERROR)(m_print_br_error): Unknown error code: 0
failed, rc=-2-> try again in 5 seconds

Код для исправления

Код выглядит следующим образом:

/*
* MQTT and Microsoft Azure IoT Hub Platform
* @history UPDATED by SDL (Steven de Luca) on Tuesday November 8th 2022 
* @history derived from:
* MQTT and AskSensors IoT Platform
* Description: Arduino Ethernet publishes data to AskSensors using MQTT
* Author: https://asksensors.com, 2020 г.
* github: https://github.com/asksensors
*/
#include <SPI.h>
#include <PubSubClient.h>
#include <Ethernet.h>
#include "SSLClient.h"






#ifndef _CERTIFICATES_H_
#define _CERTIFICATES_H_

#ifdef __cplusplus
extern "C"
{
#endif

/* This file is auto-generated by the pycert_bearssl tool.  Do not change it manually.
 * Certificates are BearSSL br_x509_trust_anchor format.  Included certs:
 *
 * Index:    0
 * Label:    Baltimore CyberTrust Root
 * Subject:  CN=Baltimore CyberTrust Root,OU=CyberTrust,O=Baltimore,C=IE
 * Domain(s): XXXX.azure-devices.net
 */

#define TAs_NUM 1

static const unsigned char TA_DN0[] = {
    0x30, 0x5a, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13,
    0x02, 0x49, 0x45, 0x31, 0x12, 0x30, 0x10, 0x06, 0x03, 0x55, 0x04, 0x0a,
    0x13, 0x09, 0x42, 0x61, 0x6c, 0x74, 0x69, 0x6d, 0x6f, 0x72, 0x65, 0x31,
    0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x0a, 0x43, 0x79,
    0x62, 0x65, 0x72, 0x54, 0x72, 0x75, 0x73, 0x74, 0x31, 0x22, 0x30, 0x20,
    0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x19, 0x42, 0x61, 0x6c, 0x74, 0x69,
    0x6d, 0x6f, 0x72, 0x65, 0x20, 0x43, 0x79, 0x62, 0x65, 0x72, 0x54, 0x72,
    0x75, 0x73, 0x74, 0x20, 0x52, 0x6f, 0x6f, 0x74,
};

static const unsigned char TA_RSA_N0[] = {
    0xa3, 0x04, 0xbb, 0x22, 0xab, 0x98, 0x3d, 0x57, 0xe8, 0x26, 0x72, 0x9a,
    0xb5, 0x79, 0xd4, 0x29, 0xe2, 0xe1, 0xe8, 0x95, 0x80, 0xb1, 0xb0, 0xe3,
    0x5b, 0x8e, 0x2b, 0x29, 0x9a, 0x64, 0xdf, 0xa1, 0x5d, 0xed, 0xb0, 0x09,
    0x05, 0x6d, 0xdb, 0x28, 0x2e, 0xce, 0x62, 0xa2, 0x62, 0xfe, 0xb4, 0x88,
    0xda, 0x12, 0xeb, 0x38, 0xeb, 0x21, 0x9d, 0xc0, 0x41, 0x2b, 0x01, 0x52,
    0x7b, 0x88, 0x77, 0xd3, 0x1c, 0x8f, 0xc7, 0xba, 0xb9, 0x88, 0xb5, 0x6a,
    0x09, 0xe7, 0x73, 0xe8, 0x11, 0x40, 0xa7, 0xd1, 0xcc, 0xca, 0x62, 0x8d,
    0x2d, 0xe5, 0x8f, 0x0b, 0xa6, 0x50, 0xd2, 0xa8, 0x50, 0xc3, 0x28, 0xea,
    0xf5, 0xab, 0x25, 0x87, 0x8a, 0x9a, 0x96, 0x1c, 0xa9, 0x67, 0xb8, 0x3f,
    0x0c, 0xd5, 0xf7, 0xf9, 0x52, 0x13, 0x2f, 0xc2, 0x1b, 0xd5, 0x70, 0x70,
    0xf0, 0x8f, 0xc0, 0x12, 0xca, 0x06, 0xcb, 0x9a, 0xe1, 0xd9, 0xca, 0x33,
    0x7a, 0x77, 0xd6, 0xf8, 0xec, 0xb9, 0xf1, 0x68, 0x44, 0x42, 0x48, 0x13,
    0xd2, 0xc0, 0xc2, 0xa4, 0xae, 0x5e, 0x60, 0xfe, 0xb6, 0xa6, 0x05, 0xfc,
    0xb4, 0xdd, 0x07, 0x59, 0x02, 0xd4, 0x59, 0x18, 0x98, 0x63, 0xf5, 0xa5,
    0x63, 0xe0, 0x90, 0x0c, 0x7d, 0x5d, 0xb2, 0x06, 0x7a, 0xf3, 0x85, 0xea,
    0xeb, 0xd4, 0x03, 0xae, 0x5e, 0x84, 0x3e, 0x5f, 0xff, 0x15, 0xed, 0x69,
    0xbc, 0xf9, 0x39, 0x36, 0x72, 0x75, 0xcf, 0x77, 0x52, 0x4d, 0xf3, 0xc9,
    0x90, 0x2c, 0xb9, 0x3d, 0xe5, 0xc9, 0x23, 0x53, 0x3f, 0x1f, 0x24, 0x98,
    0x21, 0x5c, 0x07, 0x99, 0x29, 0xbd, 0xc6, 0x3a, 0xec, 0xe7, 0x6e, 0x86,
    0x3a, 0x6b, 0x97, 0x74, 0x63, 0x33, 0xbd, 0x68, 0x18, 0x31, 0xf0, 0x78,
    0x8d, 0x76, 0xbf, 0xfc, 0x9e, 0x8e, 0x5d, 0x2a, 0x86, 0xa7, 0x4d, 0x90,
    0xdc, 0x27, 0x1a, 0x39,
};

static const unsigned char TA_RSA_E0[] = {
    0x01, 0x00, 0x01,
};

static const br_x509_trust_anchor TAs[] = {
    {
        { (unsigned char *)TA_DN0, sizeof TA_DN0 },
        BR_X509_TA_CA,
        {
            BR_KEYTYPE_RSA,
            { .rsa = {
                (unsigned char *)TA_RSA_N0, sizeof TA_RSA_N0,
                (unsigned char *)TA_RSA_E0, sizeof TA_RSA_E0,
            } }
        }
    },
};

#ifdef __cplusplus
} /* extern "C" */
#endif

#endif /* ifndef _CERTIFICATES_H_ */









// Конфигурация хоста MQTT
const char *mqtt_server     = "XXXX.azure-devices.net";
//MQTT v3.1.1 на порту 8883
//MQTT v3.1.1 через WebSocket на порту 443.
unsigned int mqtt_port = 
  8883
  //443 через сокеты
;

const char *deviceId = "YYYY";
const char *username = "XXXX.azure-devices.net/YYYY/?api-version=2020-09-30&dct=azsdk-c%2F1.3.0-beta.2";
const char *password = "SharedAccessSignature sr=XXXX.azure-devices.net%2Fdevices%2FYYYY&sig=x9xm%2FVmtXJlLe2kWZd1LjRyGzUUXVDZlGM3Z%2BvjyPZc%3D&se=3618";

const char* pubTopic = "install/..../.....";  // публикация/имя пользователя/apiKeyIn
const unsigned int writeInterval = 25000;     // интервал записи (в мс)


// == MAC-адрес
byte mac[] = {
  0x90, 0x38, 0x0C, 0x9F, 0x75, 0x84
};

// == Установите статический IP-адрес, который будет использоваться, если DHCP не сможет назначить
IPAddress ip(192, 168, 1, 123);  // TODO: удалить резервный статический IP-адрес в случае сбоя DHCP

EthernetClient ethernetClient;



// Сертификат клиента, может быть в формате PEM или DER
// Формат DER будет массивом необработанных байтов, а формат PEM будет строкой
// Как указано в https://learn.microsoft.com/en-us/azure/iot-hub/iot-hub-mqtt-support: "Вы можете
// создайте этот файл, скопировав информацию о сертификате из certs.c в
// SDK Azure IoT для C. Включите строки -----BEGIN CERTIFICATE----- и
// -----КОНЕЦ СЕРТИФИКАТА-----""
// Итак, из Azure https://github.com/Azure/azure-iot-sdk-c/blob/main/certs/certs.c
const char my_cert[] = 
/* Baltimore CyberTrust Root */
"-----BEGIN CERTIFICATE-----\r\n"
"MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ\r\n"
"RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD\r\n"
"VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX\r\n"
"DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y\r\n"
"ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy\r\n"
"VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr\r\n"
"mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr\r\n"
"IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK\r\n"
"mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu\r\n"
"XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy\r\n"
"dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye\r\n"
"jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1\r\n"
"BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3\r\n"
"DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92\r\n"
"9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx\r\n"
"jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0\r\n"
"Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz\r\n"
"ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS\r\n"
"R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp\r\n"
"-----END CERTIFICATE-----\r\n";

// Закрытый ключ клиента должен быть того же формата, что и сертификат клиента
// Поддерживаются как RSA, так и ECC, ECC показан ниже
const char my_key[] = 
"-----BEGIN EC PRIVATE KEY-----\n"
"-----END EC PRIVATE KEY-----\n";

// Эта строка будет анализировать и сохранять указанную выше информацию, чтобы SSLClient мог использовать ее позже
// Замените `fromPEM` на `fromDER`, если вы используете сертификаты в формате DER.
SSLClientParameters mTLS = SSLClientParameters::fromPEM(my_cert, sizeof(my_cert), my_key, sizeof(my_key));
SSLClient sslClient(ethernetClient, TAs, 2, A7);


PubSubClient client(sslClient);


void setup() {
  Serial.begin(115200);
  Serial.println("*****************************************************");
  Serial.printf ("* ZZZZ IoT hub over MQTT port %d", mqtt_port); Serial.println();
  Serial.print  ("********** connecting to Ethernet : ");
  
  // запускаем соединение Ethernet:
  Ethernet.init(5);

  if (Ethernet.begin(mac) == 0) {
    Serial.println("Failed to configure Ethernet using DHCP");
    // попробуйте настроить с использованием IP-адреса вместо DHCP:
    Ethernet.begin(mac, ip);
    Serial.println("Static IP ok");
  }
  else {
    Serial.println("DHCP ok");
  }
  // даем Ethernet Shield секунду для инициализации:
  delay(1000);


  sslClient.setMutualAuthParams(mTLS);

  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);
}

void loop() {
  if (!client.connected())
    reconnect();
  client.loop();

  Serial.println("********** Publish MQTT data");
  char mqtt_payload[30] = "";
  snprintf(mqtt_payload, 100, "m1=%ld&m2=%ld", random(10, 100), random(10, 100));
  Serial.print("Publish message: ");
  Serial.println(mqtt_payload);
  client.publish(pubTopic, mqtt_payload);
  Serial.println("> MQTT data published");
  Serial.println("********** End ");
  Serial.println("*****************************************************");
  delay(writeInterval);  // задерживать
}

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();
}

void reconnect() {
  // Цикл, пока мы снова не подключимся
  while (!client.connected()) {
    Serial.print("********** Attempting MQTT connection…");
    // Попытка подключения
    if (client.connect(deviceId, username, password)) {
      Serial.println("-> MQTT client connected");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println("-> try again in 5 seconds");
      // Подождите 5 секунд перед повторной попыткой: TODO: DEBUG подождите одну минуту, пока я нашел способ справиться с сертификатом
      delay(60 * 1000);
    }
  }
}

, 👍0

Обсуждение

Я рекомендую вам использовать библиотеку lwIP_w5500. в комплекте с esp8266 Arduino версии 3+, @Juraj