Windows Code Signing with Azure Trusted Signing: End-to-End Guide
A complete guide to signing Windows desktop apps with Azure Trusted Signing — covering SmartScreen reputation, the dotnet sign CLI, Tauri signCommand integration, and CI/CD automation with GitHub Actions.
This is a deep-dive companion to our cross-platform code signing guide. If you need the full picture including macOS, start there.
Shipping unsigned Windows software is a non-starter. SmartScreen warnings scare users away before they even try your app. Traditional code signing certificates are expensive, require identity verification, and involve managing private keys. Azure Trusted Signing changes the economics entirely — the certificate lives in Azure’s HSM, signing happens via API calls, and you never handle private keys.
We have shipped multiple signed desktop apps using this approach. This guide covers every step, including the pitfalls that cost us hours.
Why Azure Trusted Signing
Traditional code signing requires purchasing a certificate ($200-500/year for OV, $400-700/year for EV), completing identity verification with a certificate authority, storing the private key securely (often on a USB hardware token), and managing certificate renewals.
Azure Trusted Signing costs $9.99/month, handles identity verification through Azure’s existing identity platform, stores keys in a FIPS 140-2 Level 3 HSM, and issues short-lived certificates automatically. The signing itself is fast — under two seconds per file.
The main limitation: SmartScreen reputation starts from zero with a new Trusted Signing certificate, just like any new OV certificate. EV certificates historically received immediate SmartScreen trust, though Microsoft has been moving away from that distinction. In practice, reputation builds within a few days of real user installs.
Prerequisites
You need:
- An Azure account with an active subscription
- An Azure Trusted Signing account (create in Azure Portal → search “Trusted Signing”)
- A certificate profile configured in your Trusted Signing account
- An App Registration (service principal) with the Trusted Signing Certificate Profile Signer role
- The service principal’s tenant ID, client ID, and client secret
The dotnet sign Tool
The official CLI for signing with Azure Trusted Signing is the dotnet sign tool. As of early 2026, it requires the --prerelease flag:
dotnet tool install -g --prerelease sign
Basic usage:
sign code artifact-signing \
--timestamp-url http://timestamp.acs.microsoft.com \
--artifact-signing-endpoint https://eus.codesigning.azure.net/ \
--artifact-signing-account MySigningAccount \
--artifact-signing-certificate-profile MyProfile \
MyApp.exe
Authentication uses the DefaultAzureCredential chain. In CI, set these environment variables:
AZURE_TENANT_IDAZURE_CLIENT_IDAZURE_CLIENT_SECRET
Warning: The NuGet package is called
sign, notAzure.CodeSigning. TheAzure.CodeSigningpackage was deprecated and removed from NuGet feeds. If you seeazure.codesigning is not found in NuGet feeds, you are using the old package name.
Signing Tauri Apps with signCommand
Tauri v2 supports a signCommand configuration that runs a command to sign each binary during the build process. This is the recommended approach because Tauri signs the .exe binary before packaging it into the NSIS installer, then signs the installer itself — both get signed in a single build step.
Here is the complete GitHub Actions workflow for a Tauri app:
- name: Install dotnet sign tool
shell: pwsh
run: |
dotnet tool install -g --prerelease sign
$toolDir = Join-Path $env:USERPROFILE '.dotnet\tools'
echo $toolDir >> $env:GITHUB_PATH
- name: Prepare signing config
shell: pwsh
env:
AZURE_SIGNING_ENDPOINT: ${{ secrets.AZURE_SIGNING_ENDPOINT }}
AZURE_SIGNING_ACCOUNT: ${{ secrets.AZURE_SIGNING_ACCOUNT }}
AZURE_SIGNING_PROFILE: ${{ secrets.AZURE_SIGNING_PROFILE }}
run: |
$signExe = (Get-Command sign).Source -replace '\\', '/'
$signCmd = "$signExe code artifact-signing --timestamp-url http://timestamp.acs.microsoft.com --artifact-signing-endpoint $env:AZURE_SIGNING_ENDPOINT --artifact-signing-account $env:AZURE_SIGNING_ACCOUNT --artifact-signing-certificate-profile $env:AZURE_SIGNING_PROFILE %1"
$config = @{ bundle = @{ windows = @{ signCommand = $signCmd } } } | ConvertTo-Json -Depth 3 -Compress
Set-Content "app/signing-config.json" $config
- name: Build Tauri app (Windows, signed)
working-directory: app
env:
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
run: npx tauri build --target x86_64-pc-windows-msvc --config signing-config.json
The %1 placeholder is replaced by Tauri with the path of each file to sign. The --config flag merges the signing configuration with your tauri.conf.json at build time, so your main config stays clean.
Pitfalls We Encountered
Forward slashes in signCommand paths. Tauri executes signCommand as a raw process, not through a shell. Backslashes in Windows paths cause parsing issues. Converting all paths to forward slashes (-replace '\\', '/') resolves this. Windows accepts forward slashes in file paths.
Resolving the sign tool path. After dotnet tool install -g, the sign executable is in ~/.dotnet/tools/. Even after adding it to GITHUB_PATH, Tauri’s signCommand subprocess may not inherit the updated PATH. Using (Get-Command sign).Source to resolve the absolute path ensures it works.
PowerShell JSON generation. Generating the signCommand JSON config via bash heredocs or echo statements led to quoting nightmares on Windows. Using PowerShell’s ConvertTo-Json produces clean, properly escaped JSON every time.
The deprecated Azure.CodeSigning package. Earlier documentation and blog posts reference dotnet tool install --global Azure.CodeSigning. This package has been removed from NuGet. The correct package is sign with the --prerelease flag.
Timestamp server reliability. The Microsoft timestamp server (http://timestamp.acs.microsoft.com) is generally reliable. If you experience intermittent failures, the DigiCert timestamp server (http://timestamp.digicert.com) is a solid alternative.
Verifying Signatures
After building, verify the signature:
Using PowerShell:
Get-AuthenticodeSignature "KeyQ Tempo_1.0.0_x64-setup.exe" | Format-List
Using Properties dialog:
Right-click the .exe → Properties → Digital Signatures tab. You should see your organization name with a valid timestamp.
Both the installer and the installed executable should show digital signatures when using the signCommand approach. If you only use the azure/trusted-signing-action post-build, only the installer is signed — the binary inside it is not.
Cost
Azure Trusted Signing costs $9.99/month for the Basic tier, which includes unlimited signing operations. There are no per-signature fees. Compare this to traditional OV certificates ($200-500/year) or EV certificates ($400-700/year) from providers like DigiCert or Sectigo.
Secrets Reference
Store these as GitHub Actions secrets:
| Secret | Description |
|---|---|
AZURE_TENANT_ID | Azure AD tenant ID |
AZURE_CLIENT_ID | App Registration (service principal) client ID |
AZURE_CLIENT_SECRET | App Registration client secret |
AZURE_SIGNING_ENDPOINT | Regional endpoint (e.g., https://eus.codesigning.azure.net/) |
AZURE_SIGNING_ACCOUNT | Trusted Signing account name |
AZURE_SIGNING_PROFILE | Certificate profile name |
Endpoint Regions
Use the endpoint that matches where you created your Trusted Signing account:
| Region | Endpoint |
|---|---|
| East US | https://eus.codesigning.azure.net |
| West US 2 | https://wus2.codesigning.azure.net |
| North Europe | https://neu.codesigning.azure.net |
| West Europe | https://weu.codesigning.azure.net |
A region mismatch causes a 403 Forbidden error during signing.
Setting up code signing for the first time can be surprisingly frustrating. If you want to skip the trial-and-error, we have done this across multiple apps and can help you get a working pipeline in hours, not days. Get in touch.