This project aims to make it easier to integrate Authenticode signing into a CI process by providing a secured API for submitting artifacts to be signed by a code signing cert held on the server. It uses Azure AD and Azure Key Vault's HSM for security. It is part of the .NET Foundation, and operates under their code of conduct. It is licensed under MIT (an OSI approved license).
There are a few pieces to the security model:
- One app registration for the service itself
- One app registration to for the sign client
- Service account users in AD
- Key Vault HSM's, one for each service account user
The system is designed to support multiple certificates belonging to different users. Many organizations may only have a single certificate, and thus will have a single user service account and a single vault. You may have multiple service accounts configured to the same vault as well for granular auditing.
Certificates are stored in a Key Vault. Due to the way Key Vault's security works - you can control access to the vault as a whole, not per certificate - every user service account gets its own vault. There are no charges per vault; only per certificate.
There is an administrator UI to create sign service user accounts and set the properties. The admin UI also creates the Key Vaults and configures the permissions appropriately.
The Sign Service requires user accounts to be used as service accounts. This is due to a current limitation of Azure AD that doesn't allow a Service Principal to Service Principal On-Behalf-Of flow. That flow is part of the defense-in-depth, preventing the signing service from having direct access to the signing functions on its own. The service can only access the signing method on-behalf-of the user at the time of request.
The service currently supports either individual files, or a zip archive that contains supported files to sign (works well for NuGet packages). The service code is easy to extend if additional filters or functionality is required.
.msi
,.msp
,.msm
,.cab
,.dll
,.exe
,.appx
,.appxbundle
,.msix
,.msixbundle
,.sys
,.vxd
,.ps1
,.psm1
, and Any PE file (via AzureSignTool).vsix
via OpenOpcSignTool- ClickOnce
.application
and.vsto
(viaMage
). Special instructions below. .nupkg
via NuGetKeyVaultSignTool
Documentation is here:
There is a new optimization in the 1.1 release that takes advantage of optional claims in the access token to improve per-request time from from the SignClient. To enable this, a global admin needs to do one of two things:
- Run the
InstallUtillity
again. Use the same command line parameter (if you used one). It will find the existing application registration and prompt you to update. You don't need to recreate the resource groups.
OR
- #to the admin UI, and on the
Adv Setup
tab, selectRegister Extension Attributes
. There isn't any indication currently, but you just need to select it once (clicking it again won't hurt). Caution: Do not click theUnregister Extension Attributes
option.
You can verify it worked by going to the Azure AD Admin portal -> Application Registrations -> SignService - Server ... -> Manifest. In the manifest, you should see data in the optionalClaims
property; if it's null
, then the optimization is not enabled.
The service will keep working without this optimization until 2.0, so while optional, it's recommended that it be enabled.
The latest 1.0 release runs on ASP.NET Core 2.1. If you have the service configured, there's a minor change you need to make for the Key Vault configuration settings. If you're installing fresh, the ARM template has already been updated.
The service leverages the built-in Key Vault configuration support. To use this, two environment
variables must be set in your launchSettings.json
and the App Service's Application Settings:
In your launchSettings.json, in the environment variables section, add two values:
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_HOSTINGSTARTUP__KEYVAULT__CONFIGURATIONENABLED": "true",
"ASPNETCORE_HOSTINGSTARTUP__KEYVAULT__CONFIGURATIONVAULT": "https://<config vault name>.vault.azure.net"
}
In the App Service's Application Settings, rename the ConfigurationKeyVaultUrl
to ASPNETCORE_HOSTINGSTARTUP__KEYVAULT__CONFIGURATIONVAULT
The client is distributed via NuGet and uses both a json config file and command line parameters. Common settings, like the client id and service url are stored in a config file, while per-file parameters and the client secret are passed in on the command line.
You'll need to create an appsettings.json
similar to the following:
{
"SignClient": {
"AzureAd": {
"AADInstance": "https://#.microsoftonline.com/",
"ClientId": "<client id your sign client app>",
"TenantId": "<guid or domain name>"
},
"Service": {
"Url": "https://<your-service>/",
"ResourceId": "<app id uri of your service>"
}
}
}
Then, somewhere in your build, you'll need to call the client tool. I use Azure Pipelines and call the following script to sign my files.
azure-pipelines.yml:
- task: DotNetCoreCLI@2
inputs:
command: custom
custom: tool
arguments: install --tool-path . SignClient
displayName: Install SignTool tool
- pwsh: |
.\SignClient "Sign" `
--baseDirectory "$(Pipeline.Workspace)\ToSign" `
--input "**/*.{appxbundle,appinstaller,zip,nupkg}" `
--config "$(Pipeline.Workspace)\SigningScripts\appsettings.json" `
--filelist "$(Pipeline.Workspace)\SigningScripts\filelist.txt" `
--user "$(SignClientUser)" `
--secret "$(SignClientSecret)" `
--name "NuGet Package Explorer" `
--description "NuGet Package Explorer" `
--descriptionUrl "https://github.com/NuGetPackageExplorer/NuGetPackageExplorer"
displayName: Authenticode Sign artifacts
The parameters to the signing client are as follows:
usage: SignClient <command> [<args>]
sign Sign a file
signing an archive type (.zip
, .nupkg
, .vsix
) will open up the archive and sign any
supported file types. It is strongly recommended to use the filter
parameter to explicitly
list the files inside the archive that should be signed. Signing is recursive; it will sign
contents of any detectected Zip
, NuPkg
or VSIX
files inside the uploaded one.
After signing contents of the archive, the archive itself is signed if supported
(currently VSIX
).
usage: SignClient sign [-c <arg>] [-i <arg>] [-b <arg>] [-o <arg>]
[-f <arg>] [-s <arg>] [-r <arg>] [-n <arg>] [-d <arg>] [-u <arg>] [-m <arg>]
-c, --config <arg> Path to config json file
-i, --input <arg> Path to input file
-b --baseDirectory <arg> Base directory for files to override the working directory
-o, --output <arg> Path to output file. May be same
as input to overwrite
-f, --filelist <arg> Path to file containing paths of
files to sign within an archive
-s, --secret <arg> Client Secret
-r --user <arg> Username
-n, --name <arg> Name of project for tracking
-d, --description <arg> Description
-u, --descriptionUrl <arg> Description Url
-m, --maxConcurrency <arg> Maximum concurrency (default is 4)
ClickOnce files can be signed with this tool, but it requires an extra step -- you must zip up the publish
directory containing the setup.exe
, foo.application
or foo.vsto
files along with the Application Files
directory.
The Application Files
must only have a single subdirectory (version you want to sign). Zip these and then rename the
extension to .clickonce
before submitting to the tool. Once done, you can extract the signed files wherever you'd like
for publication. If the name
parameter is supplied, it's used in the Mage
name to update the Product
in the manifests.
If the descriptionUrl
parameter is supplied, it's used as the supportUrl
in the manifests.
You should also use the filter
parameter with the file list to sign, something like this:
**/ProjectAddIn1.*
**/setup.exe
To get certificates into Key Vault, there are several options:
- Use the Admin UI to create a CSR and then merge the certificate.
- Upload a pfx file using the Portal, CLI, or PowerShell
- Use this GUI tool: https://github.com/microsoft/AzureKeyVaultExplorer. Before you can login, you'll need to go to the settings, put your tenant name in and change the login endpoint to
https://#.microsoftonline.com/common
. The tool makes it easy to upload and manage certificates in Key Vault.
I'm very much open to any collaboration and contributions to this tool to enable additional scenarios. Pull requests are welcome, though please open an issue to discuss first. Security reviews are also much appreciated!