本文介紹了開源Paho MQTT embeded-c如何通過更安全的身份認證方式(ID2)連接到阿里云物聯網平臺,并建立安全傳輸通道。
1. 術語
Soft-KM(Key Management)密鑰管理,由阿里提供的軟件安全沙箱,基于軟件加固和虛擬化技術提供對密鑰(IoT ID2)的安全保護。
OSA(Operation System Abstractor Layer)操作系統抽象層,定義內存申請和釋放、日志打印、系統時間、網絡通信等接口,非遵循POSIX標準的OS,需重新適配這些接口。
HAL(Hardware Abstractor Layer)硬件抽象層,根據設備硬件特性,完成對加解密算法、設備唯一ID、密鑰管理和數據安全存儲的接口適配。
ID2 OTP(One-Time Provisioning)一次性燒錄,也稱ID2空發,指設備在第一次聯網時,通過網絡請求,下發ID2數據燒錄到設備中。
TLS(Transport Layer Security)安全傳輸層協議, 用于兩個通信實體之間,保護通信數據的私密性和完整性。
IoT(Internet of things)物聯網,基于互聯網實現萬物互聯。
阿里云物聯網平臺(Link Platform)阿里云物聯網平臺,提供物聯網的設備管理。
?
2. ID2產品架構
ID2控制中心:
ID2的Web控制臺,提供對ID2產線灌裝、ID2產品和配額申請、以及ID2使用統計的管理。
ID2服務中心:
ID2在云端的應用,提供ID2的各種安全能力,包括ID2密鑰安全分發、設備認證、基于ID2的安全連接協議等;同時,提供各種能力的云端接口,支持業務平臺的二次開發,支持不同的安全業務需求和場景。
ID2 Client SDK:
ID2在設備端的功能組件和軟件開發框架,可支持不同操作系統和不同硬件,為IoT設備提供基于ID2的端到端的設備認證、數據加解密等各種安全能力。
?
3. ID2接入流程
3.1 概述:
阿里云物聯網平臺接入的方式,ID2已默認同物聯網平臺打通,因此不需要進行服務端對接。
設備建連的過程,IoT設備通過Paho MQTT Client SDK調用ID2-iTLS,進行設備認證和會話密鑰協商,最后建立數據安全傳輸通道。
設備建連成功后,IoT設備和物聯網平臺,通過安全通道進行應用數據的安全傳輸。
?
ID2對接步驟如下:
3.2 創建產品:
登錄物聯網平臺控制臺。
點擊選擇的實例,在左側導航欄,選擇設備管理 > 產品,單擊創建產品:
填寫產品信息,認證方式選擇ID2:
3.3 購買ID2:
購買ID2認證授權:
點擊購買鏈接 - 購買ID2認證授權。
購買數量和有效期:
分配ID2認證授權:
2.1 登錄到ID2控制臺,進入入門指引 - IoT設備身份認證頁面,選擇“與阿里云物聯網平臺組合使用”,點擊“開始接入”。
2.2 在“配置產品“步驟中,“選擇產品”,然后“分配ID2授權數量”。
3.4 設備端對接:
3.4.1 ID2 SDK框架:
IoT Application:
設備端的應用程序,負責業務數據處理,包括設備認證、設備建連和數據收發等。
Paho MQTT Client SDK:
Eclipse Paho MQTT C/C++ Client開源代碼實現,提供基于MQTT協議的數據管理。
ID2 Client SDK:
iTLS:輕量的安全連接協議TLS,基于ID2完成TLS的握手認證和密鑰協議,提供應用數據的收發。
ID2:IoT設備認證的對外接口,上層應用/協議基于此接口進行開發。
KM:密鑰管理模塊,支持不同形式的載體:
Soft-KM:軟件沙箱,基于軟件加固和虛擬化技術提供對ID2密鑰的安全保護。
SE:安全芯片,基于物理防護機制,提供對ID2密鑰的安全保護,通過AT指令對設備提供ID2的運算。
Crypto:提供統一的加解密算法接口。
OSA:操作系統適配接口,廠商需根據使用的OS,重新進行接口適配。
HAL:硬件適配接口,提供算法庫和Soft-KM的適配接口,廠商需根據選擇的硬件平臺,重新進行接口適配。
3.4.2 ID2 設備端SDK獲取:
ID2 設備端 SDK下載:
可以到ID2安全Agent參考頁面下載ID2設備端的SDK。
ID2 Release Package目錄:
目錄/文件 | 說明 |
app | 測試用例,包括HAL和ID2 |
include | 頭文件 |
libs | ID2模塊的靜態庫 |
makefile | 編譯腳本 |
make.rules | 編譯系統配置文件,可配置編譯工具鏈和編譯參數 |
sample | ID2的示例代碼 |
src | 需適配的OSA和HAL接口和參考實現 |
3.4.3 ID2 設備端SDK對接:
設備端適配:
根據選擇的OS和硬件平臺,完成ID2 SDK的移植。
?
基于ID2 Release Package進行移植,OSA和HAL需要廠商根據接口說明進行重新適配。
第一步:OSA接口適配:
實現src/osa/ls_osa.c中的接口:
已提供Linux系統的參考實現,可只實現其中的基礎接口和網絡接口。
第二步:HAL接口適配:
實現src/hal/km/demo/ls_hal_km.c中的接口:
已提供Linux系統的參考實現:
單獨預留的KM安全分區大于2K, 且需保證在系統升級和重啟時,分區數據不被破壞。
ls_hal_get_id接口,需使用設備硬件唯一標識。
第三步:HAL接口測試:
修改makefile.rules的編譯配置文件:
配置目標平臺(pLat := xxx)。
CROSS_COMPILE設置對應的編譯工具鏈。
CFLAGS設置編譯的配置參數。
執行編譯"make clean & make"。
正確運行程序hal_test,可得到如下成功日志:
"HAL Key Management Test Pass"。
設備端集成:
Paho MQTT調用設備端ID2的接口,完成相應的設備認證、數據加密等安全功能。
下文通過在Linux系統,演示如何集成和使能ID2的認證和數據加密能力
第一步:下載Paho MQTT代碼:
Paho MQTT Embeded-c代碼下載:
第二步:MQTT集成ID2的安全通道:
MQTTClient-C/src目錄,在Linux下創建libs,拷貝已適配好的ID2靜態庫和頭文件。
linux/MQTTLinux.h - 更新Network結構體和接口:
typedef struct Network
{
char *product_key;
char *product_secret;
uintptr_t handle;
int (*mqttread) (struct Network*, unsigned char*, int, int);
int (*mqttwrite) (struct Network*, unsigned char*, int, int);
} Network;
int mqtt_itls_read(Network*, unsigned char*, int, int);
int mqtt_itls_write(Network*, unsigned char*, int, int);
DLLExport void NetworkSetConfig(Network *, char *, char *);
DLLExport void NetworkInit(Network*);
DLLExport int NetworkConnect(Network*, char*, int);
DLLExport void NetworkDisconnect(Network*);
Network結構體:
增加product_key成員:阿里云物聯網平臺(LP)- 產品標識
增加product_secret成員:阿里云物聯網平臺(LP)- 產品密鑰
變更網絡句柄類型:my_socket(int) -> handle(uintptr_t)
Network網絡接口:
增加NetworkSetConfig:用于配置LP產品標識和產品密鑰
linux/MQTTLinux.c - 適配Network網絡接口:
#include "hal_itls.h"
int mqtt_itls_read(Network* n, unsigned char* buffer, int len, int timeout_ms)
{
return hal_itls_read(n->handle, buffer, len, timeout_ms);
}
int mqtt_itls_write(Network* n, unsigned char* buffer, int len, int timeout_ms)
{
return hal_itls_write(n->handle, buffer, len, timeout_ms);
}
void NetworkSetConfig(Network* n, char *product_key, char *product_secret)
{
n->product_key = product_key;
n->product_secret = product_secret;
}
void NetworkInit(Network* n)
{
n->handle = 0;
n->mqttread = mqtt_itls_read;
n->mqttwrite = mqtt_itls_write;
}
int NetworkConnect(Network* n, char* addr, int port)
{
n->handle = hal_itls_establish(addr, port, n->product_key, n->product_secret);
if (n->handle == 0) {
return -1;
}
return 0;
}
void NetworkDisconnect(Network* n)
{
hal_itls_destroy(n->handle);
}
CMakeLists.txt - 鏈接ID2靜態庫:
file(GLOB SOURCES "*.c" "linux/*.c")
add_library(
paho-embed-mqtt3cc SHARED
${SOURCES}
)
install(TARGETS paho-embed-mqtt3cc DESTINATION /usr/lib)
add_library(libitls STATIC IMPORTED)
set_target_properties(libitls PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/linux/libs/libitls.a)
add_library(libid2 STATIC IMPORTED)
set_target_properties(libid2 PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/linux/libs/libid2.a)
add_library(libicrypt STATIC IMPORTED)
set_target_properties(libicrypt PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/linux/libs/libicrypt.a)
add_library(libkm STATIC IMPORTED)
set_target_properties(libkm PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/linux/libs/libkm.a)
add_library(libls_hal STATIC IMPORTED)
set_target_properties(libls_hal PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/linux/libs/libls_hal.a)
add_library(libls_osa STATIC IMPORTED)
set_target_properties(libls_osa PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/linux/libs/libls_osa.a)
target_link_libraries(paho-embed-mqtt3cc libitls libid2 libicrypt libkm libls_hal libls_osa)
target_link_libraries(paho-embed-mqtt3cc rt pthread)
target_include_directories(paho-embed-mqtt3cc PRIVATE "linux" "linux/include/itls")
target_link_libraries(paho-embed-mqtt3cc paho-embed-mqtt3c)
target_compile_definitions(paho-embed-mqtt3cc PRIVATE
MQTTCLIENT_PLATFORM_HEADER=MQTTLinux.h MQTTCLIENT_QOS2=1)
第三步:Paho通過ID2連接LP的示例 - MQTTClient-C/samples:
linux/aiot_id2_demo.c示例代碼:
#include "MQTTClient.h"
#include <stdio.h>
#include <signal.h>
#include <sys/time.h>
#define EXAMPLE_PRODUCT_KEY "xxxxxxx"
#define EXAMPLE_PRODUCT_SECRET "xxxxxxx"
#define EXAMPLE_DEVICE_NAME "Paho_ID2_Device_1"
#define EXAMPLE_DEVICE_SECRET "b21bc6147266242838d06ff50c00d0ab"
#if !defined(CONFIG_LP_INSTANCED)
#define MQTT_CLINETID_KV "|securemode=8,signmethod=hmacsha1,timestamp=2524608000000,authtype=id2|"
#else
#define MQTT_CLINETID_KV "|securemode=8,signmethod=hmacsha1,timestamp=2524608000000,authtype=id2,instanceId=xxxxx|"
#endif
#define PRODUCTKEY_MAXLEN (20)
#define DEVICENAME_MAXLEN (32)
#define DEVICESECRET_MAXLEN (64)
#define USERNAME_MAXLEN (64)
#define PASSWORD_MAXLEN (65)
int aiotMqttSign(const char *productKey, const char *deviceName, const char *deviceSecret,
char clientId[150], char username[64], char password[65])
{
char deviceId[PRODUCTKEY_MAXLEN + DEVICENAME_MAXLEN + 2] = {0};
/* setup deviceId */
memcpy(deviceId, deviceName, strlen(deviceName));
memcpy(deviceId + strlen(deviceId), "&", strlen("&"));
memcpy(deviceId + strlen(deviceId), productKey, strlen(productKey));
/* setup clientid */
memcpy(clientId, deviceId, strlen(deviceId));
memcpy(clientId + strlen(deviceId), MQTT_CLINETID_KV, strlen(MQTT_CLINETID_KV));
memset(clientId + strlen(deviceId) + strlen(MQTT_CLINETID_KV), 0, 1);
/* setup username */
memcpy(username, deviceId, strlen(deviceId));
memset(username + strlen(deviceId), 0, 1);
/* setup password */
memset(password, '0', PASSWORD_MAXLEN - 1);
memcpy(password, "FFFFFFFFFFFFFFFF1234567812345678", 32);
return 0;
}
volatile int toStop = 0;
void cfinish(int sig)
{
signal(SIGINT, NULL);
toStop = 1;
}
void messageArrived(MessageData* md)
{
MQTTMessage* message = md->message;
printf("%.*s\t", md->topicName->lenstring.len, md->topicName->lenstring.data);
printf("%.*s\n", (int)message->payloadlen, (char*)message->payload);
}
/* main function */
int main(int argc, char** argv)
{
int rc = 0;
/* setup the buffer, it must big enough for aliyun IoT platform */
unsigned char buf[1000];
unsigned char readbuf[1000];
Network n;
MQTTClient c;
char *host = EXAMPLE_PRODUCT_KEY".itls.cn-shanghai.aliyuncs.com";
short port = 1883;
const char *subTopic = "/"EXAMPLE_PRODUCT_KEY"/"EXAMPLE_DEVICE_NAME"/user/get";
const char *pubTopic = "/"EXAMPLE_PRODUCT_KEY"/"EXAMPLE_DEVICE_NAME"/user/update";
/* invoke aiotMqttSign to generate mqtt connect parameters */
char clientId[150] = {0};
char username[65] = {0};
char password[65] = {0};
if ((rc = aiotMqttSign(EXAMPLE_PRODUCT_KEY, EXAMPLE_DEVICE_NAME,
EXAMPLE_DEVICE_SECRET, clientId, username, password) < 0)) {
printf("aiotMqttSign -%0x4x\n", -rc);
return -1;
}
printf("clientid: %s\n", clientId);
printf("username: %s\n", username);
printf("password: %s\n", password);
signal(SIGINT, cfinish);
signal(SIGTERM, cfinish);
/* network init and establish network to aliyun IoT platform */
NetworkInit(&n);
NetworkSetConfig(&n, EXAMPLE_PRODUCT_KEY, EXAMPLE_PRODUCT_SECRET);
rc = NetworkConnect(&n, host, port);
if (rc < 0) {
printf("NetworkConnect %d\n", rc);
return -1;
}
/* init mqtt client */
MQTTClientInit(&c, &n, 1000, buf, sizeof(buf), readbuf, sizeof(readbuf));
/* set the default message handler */
c.defaultMessageHandler = messageArrived;
/* set mqtt connect parameter */
MQTTPacket_connectData data = MQTTPacket_connectData_initializer;
data.willFlag = 0;
data.MQTTVersion = 3;
data.clientID.cstring = clientId;
data.username.cstring = username;
data.password.cstring = password;
data.keepAliveInterval = 60;
data.cleansession = 1;
printf("Connecting to %s %d\n", host, port);
rc = MQTTConnect(&c, &data);
if (rc < 0) {
printf("MQTTConnect fail %d!\n", rc);
return -1;
} else {
printf("MQTTConnect %d, Connect aliyun IoT Cloud Success!\n", rc);
}
printf("Subscribing to %s\n", subTopic);
rc = MQTTSubscribe(&c, subTopic, 1, messageArrived);
printf("MQTTSubscribe %d\n", rc);
int cnt = 0;
unsigned int msgid = 0;
while (!toStop)
{
MQTTYield(&c, 1000);
if (++cnt % 5 == 0) {
MQTTMessage msg = {
QOS1, 0, 0, 0, "Hello world", strlen("Hello world"),
};
msg.id = ++msgid;
rc = MQTTPublish(&c, pubTopic, &msg);
printf("MQTTPublish %d, msgid %d\n", rc, msgid);
}
}
printf("Stopping\n");
MQTTDisconnect(&c);
NetworkDisconnect(&n);
return 0;
}
更新如下的信息:
EXAMPLE_PRODUCT_KEY:填寫阿里云物聯網平臺(LP)產品標識,在創建時需選擇“ID2認證”
EXAMPLE_PRODUCT_SECRET:填寫阿里云物聯網平臺(LP)產品密鑰,在產品創建控制臺查詢
EXAMPLE_DEVICE_NAME:填寫設備標識,產品維度內唯一
EXAMPLE_DEVICE_SECRET:填寫設備密鑰,選擇“ID2認證”時,可以使用任意字符串
選擇正確的MQTT_CLINETID_KV:ClientId的鍵值信息
LP公共實例:
deviceId+"|securemode=8,signmethod=hmacsha1,timestamp=2524608000000,authtype=id2|"
LP企業實例:
"|securemode=8,signmethod=hmacsha1,timestamp=2524608000000,authtype=id2,instanceId=xxxxx|", 其中instanceId為企業實例ID(登錄物聯網平臺控制臺,在實例概覽頁面查看)
LP平臺的域名和端口號:
host:${YourProductKey}.itls.cn-shanghai.aliyuncs.com,其中${YourProductKey}為申請的產品標識
port:1883
CMakeLists.txt - 編譯腳本:
add_executable(
stdoutsubc
stdoutsub.c
)
target_link_libraries(stdoutsubc paho-embed-mqtt3cc paho-embed-mqtt3c)
target_include_directories(stdoutsubc PRIVATE "../../src" "../../src/linux")
target_compile_definitions(stdoutsubc PRIVATE MQTTCLIENT_PLATFORM_HEADER=MQTTLinux.h)
add_executable(
aiot_id2_demo
aiot_id2_demo.c
)
target_link_libraries(aiot_id2_demo paho-embed-mqtt3cc paho-embed-mqtt3c)
target_include_directories(aiot_id2_demo PRIVATE "../../src" "../../src/linux" "./include/osa")
target_compile_definitions(aiot_id2_demo PRIVATE MQTTCLIENT_PLATFORM_HEADER=MQTTLinux.h)
第四步:固件編譯:
paho.mqtt.embedded-c根目錄,創建build.paho編譯目錄
到build.paho目錄,運行"cmake & make"進行編譯
編譯成功,ID2固件生成在build.paho/MQTTClient-C/samples/linux目錄
第五步:固件運行:
正確運行aiot_id2_demo,可得到如下日志:
3.5 調試驗證:
ID2建連錯誤調試:
ID2-iTLS建連失敗時,首先可以通過查看消息警告(alert message)進行問題排查:
上面的消息警告中,2 - FATAL類型的警告;172 - 消息警告類型,ID2-iTLS常見的錯誤警告類型如下:
錯誤碼 | 錯誤信息 | 評論 |
160 | ID2 generic error | 通用錯誤,檢查Product Secret是否正確 |
161 | ID2 no quota | ID2配額不足,需先購買ID2 |
162 | ID2 is not exist | 服務端識別不了此ID2,可刪除設備上ID2(如KM載體)后,重新空發ID2 |
163 | ID2 authcode is invalid | ID2認證碼錯誤,校驗挑戰字是否有效(ID2服務端申請&只使用一次) |
164 | ID2 has not been activated | ID2已經激活 |
165 | The timestamp used in authcode is expired | 檢查設備端時間是否同步 |
166 | ID2 challenge is invalid | ID2認證碼中,挑戰字非法(挑戰字模式) |
167 | Not support this operation | 不支持此操作 |
168 | ID2 has been suspended | ID2已暫停使用 |
169 | ID2 has been discarded | ID2已廢棄 |
170 | Permission denied, id2 has been blinded to other product key | ID2已綁定到其他產品,不容許在該產品使用;改回綁定的產品,或者刪除設備上的ID2,再重新空發一個新的ID2 |
171 | Product key is invalid | ID2產品非法,如已廢棄 |
172 | Product key is not exist | ID2產品不存在,產品錯誤填入,或者申請產品時,沒有選擇"ID2認證" |
ID2設備上線驗證:
設備端ID2-iTLS建連成功后,在物聯網管理平臺,可看到新創建的設備和設備的上線信息。
登錄物聯網平臺控制臺。
在左側導航欄,選擇設備管理 > 設備,DeviceName是ID2示例中設置的設備證書(ProductKey、DeviceName、DeviceSecret)設備名。
登錄IoT安全中心。
在左側導航欄,選擇物聯網身份 > 設備身份認證,查看通過ID2激活的設備信息。