使用 Azure Key Vault 进行 MSIX 和 CI/CD 管道签名

签名是在使用 MSIX 包时要执行的其中一项关键任务。 如果 MSIX 包未使用受信任的证书进行签名,用户将无法安装应用程序。 同时,签名也是涉及到安全性时最重要的关键任务之一。 必须安全存储证书,避免恶意操作者重复使用它们通过我们的标识对其应用程序签名。 Azure Key Vault 是支持这一要求的最佳选项。

在本文中,我们将了解可如何在 CI/CD 管道中使用 Azure Key Vault,以便可在操作过程中自动对我们的 MSIX 包进行签名。

重要

本文中描述的过程基于一种名为 Azure SignTool 的开源工具,它既适用于 Azure Pipelines,也适合 GitHub Actions。 如果你使用的是 Azure Pipelines,那么还可将 MSIX 扩展Azure Key Vault 任务结合使用。

先决条件

  • 一个 Azure 帐户。 如果还没有 Azure 帐户,请在此处开始。
  • 一个 Azure Key Vault。 有关详细信息,请参阅创建 Key Vault
  • 一个导入到 Azure Key Vault 的有效包签名证书。 Azure Key Vault 生成的默认证书不能用于代码签名。 要详细了解如何创建包签名证书,请参阅为包签名创建证书
  • 一个 CI/CD 管道,它会生成一个托管在 Azure Pipelines 或 GitHub Actions 上的 MSIX 包。 有关详细信息,请查看使用 YAML 文件配置 CI/CD 管道

在 Azure 上注册应用程序

为了将包注册为 CI/CD 管道的一部分,我们将使用一种名为 Azure SignTool 的工具。 它的工作方式与 Windows 10 SDK 中包含的标准 SignTool 实用程序类似,但它会连接到 Azure Key Vault 来使用其中一个可用证书,而不是使用本地证书。 但若要建立连接,我们首先需要在 Azure 上注册一个应用程序,它将向我们提供启用 Azure SignTool 来针对 Azure Key Vault 服务进行身份验证所需的凭据。

打开 Azure 门户,然后在可用服务中选择“Azure Active Directory”。 单击“应用注册”,然后选择“新建注册”来开始该流程。 为应用程序指定一个青春(例如,它在下图中是 SignToolForContoso),然后保留默认设置

Register an application on Azure

接下来是将应用程序看作是公共客户端,因为我们的方案不需要重定向 URI。 转到“身份验证”部分,在“高级设置”下,将“将应用程序视为公共客户端”开关更改为“是”

Set advanced settings

最后一步是创建客户端密码,它是我们在从 Azure SignTool 进行身份验证时需要使用的密码。 移动到“证书和密码”部分,然后单击“新建客户端密码”。 为其指定一个名称,选择过期时间,然后按“添加”按钮。 你将被重定向回到主页面,密码将在这里与其值一并列出。 请一定要复制它并将其存储在安全的位置。 你将无法再次检索它。 刷新页面后,密码将被掩码,没有任何方式来显示它。 此时你只能生成新的密码。

你需要与客户端密码一起保存的最后一项信息是应用程序标识符。 单击“概述”回到应用程序的主页,然后在上半部分中,查找“应用程序(客户端) ID”值

Application Id

启用对 Azure Key Vault 的访问权限

接下来是配置我们刚才创建的用于访问 Azure Key Vault 服务的 Azure 应用程序。 在 Azure 门户中,转到包含你要用于对 MSIX 包进行签名的证书的 Azure Key Vault 实例。 转到“访问策略”部分,然后单击“添加访问策略”。 该工具支持选择其中一种可用的模板来定义我们想要授予的权限,但在我们的方案中,这些模板都不适合。 同样,我们将需要使用下拉列表来手动设置以下选项:

  • 在“密钥权限”下,启用“签名”选项
  • 在“证书权限”下,启用“获取”选项

最后一个重要的步骤是指定哪个应用程序将访问此策略。 单击“选择主体”,使用 Azure 应用程序的名称搜索你在上一步中创建的应用。 在本例中,它被命名为“SignToolForContoso”

Select principal

找到后,按“选择”。 策略应如下所示。

Add access policy

完成此过程后,单击“添加”来创建策略

使用 Azure SignTool 在本地对包进行签名

Azure 配置现已完成,我们可使用 Azure SignTool 来对包进行签名了。 在本部分,我们将在本地使用该工具来熟悉相关用法。 在接下来的部分中,我们会将它用作 CI/CD 管道的一部分。

该工具以 .NET 全局工具的形式提供。 请确保已安装最新的 .NET SDK,然后打开命令提示符并启动以下命令:

dotnet tool install --global AzureSignTool 

现在,你就可使用 AzureSignTool 命令对你的包进行签名,它要求使用以下参数:

  • kvu 是 Azure Key Vault 的 URL。 你可在 Azure 门户中服务主页中的“DNS 名称”下找到它。
  • kvi 是你已注册的 Azure 应用的应用程序 ID,你之前已记下此信息。
  • kvs 是你之前生成的客户端密码,你之前已记下此信息。
  • kvc 是要使用的证书的易记名称。
  • tr 是时间戳服务器的 URL。 通过使用此选项,当证书过期后,我们也能使我们的包正常工作。
  • v 是我们要对其进行签名的 MSIX 包的路径。

下面是一个示例命令:

AzureSignTool sign -kvt "<tenantID>" -kvu "https://contosoexpenses-blog.vault.azure.net/" -kvi "64fae35e-cb84-4b9f-86eb-5170d169316d" -kvs "this-is-the-secret" -kvc "MyCertificate" -tr http://timestamp.digicert.com -v .\MyContosoApp.msix

注意

若要了解有关 AzureSignTool 的详细信息,请运行 AzureSignTool sign --help

将 Azure SignTool 与 Azure Pipelines 配合使用

本部分假设你已有一个 CI/CD 管道用于在 Azure Pipelines 上配置有 YAML 文件的 Windows 应用程序,如此处所述。

首先,需要创建几个变量来存储 Azure SignTool 连接到 Azure Key Vault 所需的信息。 在 Azure DevOps 中,选择你的管道,然后按顶部的“编辑”按钮。 进入 YAML 编辑器后,单击顶部的“变量”按钮来打开面板。 单击“+”按钮来添加以下变量:

  • AzureKeyVaultName,带有保管库的易记名称。
  • AzureKeyVaultUrl,带有保管库的 URL。
  • AzureKeyVaultClientId,带有 Azure 应用程序的 ID。
  • AzureKeyVaultClientSecret,带有 Azure 应用程序的客户端密码。

创建每个变量时,都请务必启用“保留此值”密码选项。 它将确保有权访问管道的其他人无法查看其值。

Add variable

现在,你可添加 .NET Core 任务在代理上安装 Azure SignTool,来自定义现有 YAML 管道。 这是要添加的 YAML:

- task: DotNetCoreCLI@2
  displayName: 'Install Azure SignTool'
  inputs:
    command: custom
    custom: tool
    arguments: 'install --global AzureSignTool'

下一步是添加 PowerShell 任务来执行将对包进行签名的命令。 一旦 MSIX 包创建后,就只能在生成过程结束时执行此任务。

- powershell: '& AzureSignTool sign -kvu $(AzureKeyVaultUrl) -kvi $(AzureKeyVaultClientId) -kvs $(AzureKeyVaultClientSecret) -kvc $(AzureKeyVaultName) -tr http://timestamp.digicert.com -v "$(System.DefaultWorkingDirectory)\MyPipeline\MyContosoApp\MyContosoApp.msix"'
  displayName: 'Sign the package'

此命令与我们用于在本地对包签名的命令很相似。 只存在下述区别:

  • 我们会将我们创建的变量与 $(Variable-Name) 语法相结合,而不是对各种参数使用固定值
  • MSIX 包的路径指向在生成结束时创建的 MSIX 包所在的代理上的文件夹。

将 Azure SignTool 与 GitHub Actions 结合使用

本部分假设你已有一个 CI/CD 管道用于在 GitHub Actions 上配置有 YAML 文件的 Windows 应用程序,如此处所述。

就像在 Azure 管道上操作的一样,首先需要安全地存储凭据。 GitHub 使用密码,它们可添加到存储库的设置中。 进入托管你的 Windows 应用程序的 GitHub 存储库后,单击“设置”,然后转到“密码”

与使用 Azure Pipelines 时的操作类似,你将单击“新建密码”来创建 4 个密码

  • AzureKeyVaultName,带有保管库的易记名称。
  • AzureKeyVaultUrl,带有保管库的 URL。
  • AzureKeyVaultClientId,带有 Azure 应用程序的 ID。
  • AzureKeyVaultClientSecret,带有 Azure 应用程序的客户端密码。

与 Azure 管道的区别是密码隐式隐藏,因此你不必启用任何选项来保护它们。

现在,通过存储库的“操作”选项卡,你可打开现有工作流并添加进行签名所需的任务。 第一项任务将在代理上安装 Azure SignTool:

- name: Install AzureSignTool
  run: dotnet tool install --global AzureSignTool

第二项任务将对包进行签名;因此,必须在 Visual Studio 生成完成且 MSIX 包已生成之后,再执行此任务。

 - name: Sign package
   run: |
        Get-ChildItem -recurse -Include **.msix | ForEach-Object {
        $msixPath = $_.FullName
        & AzureSignTool sign -kvu "${{ secrets.AzureKeyVaultUrl }}" -kvi "${{ secrets.AzureKeyVaultClientId }}" -kvs "${{ secrets.AzureKeyVaultClientSecret }}" -kvc ${{ secrets.AzureKeyVaultName }} -tr http://timestamp.digicert.com -v $msixPath
        }

与我们在 Pipelines 中采用的任务相比,此任务中有几项区别。 第一个是 GitHub 使用不同的语法来访问密码,即使用 ${{ secrets.SECRET_NAME }}。 因此,各种参数填充了我们之前在“密码”部分中创建的值。 另一点区别是你需要使用其他方法来查找用于签名的 MSIX 包。 此任务使用 PowerShell 脚本而不是指向特定的 MSIX 包,该脚本会循环访问生成输出中存储的所有文件。 如果文件具有 MSIX 扩展名,则它将使用 AzureSignTool 命令对其进行签名。

部署包

无论你选择的 CI/CD 平台如何,在流程结束时,你都将有一个用 Azure Key Vault 中存储的证书签名的 MSIX 包。 现在,你可使用任何其他可用任务来通过你首选的分发部署包,这些分发包括 Microsoft Store、网站和 Microsoft Store 等。