0

I am implementing Azure DPS (device provisioning service) for my ESP32-based firmware.

The bash script I use so far is as follows (where KEY is the primary key of the DPS enrolment group and REG_ID is the registration device Id for the given ESP it runs on):

#!/bin/sh

KEY=KKKKKKKKK
REG_ID=RRRRRRRRRRR

keybytes=$(echo $KEY | base64 --decode | xxd -p -u -c 1000)

echo -n $REG_ID | openssl sha256 -mac HMAC -macopt hexkey:$keybytes -binary | base64

I use the Arduino platform in platformIO.

How to translate the script in C/C++?

[UPDATE] The reason why I can't run openSSL: I need to generate the symmetric key from the actual device MAC address in order to obtain the credential from DPS and then be granted to connect to IoT Hub - I run on an EPS32-based custom PCB. No shell. No OS.

Stéphane de Luca
  • 12,745
  • 9
  • 57
  • 95
  • You'll need to use an appropriate non standard library to do that. – πάντα ῥεῖ Apr 27 '22 at 15:22
  • What's the problem with using the script you already have? – John Bollinger Apr 27 '22 at 15:38
  • 2
    @JohnBollinger: I guess his microcontroller doesn't have a shell capable of running a script like that or an openssl executable for it to spawn as a child process or pipes to pass data between processes. It probably doesn't support multiple processes at all. – Ben Voigt Apr 27 '22 at 16:08
  • I need to generate the symmetric key from the actual device MAC address in order to obtain the credential from DPS and then be granted to connect to IoT Hub - I run on an EPS32-based custom PCB – Stéphane de Luca Apr 27 '22 at 16:09
  • 2
    It looks like you're taking HMACs of something. ESP-IDF comes with support for two TLS libraries: [mbedTLS](https://github.com/espressif/esp-idf/tree/release/v4.4/components/mbedtls) (default) or [wolfSSL](https://github.com/espressif/esp-wolfssl) (optional). Both should include SHA256 support. See the API documentation. I'd avoid the base64 steps, but if required, there's probably an implementation somewhere in the [HTTP client](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/protocols/esp_http_client.html) component. – Tarmo Apr 27 '22 at 18:44
  • 1
    Please put a look at [my answer](https://stackoverflow.com/a/72117398/941531) too, I re-implemented all algorithms from scratch just for fun. Your Bash script tests are passing well. – Arty May 05 '22 at 15:12
  • BTW, your example bash script produces runtime error `base64: invalid input`, although runs tilll the end ignoring this error. It is because `KEY` has invalid base64 sequence, if you write one less `K` letter there then it will work, i.e. it works for `KEY=KKKKKKKK`, in such case this script produces signature `2d5oEcRhy+0wV2iNqOji6N8i93QH8I0KCJA0sg3TVfw=` , would be great if you correct `KEY` value in your example script and also write resulting base64 output signature same as I did in this comment, so that people know for reference what are correct inputs and correct output. – Arty May 06 '22 at 12:05

2 Answers2

1

I manage to do it by using bed library (which is available from both ESP32/Arduino platforms).

Here is my implementation for the Arduino platform:

#include <mbedtls/md.h>         // mbed tls lib used to sign SHA-256

#include <base64.hpp>           // Densaugeo Base64 version 1.2.0 or 1.2.1


/// Returns the SHA-256 signature of [dataToSign] with the key [enrollmentPrimaryKey]
/// params[in]: dataToSign The data to sign (for our purpose, it is the registration ID (or the device ID if it is different)
/// params[in]: enrollmentPrimaryKey The group enrollment primary key.
/// returns The SHA-256 base-64 signature to present to DPS.
/// Note: I use mbed to SHA-256 sign.
String Sha256Sign(String dataToSign, String enrollmentPrimaryKey){
  /// Length of the dataToSign string
  const unsigned dataToSignLength = dataToSign.length();
  /// Buffer to hold the dataToSign as a char[] buffer from String.
  char dataToSignChar[dataToSignLength + 1];
  /// String to c-style string (char[])
  dataToSign.toCharArray(dataToSignChar, dataToSignLength + 1);

  /// The binary decoded key (from the base 64 definition)
  unsigned char decodedPSK[32];

  /// Encrypted binary signature
  unsigned char encryptedSignature[32];

  /// Base 64 encoded signature
  unsigned char encodedSignature[100];
  
  Serial.printf("Sha256Sign(): Registration Id to sign is: (%d bytes) %s\n", dataToSignLength, dataToSignChar);
  Serial.printf("Sha256Sign(): DPS group enrollment primary key is: (%d bytes) %s\n", enrollmentPrimaryKey.length(), enrollmentPrimaryKey.c_str());


  // Need to base64 decode the Preshared key and the length
  const unsigned base64DecodedDeviceLength = decode_base64((unsigned char*)enrollmentPrimaryKey.c_str(), decodedPSK);
  Serial.printf("Sha256Sign(): Decoded primary key is: (%d bytes) ", base64DecodedDeviceLength);

  for(int i= 0; i<base64DecodedDeviceLength; i++) {
    Serial.printf("%02x ", (int)decodedPSK[i]);
  }
  Serial.println();
  
  // Use mbed to sign
  mbedtls_md_type_t mdType = MBEDTLS_MD_SHA256;
  mbedtls_md_context_t hmacKeyContext;    

  mbedtls_md_init(&hmacKeyContext);
  mbedtls_md_setup(&hmacKeyContext, mbedtls_md_info_from_type(mdType), 1);
  mbedtls_md_hmac_starts(&hmacKeyContext, (const unsigned char *) decodedPSK, base64DecodedDeviceLength);
  mbedtls_md_hmac_update(&hmacKeyContext, (const unsigned char *) dataToSignChar, dataToSignLength);
  mbedtls_md_hmac_finish(&hmacKeyContext, encryptedSignature);
  mbedtls_md_free(&hmacKeyContext);
  
  Serial.print("Sha256Sign(): Computed hash is: ");

  for(int i= 0; i<sizeof(encryptedSignature); i++) {
    Serial.printf("%02x ", (int)encryptedSignature[i]);
  }
  Serial.println();
  // base64 decode the HMAC to a char
  encode_base64(encryptedSignature, sizeof(encryptedSignature), encodedSignature);

  Serial.printf("Sha256Sign(): Computed hash as base64: %s\n", encodedSignature);

  // creating the real SAS Token
  return String((char*)encodedSignature);
}
Stéphane de Luca
  • 12,745
  • 9
  • 57
  • 95
1

You have a very interesting question from mathematical/algorithmical point of view. So just for fun decided to implement ALL sub-algorithms of it from scratch, without almost NO dependacy on standard C++ library.

All algorithms of me are based on Wikipedia and described well in its articles SHA-256, HMAC, Base64 (and StackOverflow), Hex.

I made whole my code specifically from scratch and without almost NO dependency on std C++ library. Only two headers used right now <cstdint> for implementing all sized integers u8, u16, u32, i32, u64, i64.

And <string> is used only to implement Heap allocations. Also you can easily implement this heap allocations inside my HeapMem class, or by removing using String = std::string; (and #include <string>) on first lines of my code and using built-in heap-allocated String of Arduino if it has built-in one.

Header <iostream> is used only in few last lines of code snippet, only to output result to console, so that StackOverflow visitors my run program without external dependencies. This console output may be removed of course.

Besides main algorithms I had to implement my own classes Array, Vector, Str, Tuple, HeapMem to re-implement basic concepts of standard C++ library. Also standard library function like MemSet(), MemCpy(), MemCmp(), StrLen(), Move() had to be implemented.

You may notice too that I never used exceptions in code, specifically if you have disabled/non-supporting them. Instead of exceptions I implemented special Result<T> template that resembles Result from Rust language. This template is used to return/check correct and error results from whole stack of functions.

All algorithms (Sha256, Hmac, Base64) are tested by simple test cases with reference vectors taken from internet. Final SignSha256() function that you desired is also tested by several test cases against your reference bash OpenSSL script.

Important!. Don't use this code snippet directly inside production code, because it is not very well tested and might contain some errors. Use it Only for educational purposes or test it thourughly before using.

Code snippet is very large, around 32 KiB, bigger that limit of StackOverflow post size (which is 30 000 symbols), so I'm sharing code snippet through two external services - GodBolt (click Try it online! link), where you can also test it online, and GitHub Gist service for download/view only.

SOURCE CODE HERE

Try it online on GodBolt!

GitHub Gist

Arty
  • 14,883
  • 6
  • 36
  • 69
  • 1
    Hey Arty, thanks a lot for you contribution: and what I love the most, is the word you used: "just for fun" and I totally share this point of view. On my side, this firmware running on ESP32-based custom PCB is for production. Nevertheless, I will thoroughly look at your work in the following weeks. Bravo and thanks a lot! – Stéphane de Luca May 06 '22 at 07:32
  • @StéphanedeLuca Welcome! And thanks for nice words in my address! :) – Arty May 06 '22 at 08:31