Skip to content

Commit a6205a7

Browse files
Add ADU capabilities for preview release (#27) (#31)
1 parent 8f8f035 commit a6205a7

27 files changed

+4638
-340
lines changed

.github/workflows/main.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ on:
44
push:
55
branches: [ main ]
66
pull_request:
7-
branches: [ main ]
7+
branches: [ main, feature/* ]
88

99
workflow_dispatch:
1010

@@ -31,3 +31,6 @@ jobs:
3131

3232
- name: Build Azure_IoT_Central_ESP32
3333
run: arduino --verify --board esp32:esp32:esp32 -v --preserve-temp-files $GITHUB_WORKSPACE/examples/Azure_IoT_Central_ESP32/Azure_IoT_Central_ESP32.ino
34+
35+
- name: Build Azure_IoT_Adu_ESP32
36+
run: arduino --verify --board esp32:esp32:esp32 -v --preserve-temp-files $GITHUB_WORKSPACE/examples/Azure_IoT_Adu_ESP32/Azure_IoT_Adu_ESP32.ino

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,18 @@ This library package contains the following samples. Please refer to their docum
1010

1111
- [Azure IoT Central ESPRESSIF ESP32](examples/Azure_IoT_Central_ESP32/readme.md)
1212

13-
- [Azure IoT Hub ESPRESSIF ESP-8266](examples/Azure_IoT_Hub_ESP8266/readme.md)
13+
- [Azure IoT Hub ESPRESSIF ESP8266](examples/Azure_IoT_Hub_ESP8266/readme.md)
1414

15-
- [Azure IoT Hub ESPRESSIF ESP-32](examples/Azure_IoT_Hub_ESP32/readme.md)
15+
- [Azure IoT Hub ESPRESSIF ESP32](examples/Azure_IoT_Hub_ESP32/readme.md)
1616

1717
- [Azure IoT Hub Realtek AmebaD](examples/Azure_IoT_Hub_RealtekAmebaD/readme.md)
1818

19+
- [Azure IoT Device Update ESP32](examples/Azure_IoT_Adu_ESP32/readme.md)
20+
1921
What is the difference between **IoT Hub** and **IoT Central** samples?
2022

21-
1. IoT Hub samples will get devices connected directly to [Azure IoT Hub](https://docs.microsoft.com/en-us/azure/iot-hub/iot-concepts-and-iot-hub)
22-
1. IoT Central samples will leverage DPS ([Device Provisioning Service](https://docs.microsoft.com/en-us/azure/iot-dps/about-iot-dps)) to provision the device and then connect it to [Azure IoT Central](https://docs.microsoft.com/en-us/azure/iot-central/core/overview-iot-central).
23+
1. IoT Hub samples will get devices connected directly to [Azure IoT Hub](https://docs.microsoft.com/azure/iot-hub/iot-concepts-and-iot-hub)
24+
1. IoT Central samples will leverage DPS ([Device Provisioning Service](https://docs.microsoft.com/azure/iot-dps/about-iot-dps)) to provision the device and then connect it to [Azure IoT Central](https://docs.microsoft.com/azure/iot-central/core/overview-iot-central).
2325

2426
Please note that provisioning through DPS is mandatory for IoT Central scenarios, but DPS can also be used for IoT Hub devices as well.
2527

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
#include "AzIoTSasToken.h"
5+
#include "SerialLogger.h"
6+
#include <az_result.h>
7+
#include <mbedtls/base64.h>
8+
#include <mbedtls/md.h>
9+
#include <mbedtls/sha256.h>
10+
#include <stdlib.h>
11+
#include <time.h>
12+
13+
#define INDEFINITE_TIME ((time_t)-1)
14+
15+
#define az_span_is_content_equal(x, AZ_SPAN_EMPTY) \
16+
(az_span_size(x) == az_span_size(AZ_SPAN_EMPTY) && az_span_ptr(x) == az_span_ptr(AZ_SPAN_EMPTY))
17+
18+
static uint32_t getSasTokenExpiration(const char* sasToken)
19+
{
20+
const char SE[] = { '&', 's', 'e', '=' };
21+
uint32_t se_as_unix_time = 0;
22+
23+
int i, j;
24+
for (i = 0, j = 0; sasToken[i] != '\0'; i++)
25+
{
26+
if (sasToken[i] == SE[j])
27+
{
28+
j++;
29+
if (j == sizeof(SE))
30+
{
31+
// i is still at the '=' position. We must advance it by 1.
32+
i++;
33+
break;
34+
}
35+
}
36+
else
37+
{
38+
j = 0;
39+
}
40+
}
41+
42+
if (j != sizeof(SE))
43+
{
44+
Logger.Error("Failed finding `se` field in SAS token");
45+
}
46+
else
47+
{
48+
int k = i;
49+
while (sasToken[k] != '\0' && sasToken[k] != '&')
50+
{
51+
k++;
52+
}
53+
54+
if (az_result_failed(
55+
az_span_atou32(az_span_create((uint8_t*)sasToken + i, k - i), &se_as_unix_time)))
56+
{
57+
Logger.Error("Failed parsing SAS token expiration timestamp");
58+
}
59+
}
60+
61+
return se_as_unix_time;
62+
}
63+
64+
static void mbedtls_hmac_sha256(az_span key, az_span payload, az_span signed_payload)
65+
{
66+
mbedtls_md_context_t ctx;
67+
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
68+
69+
mbedtls_md_init(&ctx);
70+
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 1);
71+
mbedtls_md_hmac_starts(&ctx, (const unsigned char*)az_span_ptr(key), az_span_size(key));
72+
mbedtls_md_hmac_update(&ctx, (const unsigned char*)az_span_ptr(payload), az_span_size(payload));
73+
mbedtls_md_hmac_finish(&ctx, (byte*)az_span_ptr(signed_payload));
74+
mbedtls_md_free(&ctx);
75+
}
76+
77+
static void hmac_sha256_sign_signature(
78+
az_span decoded_key,
79+
az_span signature,
80+
az_span signed_signature,
81+
az_span* out_signed_signature)
82+
{
83+
mbedtls_hmac_sha256(decoded_key, signature, signed_signature);
84+
*out_signed_signature = az_span_slice(signed_signature, 0, 32);
85+
}
86+
87+
static void base64_encode_bytes(
88+
az_span decoded_bytes,
89+
az_span base64_encoded_bytes,
90+
az_span* out_base64_encoded_bytes)
91+
{
92+
size_t len;
93+
if (mbedtls_base64_encode(
94+
az_span_ptr(base64_encoded_bytes),
95+
(size_t)az_span_size(base64_encoded_bytes),
96+
&len,
97+
az_span_ptr(decoded_bytes),
98+
(size_t)az_span_size(decoded_bytes))
99+
!= 0)
100+
{
101+
Logger.Error("mbedtls_base64_encode fail");
102+
}
103+
104+
*out_base64_encoded_bytes = az_span_create(az_span_ptr(base64_encoded_bytes), (int32_t)len);
105+
}
106+
107+
static int decode_base64_bytes(
108+
az_span base64_encoded_bytes,
109+
az_span decoded_bytes,
110+
az_span* out_decoded_bytes)
111+
{
112+
memset(az_span_ptr(decoded_bytes), 0, (size_t)az_span_size(decoded_bytes));
113+
114+
size_t len;
115+
if (mbedtls_base64_decode(
116+
az_span_ptr(decoded_bytes),
117+
(size_t)az_span_size(decoded_bytes),
118+
&len,
119+
az_span_ptr(base64_encoded_bytes),
120+
(size_t)az_span_size(base64_encoded_bytes))
121+
!= 0)
122+
{
123+
Logger.Error("mbedtls_base64_decode fail");
124+
return 1;
125+
}
126+
else
127+
{
128+
*out_decoded_bytes = az_span_create(az_span_ptr(decoded_bytes), (int32_t)len);
129+
return 0;
130+
}
131+
}
132+
133+
static int iot_sample_generate_sas_base64_encoded_signed_signature(
134+
az_span sas_base64_encoded_key,
135+
az_span sas_signature,
136+
az_span sas_base64_encoded_signed_signature,
137+
az_span* out_sas_base64_encoded_signed_signature)
138+
{
139+
// Decode the sas base64 encoded key to use for HMAC signing.
140+
char sas_decoded_key_buffer[32];
141+
az_span sas_decoded_key = AZ_SPAN_FROM_BUFFER(sas_decoded_key_buffer);
142+
143+
if (decode_base64_bytes(sas_base64_encoded_key, sas_decoded_key, &sas_decoded_key) != 0)
144+
{
145+
Logger.Error("Failed generating encoded signed signature");
146+
return 1;
147+
}
148+
149+
// HMAC-SHA256 sign the signature with the decoded key.
150+
char sas_hmac256_signed_signature_buffer[32];
151+
az_span sas_hmac256_signed_signature = AZ_SPAN_FROM_BUFFER(sas_hmac256_signed_signature_buffer);
152+
hmac_sha256_sign_signature(
153+
sas_decoded_key, sas_signature, sas_hmac256_signed_signature, &sas_hmac256_signed_signature);
154+
155+
// Base64 encode the result of the HMAC signing.
156+
base64_encode_bytes(
157+
sas_hmac256_signed_signature,
158+
sas_base64_encoded_signed_signature,
159+
out_sas_base64_encoded_signed_signature);
160+
161+
return 0;
162+
}
163+
164+
int64_t iot_sample_get_epoch_expiration_time_from_minutes(uint32_t minutes)
165+
{
166+
time_t now = time(NULL);
167+
return (int64_t)(now + minutes * 60);
168+
}
169+
170+
az_span generate_sas_token(
171+
az_iot_hub_client* hub_client,
172+
az_span device_key,
173+
az_span sas_signature,
174+
unsigned int expiryTimeInMinutes,
175+
az_span sas_token)
176+
{
177+
az_result rc;
178+
// Create the POSIX expiration time from input minutes.
179+
uint64_t sas_duration = iot_sample_get_epoch_expiration_time_from_minutes(expiryTimeInMinutes);
180+
181+
// Get the signature that will later be signed with the decoded key.
182+
// az_span sas_signature = AZ_SPAN_FROM_BUFFER(signature);
183+
rc = az_iot_hub_client_sas_get_signature(hub_client, sas_duration, sas_signature, &sas_signature);
184+
if (az_result_failed(rc))
185+
{
186+
Logger.Error("Could not get the signature for SAS key: az_result return code " + rc);
187+
return AZ_SPAN_EMPTY;
188+
}
189+
190+
// Generate the encoded, signed signature (b64 encoded, HMAC-SHA256 signing).
191+
char b64enc_hmacsha256_signature[64];
192+
az_span sas_base64_encoded_signed_signature = AZ_SPAN_FROM_BUFFER(b64enc_hmacsha256_signature);
193+
194+
if (iot_sample_generate_sas_base64_encoded_signed_signature(
195+
device_key,
196+
sas_signature,
197+
sas_base64_encoded_signed_signature,
198+
&sas_base64_encoded_signed_signature)
199+
!= 0)
200+
{
201+
Logger.Error("Failed generating SAS token signed signature");
202+
return AZ_SPAN_EMPTY;
203+
}
204+
205+
// Get the resulting MQTT password, passing the base64 encoded, HMAC signed
206+
// bytes.
207+
size_t mqtt_password_length;
208+
rc = az_iot_hub_client_sas_get_password(
209+
hub_client,
210+
sas_duration,
211+
sas_base64_encoded_signed_signature,
212+
AZ_SPAN_EMPTY,
213+
(char*)az_span_ptr(sas_token),
214+
az_span_size(sas_token),
215+
&mqtt_password_length);
216+
217+
if (az_result_failed(rc))
218+
{
219+
Logger.Error("Could not get the password: az_result return code " + rc);
220+
return AZ_SPAN_EMPTY;
221+
}
222+
else
223+
{
224+
return az_span_slice(sas_token, 0, mqtt_password_length);
225+
}
226+
}
227+
228+
AzIoTSasToken::AzIoTSasToken(
229+
az_iot_hub_client* client,
230+
az_span deviceKey,
231+
az_span signatureBuffer,
232+
az_span sasTokenBuffer)
233+
{
234+
this->client = client;
235+
this->deviceKey = deviceKey;
236+
this->signatureBuffer = signatureBuffer;
237+
this->sasTokenBuffer = sasTokenBuffer;
238+
this->expirationUnixTime = 0;
239+
this->sasToken = AZ_SPAN_EMPTY;
240+
}
241+
242+
int AzIoTSasToken::Generate(unsigned int expiryTimeInMinutes)
243+
{
244+
this->sasToken = generate_sas_token(
245+
this->client,
246+
this->deviceKey,
247+
this->signatureBuffer,
248+
expiryTimeInMinutes,
249+
this->sasTokenBuffer);
250+
251+
if (az_span_is_content_equal(this->sasToken, AZ_SPAN_EMPTY))
252+
{
253+
Logger.Error("Failed generating SAS token");
254+
return 1;
255+
}
256+
else
257+
{
258+
this->expirationUnixTime = getSasTokenExpiration((const char*)az_span_ptr(this->sasToken));
259+
260+
if (this->expirationUnixTime == 0)
261+
{
262+
Logger.Error("Failed getting the SAS token expiration time");
263+
this->sasToken = AZ_SPAN_EMPTY;
264+
return 1;
265+
}
266+
else
267+
{
268+
return 0;
269+
}
270+
}
271+
}
272+
273+
bool AzIoTSasToken::IsExpired()
274+
{
275+
time_t now = time(NULL);
276+
277+
if (now == INDEFINITE_TIME)
278+
{
279+
Logger.Error("Failed getting current time");
280+
return true;
281+
}
282+
else
283+
{
284+
return (now >= this->expirationUnixTime);
285+
}
286+
}
287+
288+
az_span AzIoTSasToken::Get() { return this->sasToken; }
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
#ifndef AZIOTSASTOKEN_H
5+
#define AZIOTSASTOKEN_H
6+
7+
#include <Arduino.h>
8+
#include <az_iot_hub_client.h>
9+
#include <az_span.h>
10+
11+
class AzIoTSasToken
12+
{
13+
public:
14+
AzIoTSasToken(
15+
az_iot_hub_client* client,
16+
az_span deviceKey,
17+
az_span signatureBuffer,
18+
az_span sasTokenBuffer);
19+
int Generate(unsigned int expiryTimeInMinutes);
20+
bool IsExpired();
21+
az_span Get();
22+
23+
private:
24+
az_iot_hub_client* client;
25+
az_span deviceKey;
26+
az_span signatureBuffer;
27+
az_span sasTokenBuffer;
28+
az_span sasToken;
29+
uint32_t expirationUnixTime;
30+
};
31+
32+
#endif // AZIOTSASTOKEN_H

0 commit comments

Comments
 (0)