Signing a MSIX package with Azure Key Vault
Published Jun 03 2020 06:58 AM 7,649 Views
Microsoft

Signing is one of the most critical aspects when working with MSIX packages. One of the key goals of MSIX as deployment technology is to ensure safety and trust and signing is one of the ways to achieve it. Thanks to signing, we are sure that the application we're about to install is coming from a trusted publisher and not from a potentially malicious developer. Certification authorities (either enterprise or public ones) have a rigorous background check process, which makes sure that when you request a certificate under a name or a brand, you're indeed the person or the company you claim to be.

 

certificate.png

For the same reason, certificates must be stored safely to avoid any identity theft. If we would expose the private key of the certificate on a public repository, for example, other developers could reuse it and sign other applications with our identity.

 

As such, especially when we want to implement a CI/CD pipeline to continuously build and deploy our desktop applications, we need to find the right balance between safety and convenience. We want to make it easy to sign our packages with our certificates as part of the process; at the same time, we must do it without compromising the private key.

 

There are various options to achieve this goal. In this post, we're going to focus on Azure Key Vault, a service provided by Azure which allows to store in a safe way keys, secrets and certificates. Azure Key Vault is a critical service for many scenarios: as developers, for example, we can use it to store the connection strings to our database; or the secrets to connect to a 3rd part service, like a protected API. Thanks to the APIs offered by Azure Key Vault, we can retrieve the protected information at runtime, without having to expose them in the source code or in a configuration file. Among the various features offered by Azure Key Vault, we can use it also to store certificates. We can use it to acquire, generate and renew certificates from a public authority, relieving us from all the troubles of certificates management.

 

In this post we're going to see how we can store on Azure Key Vault a certificate we own and then use it to sign a MSIX package using an open source tool called Azure Sign Tool, developed by Kevin Jones. In the end, we're going to see how we can integrate it in our CI/CD pipeline on Azure DevOps or GitHub.

 

Setup Azure Key Vault

As first step, you must login to the Azure Portal with your Azure credentials. Click on the Create a resource button and, using the internal search, look for Key Vault:

 

KeyVault.png

Click on Create to kick start the provisioning process. There are only a few required settings to setup:

 

  • Name
  • Region
  • Pricing tier: for this post I'm going to use the default one. However, if you really care about security, you should consider the Premium tier, which supports to store secret and keys backed by Hardware Security Modules.
  • Retention period (leave the default value of 90 days)

KeyVaultSetup.png

Once you're done, click Review + create, followed by the Create button to complete the deployment. Once it's completed, go to the resource. We're now ready to import our certificate.

 

Create or import the certificate

Before you start you need to decide which kind of certificate you're going to use. Ideally, you should have acquired it from a certificate authority like DigiCert. These certificates are the best to use for public distribution, since they are automatically trusted by Windows 10. As such, the user won't have to do anything special to install your application. If you don't have one and you aren't willing to buy one for the moment, you can also generate a self-signing certificate directly through the portal. It isn't the ideal solution for publicly distributing an application since these certificates can't validate the identify of the person / company and, as such, they aren't implicitly trusted by Windows. The user will need to manually trust the certificate, which requires administrator rights.

 

However, self-signing certificates are good enough for testing, so let's use this approach. In the Key Vault you have just created move to Certificate and click on Generate/Import.

 

NewCertificate.png

 

Let's start the process to generate a new self-signed certificate. This is the setup screen:

 

CreateCertificate.png

These are the values to configure:

  • Method or Certificate creation: since we're creating a new certificate, leave Generate.
  • Certificate Name: this name identifies your certificate.
  • Type or Certificate Authority (CA): leave self-signed certificate. Eventually, you can change this option to start the process to directly purchase a certificate from a public CA and store it in Key Vault.
  • Subject: this field is very important because it must match the Publisher you're going to use in the manifest of your MSIX package.

Leave all the other settings with the default value. However, there's one last important section to setup: Advanced Policy Configuration. If we click on it, we will have access to various advanced options for the certificate. The most important one is Ektended Key Usage (EKUs), which specifies the various use cases supported by the certificate. As per the documentation, in order to use a certificate to sign a MSIX package we need to enable the following EKUs:

 

  • 1.3.6.1.5.5.7.3.3 indicates that the certificate is valid for code signing.
  • 1.3.6.1.4.1.311.10.3.13 indicates that the certificate respects lifetime signing.

As such, you need to replace the existing values with these two ones, separated by a comma. Another setting that you may want to configure, in order to get better security, is to set the Exportable Private Key to No. This way, even if you store the certificate locally, you won't be able to export the private key in any way.

 

PolicyConfiguration.png

 

At the end of the process, press Create. The certificate will be displayed initially in the In progress, failed or cancelled section but, if you refresh after a few seconds, you will see it appearing in the Completed section.

 

CertificateCreated.png

If, instead, you already have a certificate you want to use, you'll just need to choose Import in the Method of Certificate creation dropdown when you start the process to generate or import a certificate.

 

ImportCertificate.png

In this case the procedure will be simpler, since you'll just need to assign a name to the certificate, browse your computer to find the right PFX file and provide the password.

Register a new Azure application

In order to access to our Key Vault, we need a way to connect to it through protected credentials. The way to do that is by registering a new application in Azure, which will have access to our certificate. Then, the Azure Sign Tool utility will connect to the Key Vault through the credentials associated to our application.

 

Let's start the process. Go back to the home page of your Azure portal and choose Azure Active Directory, then move to App registrations. Click on New registration to start the process.

 

NewRegistration.png

 

Give a name to your application, then leave the default settings. We aren't going to use this application to enable the Microsoft Identity platform, so we can use the standard configuration.

 

RegisterAnApplication.png

The next step is to treat the application as a public client, since we are in a scenario where a redirect URI is not used (again, we aren't using this registration to enable authentication in an web or desktop application to leverage features like SSO or Microsoft Graph). Move to the Authentication section and, under Advanced settings, move the switch Treat application as a public client to Yes.

 

AdvancedSettings.png

The last step is to create a client secret, which is the password we'll need to authenticate from the Azure SignTool. Move to the Certificates & secrets section, then click on New client secret.

 

AddAClientSecret.png

Once you have pressed the Add button, you will be redirected back to the main page, where the secret will be listed together with its value. Make sure to copy it and to store it somewhere safe. You won't be able to retrieve it again. As soon as you refresh the page, the secret will be masked and there won't be any way to reveal it. You will be forced to generate a new secret.

 

There's one last information we need to store together with the client secret: the application identifier. Go back to the home of the application (by clicking on Overview) and, in the upper section, look for the value Application (client) ID:

 

ApplicationId.png

Make a note of it, since we're going to need it later. That's it for the app configuration.

 

Enable access to Key Vault

Now go back to the Key Vault you have previously created and move to the Access policies section. This is the place where we can enable our new application to access to the information stored in the Key Vault. 

 

AccessPolicies.png

 

Click on Add Access Policy to start the wizard to setup the access. The tool supports choosing one of the available templates to define the permissions we want to grant but, in our scenario, no one of them is a good fit. We can manually set the right permission using the other dropdowns. Specifically, for our scenario we need to:

 

  • Under Key permissions, enable the Sign option.
  • Under Certificate permissions, enable the Get option.

These permissions are enough for Azure SignTool: the second one will enable it to download the certificate, the first one to use it for signing the MSIX package.

 

The last important step is to specify which application is going to access to this policy. Click on Select principal and, using the internal search, look for the Azure application you have created in the previous step. In my case, for example, it's called SignToolForContoso:

 

SignToolForContoso.png

Once you have found it, click on it and then press Select at the end of the blade.

This is how the policy should look like:

 

AddAccessPolicy.png

Press Add and, when you're back to the main Access policies page, hit Save.

 

Sign the MSIX package

We have completed the configuration on the Azure side. Now we can start the process of signing the MSIX package. As first step, we need to acquire the Azure SignTool utility. Azure SignTool works like the signtool included in the Windows 10 SDK, with the difference that instead of using a local certificate, it can leverage the ones we have stored in Azure Key Vault.

The easiest way to acquire it is through the .NET Core CLI, since it's available as a .NET Core Global Tool. Make sure to have the latest .NET Core SDK installed on your machine, then open a terminal and run the following command:

 

dotnet tool install --global AzureSignTool --version 2.0.17

Once the operation has been completed, you will be able to run the command simply by typing AzureSignTool from any location. Now you will be able to sign your MSIX packages by running the following command:

 

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

Let's see in details all the parameters we have configured:

  • kvu is the URL of your Key Vault. You can find it in the main page of the service in the Azure portal, under DNS Name.

DNSName.png

  • kvi is the application id of the Azure app we have previously registered.

  • kvs is the client secret we have previously generated.

  • kvc is the friendly name of our certificate that we have chosen during the creation / import process.

  • tr is the URL of a timestamp server. By using this option, we can enable our signing to work even after the certificate has expired.

  • v is the path of the MSIX package we want to sign.

That's it. If we did everything correct, this is the output we should see in the terminal:

 

Terminal.png

 

Please note! Be aware that future releases of Azure SignTool will require also a new parameter to do the signing: -kvt, which is the Directory (tenant) ID that you can find in the Azure App registration page.

 

TenantId.png

This is a new requirement enforced by the most recent versions of the Azure SDK leveraged by the tool. The current version is still using the old one so, until the new version will be out, the -kt parameter won't be needed (actually it will break the tool, because the current version doesn't recognize it as a valid parameter).

What if something goes wrong?

There are two main reasons that could lead the Azure SignTool to fail:

    1. You didn't follow all the steps in the right way. It's enough to miss one of the steps (like forgetting to set the Azure app as public client or missing one of the permissions to the access policy) to move to an unsupported state.

    2. You didn't set the Publisher in the manifest of your MSIX package with the same subject you have selected for the certificate. For example, in my case I have created the certificate with subject CN=AppConsult, so the Identity section of my manifest looks like this:

       

      <Identity Name="MyContosoApp" Publisher="CN=AppConsult" Version="1.0.1.0" ProcessorArchitecture="x86" />

Installing the application

If you did everything correctly, when you double click on the MSIX package Windows will list it as a trusted package and you will be able to go on and install it:

 

TrustedPublisher.png

If you see, instead, an error like the one below, it's simply because the certificate is not trusted. Remember that, if you're using a self-signed certificate like I did so far, it isn't automatically trusted by Windows.

 

NotTrusted.png

As such, you will need to right click on the MSIX package in File Explorer, click on Properties, choose Digital Signatures and, from there, start the process to install the certificate in the Trusted People store.

 

Using Azure Key Vault as part of your CI/CD pipeline

So far we have seen how to manually use the Azure SignTool utility. However, the scenario in which can be really helpful is in a CI/CD pipeline, where we need to automate the signing operation but, at the same time, protect the certificate. Regardless if we have built our CI/CD pipeline in Azure DevOps or GitHub, the fundamentals of the operations are the same:

 

  1. We need to install the Azure SignTool on the hosted agent
  2. We need to run a script to sign the package using the same command we have just used manually from the terminal

The only difference, based on the platform you're using, is the way you're going to use to protect the sensitive information required by the tool, like the application id and the client secret. We can't expose them publicly in the pipeline, otherwise we are back to square one. Let's see how to leverage Azure Key Vault in two common platforms: Azure DevOps and GitHub Actions.

 

Using Azure Key Vault in Azure DevOps

If you don't already have a CI/CD pipeline for a desktop app on Azure DevOps, you can learn how to create one in the official documentation or in the last chapter of my free e-book.

When I use this platform, I prefer to leverage a Release pipeline to take care of the signing and deployment of the MSIX package. This way, I can define multiple deployment stages. For example, this is how my Release pipeline looks like:

 

ReleasePipeline.png

 

I have two stages: one for deployment in a testing environment and one in production environment. The deployment to the testing environment kicks in automatically whenever there's a new MSIX package (which is produced, instead, by a regular pipeline). Let's see how one of my stages look like:

 

pipeline.png

 

In this pipeline you can see the two actions we need to perform: install Azure SignTool and sign the package with it. Let's configure it. As first step, we need to use the Variables feature provided by Azure DevOps to safely store the credentials to access to Key Vault. Click on Variables, make sure you are in the Pipeline variables section and then add a new variable for each sensitive information:

 

  • The Azure Key Vault URL
  • The Azure Key Vault name
  • The application identifier
  • The client secret

After you have created them, make sure to click on the lock icon near each variable. This option will protect them, making sure that no one else (including yourself) will be able to see the value.

 

Variables.png

 

As second step, click on Agent job and make sure it's running on windows-2019. Azure SignTool is a .NET Core Global Tool, but it leverages specific Windows APIs to perform the signing. Then let's add the first task, by clicking on the + sign near the Agent job. Look for the task called .NET Core and click on Add:

 

netcoretask.png

 

Now let's configure the task to install Azure SignTool:

  • Display name will be used as a label for the step, so choose the name you prefer (in my case, I used Install Azure SignTool).
  • Command is the .NET Core command we want to perform. Choose custom, since tool isn't available in the dropdown.
  • Custom command is the .NET CLI command we want to run. Set it to tool.
  • Arguments is where we define all command parameters. Set it to install --global AzureSignTool --version 2.0.17.

InstallAzureSignTool.png

In case you're using a multi-stage deployment pipeline based on YAML instead of a traditional release pipeline, this is the equivalent YAML of the task we have just created:

 

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

The next step is to sign the package. Click again on + near Agent job and, this time, add a PowerShell task. Give it a name in the Display name field and, in the Type option, choose Inline. Then copy and paste the following command in the Script field:

 

& AzureSignTool sign -kvu $(AzureKeyVaultUrl) -kvi $(ClientId) -kvs $(ClientPassword) -kvc $(AzureKeyVaultName)-tr http://timestamp.digicert.com -v "$(System.DefaultWorkingDirectory)\_ContosoExpenses-Basic\CD\ContosoExpenses.Package_$(Build.BuildNumber).0_Test\ContosoExpenses.Package_$(Build.BuildNumber).0_x86.msixbundle"

We're launching the same exact command we have previously manually launched on our computer. The only difference is that the various parameters which contains sensitive information are set using the variables we have previously defined, by leveraging the Azure DevOps syntax $(variable-name). The -v parameter is the only one you will have to change, since it must point to the path of your MSIX file in your artifacts folder.

This is the equivalent YAML for a multi-stage pipeline:

 

steps:
- powershell: '& AzureSignTool sign -kvu $(AzureKeyVaultUrl) -kvi $(ClientId) -kvs $(ClientPassword) -kvc $(AzureKeyVaultName)-tr http://timestamp.digicert.com -v "$(System.DefaultWorkingDirectory)\_ContosoExpenses-Basic\CD\ContosoExpenses.Package_$(Build.BuildNumber).0_Test\ContosoExpenses.Package_$(Build.BuildNumber).0_x86.msixbundle"'
  displayName: 'Sign the package'

That's it. Now that you have a signed MSIX package, you can add another task to take care of the deployment, based on your infrastructure. In my case, for example, I'm copying the file on a blob storage on Azure.

 

Using Azure KeyVault with GitHub Actions

GitHub Actions is gaining lot of popularity and the .NET team has done a great job to support Windows developers who want to use it to build and deploy their desktop applications. Edward Skrod has prepared a detailed step-by-step guide and also helped to put together a workflow template that you can use as a starting point.

 

Compared to Azure DevOps, currently GitHub doesn't support a Release management story, but you can create multi-stage pipelines which performs the tasks in different stages. For example, you can have a single YAML file which describes both a build and a deployment task. Let's see how to add to our workflow the tasks we need to install and use Azure SignTool.

 

As first step, like we did on Azure DevOps, we need to safely store the credentials. GitHub calls them secrets and they can be added in the settings of your repository. Once you're on your GitHub repository, click on Settings then move to Secrets.

 

Secrets.png

 

From here, you just need to click on New secret and, like you did with Azure DevOps, add a new secret for each protected information: the Key Vault URL, the Key Vault name, the application id and the client secret. The only difference is that we won't have to take any extra step to protect them since secrets are already stored in a safe way. You'll be able only to update or remove a secret, no one will see its content.

 

Now you can move to your workflow and add the tasks we need to do the signing. You will have to add them after that the MSIX package has been generated so, ideally, after the Build task.

Let's start from the task to install Azure SignTool:

 

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

Nothing special, it's just a script task which will execute the same command we have used before to install Azure SignTool on our PC. Let's move now to the singing task:

 

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

In this case, there are a few differences compared to the Azure DevOps one. The first obvious one is that GitHub uses a different syntax to access to the secrets, which is ${{ secrets.SECRET_NAME }}. As such, the various parameters are filled with the values we have previously created in the Secrets section. The other one is that I'm using a slightly different approach to find the MSIX packages to sign. I'm using a PowerShell script which iterates through all the files stored in the build output and, only if they have the .msix extension, then I go on and use the Azure SignTool utility on it.

 

That's it. Now you can add additional tasks to the pipeline to take care of the deployment of the signed MSIX package on a website, on the Microsoft Store or any other supported approach.

 

Signing with Visual Studio

As a last "bonus information", Visual Studio 16.6 has recently added support for Azure Key Vault when you generate MSIX packages. This could be a great alternative to Azure SignTool if you're generating MSIX packages locally using the Windows Application Packaging Project. To enable this option you just have to right click on the Windows Application Packaging Project included in your solution and choose Publish → Create app packages. After you have chosen the path to create a package for sideloading (apps published on the Microsoft Store don't need to be signed, since the Store submission process will take care of that), you will see a new option in the list:

 

VisualStudioAzureKeyVault.png

 

By pressing the Select From Azure Key Vault button you will have the opportunity to login with your Azure account, specify the URL of your Azure Key Vault and select one of the available certificates, as you can see from the image below:

 

VisualStudioPickCertificate.png

 

However, I would recommend using this feature only for some specific scenarios, like you need to quickly generate and share a MSIX package with a colleague. The best approach to generate MSIX packages is to leverage a CI/CD pipeline to automate the process and to use Azure SignTool as part of it.

Wrapping up

Having a solid signing story when you choose to distribute your Windows desktop application with MSIX is critical. In this blog post we have seen how we can use Azure Key Vault to protect our code-signing certificate (and even generate it) and then, using an utility called Azure SignTool, sign the MSIX package. This approach can be extremely helpful when you enable a CI/CD approach to automatically build and deploy your MSIX packages, since you need to find the right balance between convenience (you need to find an easy to way to sign the packages as part of your pipeline) and security (you can't store in public your private certificate). Azure Key Vault and Azure SignTool are a great combination to solve this problem, since they give you the opportunity to sign your MSIX packages as part of the CI/CD pipeline but without storing it in the repository or in the Azure DevOps / GitHub project.

 

You can take a look an existing workflow on GitHub which uses this approach here.

 

Before wrapping up, I want to thank Claire Novotny who helped me figuring out a couple of issues around authentication with Azure Key Vault.

 

Happy packaging!

2 Comments
Version history
Last update:
‎Jun 03 2020 11:52 PM
Updated by: