How to Sign Windows Installers in CI/CD without Windows: Azure Artifact Signing & Jsign
Learn how to sign Windows installers in any CI/CD pipeline using Azure Artifact Signing and Jsign, including endpoint setup and Jenkins integration.
Code signing is essential if you want Windows installers and executables (.exe, .msi) to avoid Microsoft Defender SmartScreen warnings. Traditionally this requires managing .pfx files, hardware tokens, or always-on Windows build agents.
This guide shows how to use Azure Artifact Signing (formerly Azure Trusted Signing) with the cross-platform tool Jsign to sign installers from any CI/CD runner (Linux, macOS, or Windows).
Architecture at a glance
Instead of storing a long-lived certificate on the build server, the pipeline exchanges Azure Entra ID credentials for a short-lived access token. Jsign sends the binary’s hash to Azure, where Microsoft’s cloud HSMs sign it securely and return the signature.
Step 1: Create the Azure Artifact Signing resources
- In the Azure Portal search bar, type Artifact Signing Accounts (or Trusted Signing Accounts) and select it.
- Click + Create (or + New).
- Select your Subscription and a Resource Group.
- Enter an Account Name (for example
your-global-signing-account). - Choose a Location.
Note: If you choose Europe West, your endpoint prefix will be
weu. This determines your API URL (for examplehttps://weu.codesigning.azure.net).
- Click Review + create, then Create.
Step 2: Identity validation & certificate profiles
Microsoft requires identity verification (Individual or Organization) for publicly trusted certificates. At the time of writing, identity verification is only available for developers in the US and Canada.
2.1 Start identity validation
- Open your Artifact Signing Account.
- In the left-hand menu under Objects, click Identity validations.
- Click + New Identity.
- Choose Organization (requires company docs, domain verification and more) or Individual (personal ID verification via partner).
- Fill the forms exactly as on your legal documents and submit.
The validation status will show as Pending and may take hours or days to complete.
2.2 Create certificate profiles
You can create two profiles: one production profile and one test profile for immediate pipeline testing.
- In the Artifact Signing Account, click Certificate profiles.
- Click + Create.
Create the production profile:
- Profile name:
prod-signing-profile - Profile type: Public Trust
- Identity validation: select the identity from step 2.1 (works fully when the verification status is Completed).
Create a test profile for immediate testing:
- Profile name:
test-signing-profile - Profile type: Public Trust Test (active instantly; requires no identity validation).
Step 3: Configure Azure Entra ID (App registration)
- Go to Microsoft Entra ID → App registrations → + New registration.
- Name it
cicd-artifact-signer. - Supported account types: Accounts in this organizational directory only (Single tenant).
- Click Register.
- Copy and save these values from the Overview screen:
- Application (client) ID
- Directory (tenant) ID
- In Certificates & secrets → Client secrets → + New client secret, create a secret and immediately copy the Secret Value. You will not be able to see it again.
Step 4: Authorize the App registration (the IAM trap)
You must give the App Registration permission to use the signing account. Missing this step commonly causes HTTP 403 Forbidden errors.
Because the Portal’s IAM UI sometimes fails to find App registrations by name, you can use the App’s Service Principal ID which can be found in the App registration’s overview.
Step 5: Integrate with your CI/CD pipeline (Jenkinsfile example)
Use Jsign (pure Java) to request a cloud signature. Add the Client Secret to Jenkins Credentials as a Secret Text named azure-signing-secret, then include a signing stage in your Jenkinsfile:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
pipeline {
agent { label 'linux' }
environment {
AZURE_TENANT_ID = 'your-tenant-id-here'
AZURE_CLIENT_ID = 'your-client-id-here'
}
stages {
stage('Code Signing') {
steps {
withCredentials([string(credentialsId: 'azure-signing-secret', variable: 'AZURE_CLIENT_SECRET')]) {
sh '''
# 1. Fetch Jsign if not present
if [ ! -f jsign.jar ]; then
wget -O jsign.jar https://github.com/ebourg/jsign/releases/latest/download/jsign.jar
fi
# 2. Exchange client secret for short-lived access token
AZURE_ACCESS_TOKEN=$(curl -s -X POST "https://login.microsoftonline.com/${AZURE_TENANT_ID}/oauth2/v2.0/token" \
-d "client_id=${AZURE_CLIENT_ID}" \
-d "scope=https://codesigning.azure.net/.default" \
-d "client_secret=${AZURE_CLIENT_SECRET}" \
-d "grant_type=client_credentials" | jq -r .access_token)
if [ -z "$AZURE_ACCESS_TOKEN" ] || [ "$AZURE_ACCESS_TOKEN" == "null" ]; then
echo "ERROR: Failed to fetch Access Token from Azure Entra ID!"
exit 1
fi
# 3. Request the cloud signature via Jsign
# NOTE: Use the region-specific endpoint (e.g., swn) and NO trailing slash!
java -jar jsign.jar \
--storetype TRUSTEDSIGNING \
--keystore https://weu.codesigning.azure.net \
--storepass "$AZURE_ACCESS_TOKEN" \
--alias "your-global-signing-account/test-signing-profile" \
--tsaurl http://timestamp.acs.microsoft.com/ \
--tsmode RFC3161 \
"your-application-setup.exe"
'''
}
}
}
}
}
Crucial troubleshooting tips
- The Trailing Slash bug (HTTP 404): Jsign concatenates URLs. If
--keystoreends with a slash (e.g.,https://swn.codesigning.azure.net/) Jsign can form a broken path. Usehttps://weu.codesigning.azure.net(no trailing slash). - Region prefix: the keystore hostname depends on the resource location:
-
swn.codesigning.azure.net→ Switzerland North -
weu.codesigning.azure.net→ West Europe -
eus.codesigning.azure.net→ East US
-
- Identity delay (HTTP 403): if the pipeline works with the
Public Trust Testprofile but fails withPublic Trust, your identity validation is likely still pending. Wait until validation shows Completed.
Final thoughts
Using Azure Artifact Signing with Jsign lets you avoid storing private signing keys on build agents and keeps your Windows code signing pipeline cloud-native. Start with the Public Trust Test profile to validate your setup quickly, then switch to the production profile once your identity validation is complete.
With the right App registration, role assignment, and region-specific endpoint, this approach makes secure Windows installer signing possible from Linux or macOS CI/CD runners without requiring a dedicated Windows signing machine.
