How do we ensure that the code we’re installing is, at the very least, the code that a vendor shipped? The generally accepted solution is code signing, adding a digital signature to binaries that can be used to ensure authorship. At the same time, the signature includes a hash that can be used to show that the code you’ve received hasn’t been altered after it’s been signed.
Code signing is increasingly important as part of ensuring software bills of materials and reducing the risks associated with malware hijacking legitimate binaries. Signing is necessary if you’re planning on using services like the Microsoft Store or the Windows Package Manager to distribute your applications, allowing the repository to verify software sources.
Using public key infrastructure to secure code
The process of code signing is a straightforward one, building on familiar public key cryptography techniques. Like these it requires digital certificates and signatures, verifying the publisher’s identity and the certificate authority that issued the underlying certificates. That last feature is key to establishing the trust relationship between publisher and code, with a list of trusted certificate authorities managed both by stores and the underlying OS that the certified code targets.
For many good reasons it’s becoming a lot harder to manage your own signing infrastructure. Self-signed certificates aren’t accepted by stores, though they can be used for internal code distribution, and certificates now need to be held in trusted hardware security modules, where they must be regularly renewed to ensure that you’re always using a valid certificate for your code.
Microsoft has long offered several different ways of signing code, using the technique to ensure that Windows only installs trusted drivers. The process has since been extended to Microsoft’s own software updates and to the installers used by the Windows Store.
One of those ways was Azure Code Signing, which didn’t really capture the attention of developers, despite integrating well with Visual Studio and the Windows Store. However, Azure Code Signing wasn’t the cheapest option either, and had to compete with on-premises solutions.
Introducing Trusted Signing
Microsoft has both simplified its cloud-hosted option and incorporated the latest updates to Azure’s secure computing infrastructure, rolling out a preview of what it calls Trusted Signing. As part of the launch, Microsoft is introducing a new pricing structure and providing integration with GitHub’s build pipelines.
The goal behind Trusted Signing is to bring the entire code signing life cycle into one place, simplifying the process of acquiring the necessary certificates, storing them securely, and providing a secure and private way of signing code.
The service builds on top of familiar Azure tools. You can set it up from the Azure Portal, adding a Trusted Signing account to a resource group inside your subscription. It’s best to set up a separate resource group for code signing that’s not used for anything else, as this lets you more effectively control the users and roles that have access.
There are two options for Trusted Signing: basic and premium. The main difference between the two is the number of identity validations and certificate profiles you can store. Identity validation is used to prove who you (or your organization) are, while certificate profiles are used to generate the certificates used to sign your code. Certificate profiles contain information about the role of the signature and how the signature is trusted.
Getting started with Trusted Signing
Getting started is straightforward. You can use the Azure Portal or the Azure CLI, though you can validate identity only through the portal. You should keep this in mind if you’re using the CLI, as it adds another step and prevents you from automating the process.
The first step is to register a code signing resource provider in your Azure subscription. There’s a list of resource providers in your account settings, where you can select the Microsoft.CodeSigning option. This toggles from NotRegistered to Registered, allowing you to set up Trusted Signing by creating an account to hold your identity details and signing certificate profiles. Currently you’re limited to Azure’s US and Europe regions, with each region having its own dedicated endpoint URL that can be used to add automated signing to external build services.
Next you create a Trusted Signing account as a new Azure resource. Like most Azure resources, creating the resource involves stepping through a basic wizard, filling out the necessary forms to create a new resource group, pick a pricing plan, and deploy your account to an Azure region. It takes a few minutes to deploy your instance, then you can validate your organization’s identity.
You’ll need to be logged into Azure with an account that has the appropriate Trusted Signing Identity Verifier role. You will need to choose whether you’re validating a public or private identity. A public identity needs a legal business identity for the certificate, while private requires only your Azure tenant. Public identities must have a URL, a contact email address, and your Microsoft Store Seller ID, if you intend to deliver code through the Microsoft Store.
There are some limitations that make using Trusted Signing impossible for startups or sole traders, as you need to have three years of tax history. (The documentation implies that this may change in the future.) You may need to provide additional information through the portal if Microsoft needs additional verification. Don’t expect an instant response. It could take a week or more to get an account verified.
Once that happens, you can create a certificate profile, again for public or private use. Start by filling out a form with the information that will be encoded in your signing certificates. You should now be ready to start signing code using the details stored in your Trusted Signing account, linking it to a growing set of signing integrations.
Adding Trusted Signing to a GitHub build action
Perhaps the most interesting option is support for using a GitHub action to sign your code as soon as a build completes, using one of the Windows runners. Simply add the action after a build completes, using your account secrets and the code signing endpoint for your Trusted Signing account. As part of the configuration process, you need to add the folder and file types that are being signed, the hash type you’re using, and an RFC 3161 timestamp. If you’re targeting specific output files, you can include a file catalog file that lists the files being signed.
The result is a hands-off approach to delivering a signed build: Push some code to a specific production branch of your repository, and the action will handle building, packaging, and signing the code. It will even deliver it to the Windows Store or a Windows Package Manager repository. The runner is available in the Visual Studio Store, so it can be managed and edited inside Visual Studio.
Automating the certificate life cycle
Another aspect of the service is support for your certificate life cycle. The underlying model is the standard x.509 approach for certificates and their keys. It’s important to note that certificates are short-lived: They are renewed daily and are valid for only 72 hours. That allows you to quickly invalidate specific builds that may have been compromised. You don’t have to do anything. The entire process is automatic, with issued certificates logged in Azure and stored and managed in secure cryptographic hardware.
Of course, one of the key questions is cost, and Trusted Signing isn’t too expensive. The basic option, which allows one of each certificate profile type, costs $9.99 for 5,000 signatures per month (additional signings are $0.005 per signature). If you need more certificates, you can choose the $99.99 premium option with 100,000 signatures per month and the same pricing for overages, with 10 of each type in each account.
Microsoft is going a long way to make code signing simpler to use. It would be nice if there were a free option along the lines of Let’s Encrypt for open-source projects or for individual developers, but for now that’s not on the table.
What’s needed is a code signing infrastructure that’s simple to use and available for everyone, so we can make code signing a natural part of the software development life cycle. The more code that’s signed, the lower the risk for us all, including for Microsoft and Windows. Providing a signing service like Trusted Signing as a public service might be the best approach in the long run, but for now, Trusted Signing is what we have.