Azure 设备配置服务 (DPS) 中的组注册无法通过 REST API 工作:未经授权 401002

Group enrollment in Azure Device Provisoning Service (DPS) not working through REST API: unauthorized 401002

我正在尝试使用 SAS 通过 HTTPS REST API 为我的 ESP32 固件支持 DPS。

我的设备注册ID是:xx-xx-8c4b14149ff4

我从 DPS primary key 中获取组注册,从 registration ID 生成 symmetric key

我从中创建了相关的 SAS 并伪造了请求,但是服务器 returns "Unauthorized" 的错误代码是 401002.

以下是我的要求(为了方便提供了curl版本):

curl -L -i -X PUT \
-H 'Content-Type: application/json' \
-H 'Content-Encoding: utf-8' \
-H 'Authorization: SharedAccessSignature sr=0neXXXXXX22%2Fregistrations%xx-xx-8c4b14149ff4&sig=XXXXXXXXXXXXXXXXXXX%3D&skn=registration&se=1651482003' \
-d '{"registrationId": "xx-xx-8c4b14149ff4"}' \
https://global.azure-devices-provisioning.net/0neXXXXXX22/registrations/xx-xx-8c4b14149ff4/register?api-version=2021-06-01

请注意,我用“xx”替换了秘密信息。

响应正文如下:

{
    "errorCode": 401002,
    "trackingId": "9fecada7-4e51-455e-9392-68522654a64a",
    "message": "Unauthorized",
    "timestampUtc": "2022-05-02T08:04:03.5761437Z"
}

我必须调整什么才能从门户网站使用 HTTPS REST API?

除了信息本身(我已经仔细检查过)之外,我还应该看什么?

参考文献:

您能否提供有关如何生成单个密钥的代码示例?你试过 https://docs.microsoft.com/en-us/azure/iot-dps/how-to-legacy-device-symm-key?tabs=linux%22%20%5Cl%20%22derive-a-device-key#derive-a-device-key 的样品了吗?

这是一个使用 C# 的示例

        private string GenerateDeviceSas(string enrollmentGroupName, string dpsIdScope, string registrationId, string key)
        {
            if(String.IsNullOrEmpty(dpsIdScope) || String.IsNullOrEmpty(registrationId) || String.IsNullOrEmpty(key))
            {
                Console.WriteLine("Error: Missing required values in settings.json");
                return null;
            }

            if (String.IsNullOrEmpty(enrollmentGroupName))
            {
                // Generate Device API SAS key for individual enrollment
                return GenerateSasToken($"{dpsIdScope}/registrations/{registrationId}", key, "registration");
            }
            else
            {

                // Generate derived Device API SAS key for group enrollment
                // See https://docs.microsoft.com/en-us/azure/iot-dps/how-to-legacy-device-symm-key?tabs=linux#create-a-symmetric-key-enrollment-group

                HMACSHA256 hmacsha256 = new HMACSHA256();
                hmacsha256.Key = Convert.FromBase64String(key);
                var sig = hmacsha256.ComputeHash(ASCIIEncoding.ASCII.GetBytes(registrationId));
                var derivedkey = Convert.ToBase64String(sig);

                return GenerateSasToken($"{dpsIdScope}/registrations/{registrationId}", derivedkey, "registration");

            }
        }

        private static string GenerateSasToken(string resourceUri, string key, string policyName, int expiryInSeconds = 3600)
        {
            TimeSpan fromEpochStart = DateTime.UtcNow - new DateTime(1970, 1, 1);
            string expiry = Convert.ToString((int)fromEpochStart.TotalSeconds + expiryInSeconds);

            string stringToSign = WebUtility.UrlEncode(resourceUri) + "\n" + expiry;

            HMACSHA256 hmac = new HMACSHA256(Convert.FromBase64String(key));
            string signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));

            string token = String.Format(CultureInfo.InvariantCulture, "SharedAccessSignature sr={0}&sig={1}&se={2}", WebUtility.UrlEncode(resourceUri), WebUtility.UrlEncode(signature), expiry);

            if (!String.IsNullOrEmpty(policyName))
            {
                token += "&skn=" + policyName;
            }

            return token;
        }

考虑以下内容并注意我如何从 DPS 密钥计算设备密钥,然后从设备密钥生成 SAS 令牌。有些人忽略了这一点。 :)

from base64 import b64encode, b64decode
from hashlib import sha256
from time import time
from urllib.parse import urlencode, quote_plus
from hmac import HMAC
import requests

def generate_sas_token(uri, key, policy_name, expiry=3600):
    ttl = time() + expiry
    sign_key = "%s\n%d" % ((quote_plus(uri)), int(ttl))
    sign_key = sign_key.encode('utf-8')
    signature = b64encode(HMAC(b64decode(key), sign_key, sha256).digest())

    rawtoken = {
        'sr' :  uri,
        'sig': signature,
        'se' : str(int(ttl))
    }
    
    if policy_name:
        rawtoken['skn'] = policy_name

    return 'SharedAccessSignature ' + urlencode(rawtoken)

device_id = "3c71bfa95f7c"
scope_id = "0neYOURSCOPEHERE39"
dpskey = 'CsBYOURSASCODEHEREXWyfUMl/OA=='

deviceKey = b64encode(HMAC(b64decode(dpskey), device_id.encode('utf-8'), sha256).digest())
uri = scope_id + '/registrations/' + device_id
policy= 'registration'
url = "https://global.azure-devices-provisioning.net/" + scope_id + "/registrations/" + device_id + "/register?api-version=2021-06-01"
headers = {'Authorization': generate_sas_token(uri=uri, key=deviceKey, policy_name=policy), 'User-Agent': 'MicroPython', 'content-type': 'application/json', 'Content-Encoding': 'utf-8'}
data = '{"registrationId" : "' + device_id + '"}'

print(generate_sas_token(uri=uri, key=deviceKey, policy_name=policy))

print(requests.request("PUT", url=url, data=data, headers=headers).json())

响应显示“assigning”,表示成功。希望这有帮助。

C:/Python38-64/python.exe h:/test/sastoken.py
SharedAccessSignature sr=0ne00223A39%2Fregistrations%2F3c71bfa95f7c&sig=UH5KKeREMOVED0vcs%3D&se=1651880438&skn=registration
{'operationId': '4.4cb788ae24922c84.6856a3d0-857f-4c79-a90c-360fdf3355f8', 'status': 'assigning'}

其实我的签名代码有一个小错误。我修好了,现在可以用了。

这是用于签名的(工作)代码:

#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);
}

在那里查看: