以编程方式为 X.509 证书证明创建设备预配服务注册组

本文介绍如何以编程方式创建使用中间或根 CA X.509 证书的注册组。 注册组是使用 Azure IoT 中心 DPS 服务 SDK 和示例应用程序创建的。 注册组可以控制对设备的预配服务的访问,此类设备在其证书链中共享常用签名证书。 若要了解详细信息,请参阅将 X.509 证书与 DPS 配合使用。 若要详细了解如何将基于 X.509 证书的公钥基础结构 (PKI) 与 Azure IoT 中心和设备预配服务配合使用,请参阅 X.509 CA 证书安全概述

先决条件

注意

本文使用 Windows 开发计算机,不过,本文中的步骤在 Windows 和 Linux 计算机上均适用。

创建测试证书

可以将使用 X.509 证书证明的注册组配置为使用根 CA 证书或中间证书。 更常见的情况是使用中间证书配置注册组。 使用中间证书提供了更大的灵活性,因为同一根 CA 证书可以生成或撤销多个中间证书。

在本文中,需要根 CA 证书文件或中间 CA 证书文件,或者两者都是 .pem.cer 格式。 一个文件包含根 CA X.509 证书的公共部分,另一个文件包含中间 CA X.509 证书的公共部分。

如果已有根 CA 文件和/或中间 CA 文件,则可以继续添加和验证根或中间 CA 证书

添加并验证根或中间 CA 证书

通过使用 X.509 证书的注册组预配的设备在使用 DPS 进行身份验证时会显示整个证书链。 为了使 DPS 能够验证证书链,注册组中配置的根证书或中间证书必须是经过验证的证书,或者在向服务进行身份验证时,必须汇总到设备提供的证书链中的经过验证的证书。

本文假设你有根 CA 证书和由根 CA 签名的中间 CA 证书:

  • 如果打算使用根 CA 证书创建注册组,则需要上传并验证根 CA 证书。

  • 如果打算使用中间 CA 证书创建注册组,可以上传并验证根 CA 证书或中间 CA 证书。 (如果证书链中有多个中间 CA 证书,也可以上传并验证根 CA 证书和用于创建注册组的中间证书之间的任何中间证书。)

若要将根 CA 证书或中间 CA 证书添加到设备预配服务并验证该证书,请执行以下操作:

  1. 登录 Azure 门户

  2. 在门户页面的左侧菜单中,选择“所有资源”。

  3. 选择你的设备预配服务。

  4. 在“设置”菜单中,选择“证书”。

  5. 在顶部菜单中,选择“+ 添加:”。

  6. 输入根 CA 证书或中间 CA 证书的名称,并上传 .pem 或 .cer 文件。

  7. 选择“在上传时将证书状态设置为已验证”。

    显示将根 CA 证书添加到 DPS 实例的屏幕截图。

  8. 选择“保存” 。

获取适用于预配服务的连接字符串

对于本文中的示例,需要预配服务的连接字符串。 使用以下步骤检索它。

  1. 登录 Azure 门户

  2. 在门户页面的左侧菜单中,选择“所有资源”。

  3. 选择你的设备预配服务。

  4. 在“设置”菜单中,选择“共享访问策略” 。

  5. 选择要使用的访问策略。

  6. 在“访问策略”面板中,复制并保存主密钥连接字符串。

    显示门户中预配服务连接字符串的位置的屏幕截图。

创建注册组示例

本部分介绍如何创建一个 .NET Core 控制台应用程序,用于将注册组添加到预配服务。

  1. 打开 Windows 命令提示符并导航到要在其中创建应用的文件夹。

  2. 若要创建控制台项目,请运行以下命令:

    dotnet new console --framework net6.0 --use-program-main 
    
  3. 若要添加对 DPS 服务 SDK 的引用,请运行以下命令:

    dotnet add package Microsoft.Azure.Devices.Provisioning.Service 
    

    此步骤将下载、安装 Azure IoT DPS 服务客户端 NuGet 包及其依赖项,并添加对其的引用。 此包包含 .NET 服务 SDK 的二进制文件。

  4. 在编辑器中打开 Program.cs 文件。

  5. 将文件顶部的命名空间语句替换为以下行:

    namespace CreateEnrollmentGroup;
    
  6. 在文件顶部(namespace 语句之上)添加以下 using 语句:

    using System.Security.Cryptography.X509Certificates;
    using System.Threading.Tasks;
    using Microsoft.Azure.Devices.Provisioning.Service;
    
  7. 将以下字段添加到 Program 类,并按指示进行更改。

    private static string ProvisioningConnectionString = "{ProvisioningServiceConnectionString}";
    private static string EnrollmentGroupId = "enrollmentgrouptest";
    private static string X509RootCertPath = @"{Path to a .cer or .pem file for a verified root CA or intermediate CA X.509 certificate}";
    
    • ProvisioningServiceConnectionString 占位符值替换为在上一部分复制的预配服务连接字符串。

    • X509RootCertPath 占位符值替换为 .pem 或 .cer 文件的路径。 此文件表示根 CA X.509 证书的公共部分,该证书之前已通过预配服务上传和验证,或者表示中间证书的公共部分,该证书本身已上传和验证或其签名链中的证书已上传和验证。

    • 可以选择性地更改 EnrollmentGroupId 值。 字符串只能包含小写字符和连字符。

    重要

    在生产代码中,请注意以下安全注意事项:

    • 为预配服务管理员硬编码连接字符串不符合安全最佳做法。 与硬编码相反,连接字符串应采用安全方式进行存储,例如存储在安全配置文件或注册表中。
    • 确保只上传签名证书的公用部分。 不要将包含私钥的 .pfx (PKCS12) 或 .pem 文件上传到预配服务。
  8. 将以下方法添加到 Program 类。 此代码创建一个 EnrollmentGroup 条目,然后调用 ProvisioningServiceClient.CreateOrUpdateEnrollmentGroupAsync 方法,将注册组添加到预配服务。

    public static async Task RunSample()
    {
        Console.WriteLine("Starting sample...");
    
        using (ProvisioningServiceClient provisioningServiceClient =
                ProvisioningServiceClient.CreateFromConnectionString(ProvisioningConnectionString))
        {
            #region Create a new enrollmentGroup config
            Console.WriteLine("\nCreating a new enrollmentGroup...");
            var certificate = new X509Certificate2(X509RootCertPath);
            Attestation attestation = X509Attestation.CreateFromRootCertificates(certificate);
            EnrollmentGroup enrollmentGroup =
                    new EnrollmentGroup(
                            EnrollmentGroupId,
                            attestation)
                    {
                        ProvisioningStatus = ProvisioningStatus.Enabled
                    };
            Console.WriteLine(enrollmentGroup);
            #endregion
    
            #region Create the enrollmentGroup
            Console.WriteLine("\nAdding new enrollmentGroup...");
            EnrollmentGroup enrollmentGroupResult =
                await provisioningServiceClient.CreateOrUpdateEnrollmentGroupAsync(enrollmentGroup).ConfigureAwait(false);
            Console.WriteLine("\nEnrollmentGroup created with success.");
            Console.WriteLine(enrollmentGroupResult);
            #endregion
    
        }
    }
    
  9. 最后,将 Main 方法替换为以下行:

    static async Task Main(string[] args)
    {
        await RunSample();
        Console.WriteLine("\nHit <Enter> to exit ...");
        Console.ReadLine();
    }
    
  10. 保存所做更改。

本部分介绍如何创建一个 Node.js 脚本,用于将注册组添加到预配服务。

  1. 在工作文件夹的命令窗口中,运行以下命令:

    npm install azure-iot-provisioning-service
    

    此步骤将下载、安装 Azure IoT DPS 服务客户端包及其依赖项,并添加对其的引用。 此包包含 Node.js 服务 SDK 的二进制文件。

  2. 使用文本编辑器,在工作文件夹中创建 create_enrollment_group.js 文件。 将以下代码添加到文件并进行保存:

        'use strict';
        var fs = require('fs');
    
        var provisioningServiceClient = require('azure-iot-provisioning-service').ProvisioningServiceClient;
    
        var serviceClient = provisioningServiceClient.fromConnectionString(process.argv[2]);
    
        var enrollment = {
          enrollmentGroupId: 'first',
          attestation: {
            type: 'x509',
            x509: {
              signingCertificates: {
                primary: {
                  certificate: fs.readFileSync(process.argv[3], 'utf-8').toString()
                }
              }
            }
          },
          provisioningStatus: 'disabled'
        };
    
        serviceClient.createOrUpdateEnrollmentGroup(enrollment, function(err, enrollmentResponse) {
          if (err) {
            console.log('error creating the group enrollment: ' + err);
          } else {
            console.log("enrollment record returned: " + JSON.stringify(enrollmentResponse, null, 2));
            enrollmentResponse.provisioningStatus = 'enabled';
            serviceClient.createOrUpdateEnrollmentGroup(enrollmentResponse, function(err, enrollmentResponse) {
              if (err) {
                console.log('error updating the group enrollment: ' + err);
              } else {
                console.log("updated enrollment record returned: " + JSON.stringify(enrollmentResponse, null, 2));
              }
            });
          }
        });
    

  1. 打开 Windows 命令提示符。

  2. 使用 Java 服务 SDK 克隆设备注册代码示例的 GitHub 存储库:

    git clone https://github.com/Azure/azure-iot-sdk-java.git --recursive
    
  3. 从下载存储库的位置转到示例文件夹:

    cd azure-iot-sdk-java\provisioning\provisioning-service-client-samples\service-enrollment-group-sample 
    
  4. 在所选的编辑器中打开文件“/src/main/java/samples/com/microsoft/azure/sdk/iot/ServiceEnrollmentGroupSample.java”。

  5. [Provisioning Connection String] 替换为在获取适用于预配服务的连接字符串中复制的连接字符串。

  6. PUBLIC_KEY_CERTIFICATE_STRING 常量字符串替换为根 CA 证书或中间 CA 证书 .pem 文件的值。 此文件表示根 CA X.509 证书的公共部分,该证书之前已通过预配服务上传和验证,或者表示中间证书的公共部分,该证书本身已上传和验证或其签名链中的证书已上传和验证。

    证书文本的语法必须遵循以下模式,其中不包含额外的空格或字符。

    private static final String PUBLIC_KEY_CERTIFICATE_STRING = 
            "-----BEGIN CERTIFICATE-----\n" +
            "MIIFOjCCAyKgAwIBAgIJAPzMa6s7mj7+MA0GCSqGSIb3DQEBCwUAMCoxKDAmBgNV\n" +
                ...
            "MDMwWhcNMjAxMTIyMjEzMDMwWjAqMSgwJgYDVQQDDB9BenVyZSBJb1QgSHViIENB\n" +
            "-----END CERTIFICATE-----";
    

    手动更新此字符串值可能容易出错。 若要生成正确的语法,可以复制以下命令并并粘贴到 Git Bash 提示符中,将 your-cert.pem 替换为证书文件的位置,然后按 ENTER。 此命令将生成 PUBLIC_KEY_CERTIFICATE_STRING 字符串常量值的语法并将其写入到输出。

    sed 's/^/"/;$ !s/$/\\n" +/;$ s/$/"/' your-cert.pem
    

    复制并粘贴常量值的输出证书文本。

    重要

    在生产代码中,请注意以下安全注意事项:

    • 为预配服务管理员硬编码连接字符串不符合安全最佳做法。 与硬编码相反,连接字符串应采用安全方式进行存储,例如存储在安全配置文件或注册表中。
    • 确保只上传签名证书的公用部分。 不要将包含私钥的 .pfx (PKCS12) 或 .pem 文件上传到预配服务。
  7. 可以通过此示例在注册组中设置 IoT 中心以在其中预配设备。 设置的 IoT 中心必须是以前链接到预配服务的 IoT 中心。 在本文中,我们让 DPS 根据默认的分配策略(均匀加权分布),从链接中心中进行选择。 注释禁止文件中的以下语句:

    enrollmentGroup.setIotHubHostName(IOTHUB_HOST_NAME);                // Optional parameter.
    
  8. 示例代码为 X.509 设备创建、更新、查询和删除注册组。 若要验证是否在 Azure 门户中成功创建注册组,请注释禁止文件末尾附近的以下代码行:

    // ************************************** Delete info of enrollmentGroup ***************************************
    System.out.println("\nDelete the enrollmentGroup...");
    provisioningServiceClient.deleteEnrollmentGroup(enrollmentGroupId);
    
  9. 保存 ServiceEnrollmentGroupSample.java 文件。

运行注册组示例

  1. 运行示例:

    dotnet run
    
  2. 成功创建后,该命令窗口将显示新注册组的属性。

  1. 在命令提示符下,运行以下命令。 包括命令参数两侧的引号,并将 <connection string> 替换为在上一部分复制的连接字符串,将 <certificate .pem file> 替换为证书 .pem 文件的路径。 此文件表示根 CA X.509 证书的公共部分,该证书之前已通过预配服务上传和验证,或者表示中间证书的公共部分,该证书本身已上传和验证或其签名链中的证书已上传和验证。

    node create_enrollment_group.js "<connection string>" "<certificate .pem file>"
    
  2. 成功创建后,该命令窗口将显示新注册组的属性。

  1. 在命令提示符的 azure-iot-sdk-java\provisioning\provisioning-service-client-samples\service-enrollment-group-sample 文件夹中,运行以下命令以生成示例:

    mvn install -DskipTests
    

    此命令将 Azure IoT DPS 服务客户端 Maven 包下载到计算机并生成示例。 此包包含 Java 服务 SDK 的二进制文件。

  2. 切换到 target 文件夹并运行示例。 上一步中的生成会在 target 文件夹中输出使用以下文件格式的 .jar 文件:provisioning-x509-sample-{version}-with-deps.jar;例如:provisioning-x509-sample-1.8.1-with-deps.jar。 可能需要替换以下命令中的版本。

    cd target
    java -jar ./service-enrollment-group-sample-1.8.1-with-deps.jar
    
  3. 成功创建后,该命令窗口将显示新注册组的属性。

若要验证注册组是否已创建,请执行以下操作:

  1. Azure 门户中导航到设备预配服务实例。

  2. 在“设置”菜单中,选择“管理注册” 。

  3. 选择“注册组”选项卡。此时应会看到一个对应于示例中使用的注册组 ID 的新注册项。

显示门户中新创建的注册组的屏幕截图。

显示门户中新创建的注册组的屏幕截图。

显示门户中新创建的注册组的屏幕截图。

清理资源

如果你打算继续学习 Azure IoT 中心设备预配服务教程,请不要清理本文中创建的资源。 否则,请执行以下步骤删除本文中创建的所有资源。

  1. 关闭计算机上的示例输出窗口。

  2. 在 Azure 门户的左侧菜单中,选择“所有资源”。

  3. 选择你的设备预配服务。

  4. 在左侧菜单的“设置”下,选择“管理注册”。

  5. 选择“注册组”选项卡。

  6. 选中本文中创建的注册组的组名称旁边的复选框。

  7. 在页面顶部,选择“删除”。

  8. 在 Azure 门户的“设备预配服务”中,选择左侧菜单中“设置”下的“证书”。

  9. 选择为本文上传的证书。

  10. 在“证书详细信息”顶部,选择“删除”。

证书工具

Azure IoT C SDK 具有可以帮助创建和管理证书的脚本。 若要了解详细信息,请参阅管理用于示例和教程的测试 CA 证书

Azure IoT Node.js SDK 具有可以帮助创建和管理证书的脚本。 若要了解详细信息,请参阅 适用于 Node.js 的 Azure IoT 设备预配设备 SDK 工具

还可以使用 Azure IoT C SDK 中提供的工具。 若要了解详细信息,请参阅管理用于示例和教程的测试 CA 证书

Azure IoT Java SDK 包含测试工具,可以帮助创建和管理证书。 若要了解详细信息,请参阅使用 DICE 模拟器的 X509 证书生成器

后续步骤

在本文中,你已使用 Azure IoT 中心设备预配服务为 X.509 中间或根 CA 证书创建注册组。 若要进一步探索,请查看以下链接: