1+ # This pipeline:
2+ # 1) Builds/publishes an unsigned .NET executable
3+ # 2) Optionally loads signing inputs from Azure Key Vault
4+ # 3) Ensures the Trusted Signing certificate profile exists (creates it if needed)
5+ # 4) Signs the binary with SignTool + Artifact Signing dlib
6+ # 5) Verifies and publishes the signed artifact
7+
18trigger :
29- main
310
411pool :
512 vmImage : ' windows-latest'
613
714variables :
15+ # Build settings for the demo app.
816 buildConfiguration : ' Release'
917 runtimeIdentifier : ' win-x64'
1018
11- # Update if your service connection name differs.
19+ # AzureRM service connection name in Azure DevOps.
20+ # Terraform defaults this to 'sc-artifact-signing'.
1221 azureServiceConnection : ' sc-artifact-signing'
1322
14- # Set these as pipeline variables or in a variable group.
15- # If you set keyVaultName (or Terraform provides it via variable group),
16- # the AzureKeyVault@2 task below will populate the artifactSigning* variables.
23+ # Key Vault integration:
24+ # - If keyVaultName is non-empty, AzureKeyVault@2 will pull the secrets below
25+ # and populate the corresponding pipeline variables at runtime.
26+ # - If keyVaultName is empty, you must set the artifactSigning* variables
27+ # via pipeline variables or a variable group.
1728 keyVaultName : ' '
29+
30+ # Trusted Signing inputs (usually sourced from Key Vault):
1831 artifactSigningEndpoint : ' '
1932 artifactSigningAccountName : ' '
2033 artifactSigningCertificateProfileName : ' '
34+
35+ # Identity validation is portal-only; when the profile does not exist yet,
36+ # set this (preferably as a Key Vault secret) so the pipeline can create it.
2137 artifactSigningIdentityValidationId : ' '
38+
39+ # Resource group that contains the Code Signing account.
2240 artifactSigningResourceGroupName : ' '
41+
42+ # Certificate profile type to create if missing.
43+ # This must match the Microsoft.CodeSigning API allowed values.
2344 artifactSigningCertificateProfileType : ' PublicTrust'
45+
46+ # Optional: object id of the Azure DevOps service principal.
47+ # If provided, the pipeline will attempt to ensure the signer RBAC role assignment.
48+ # (Terraform can also handle RBAC; this is a best-effort helper.)
2449 adoServicePrincipalObjectId : ' '
2550
2651steps :
52+ # Build the demo app, producing an unsigned SigningDemo.exe.
2753- task : DotNetCoreCLI@2
2854 displayName : Publish (unsigned)
2955 inputs :
3056 command : publish
3157 projects : ' **/SigningDemo.csproj'
3258 arguments : ' -c $(buildConfiguration) -r $(runtimeIdentifier) --self-contained false'
3359
60+ # Load signing inputs from Key Vault (only if keyVaultName is set).
3461- task : AzureKeyVault@2
3562 displayName : Load signing variables from Key Vault
3663 condition : and(succeeded(), ne(variables['keyVaultName'], ''))
@@ -40,6 +67,11 @@ steps:
4067 SecretsFilter : ' artifactSigningEndpoint,artifactSigningAccountName,artifactSigningCertificateProfileName,artifactSigningIdentityValidationId'
4168 RunAsPreJob : false
4269
70+ # Sign the binary.
71+ # Notes:
72+ # - If the certificate profile doesn't exist yet, this step creates it via ARM (az rest).
73+ # - To create the profile, the service connection identity typically needs permission at RG/account scope.
74+ # - To sign, the identity needs the appropriate Artifact Signing signer role.
4375- task : AzureCLI@2
4476 displayName : Sign with Azure Artifact Signing (SignTool + dlib)
4577 inputs :
@@ -74,6 +106,7 @@ steps:
74106 $profileResourceId = "/subscriptions/$subId/resourceGroups/$rg/providers/Microsoft.CodeSigning/codeSigningAccounts/$accountName/certificateProfiles/$profileName"
75107 $profileUrl = "https://management.azure.com$profileResourceId?api-version=2025-10-13"
76108
109+ # If the profile doesn't exist yet (first run), create it using the identity validation ID.
77110 Write-Host "Ensuring certificate profile exists: $profileResourceId"
78111
79112 $profileExists = $false
@@ -102,6 +135,8 @@ steps:
102135 Write-Host "Created certificate profile: $profileName"
103136 }
104137
138+ # Optional best-effort RBAC helper: assign signer role at the profile scope.
139+ # If Terraform already assigned roles, this is usually unnecessary.
105140 $spObjectId = Get-Trimmed "$(adoServicePrincipalObjectId)"
106141 if (-not [string]::IsNullOrWhiteSpace($spObjectId)) {
107142 $roleName = "Artifact Signing Certificate Profile Signer"
@@ -115,6 +150,10 @@ steps:
115150
116151 az account show --output table
117152
153+ # Download tooling via NuGet extraction:
154+ # - nuget.exe (for pulling packages)
155+ # - Microsoft.Windows.SDK.BuildTools (signtool.exe)
156+ # - Microsoft.ArtifactSigning.Client (Azure.CodeSigning.Dlib.dll)
118157 $tempRoot = Join-Path "$(Agent.TempDirectory)" "artifact-signing"
119158 New-Item -ItemType Directory -Force -Path $tempRoot | Out-Null
120159
@@ -140,6 +179,7 @@ steps:
140179 if (-not $dlib) { throw "Azure.CodeSigning.Dlib.dll not found after extracting Microsoft.ArtifactSigning.Client" }
141180
142181 $metadataPath = Join-Path $tempRoot "metadata.json"
182+ # metadata.json is consumed by the dlib and tells it which account/profile to use.
143183 $metadata = @{
144184 Endpoint = "$(artifactSigningEndpoint)"
145185 CodeSigningAccountName = "$(artifactSigningAccountName)"
@@ -160,14 +200,17 @@ steps:
160200 Write-Host "Using signtool: $($signtool.FullName)"
161201 Write-Host "Using dlib: $($dlib.FullName)"
162202
203+ # Sign in-place using the official SignTool + /dlib flow.
163204 & $signtool.FullName sign /v /debug /fd SHA256 /tr "http://timestamp.acs.microsoft.com" /td SHA256 /dlib "$($dlib.FullName)" /dmdf "$metadataPath" "$signed"
164205
206+ # Verify that Windows considers the resulting file authenticode-signed.
165207- powershell : |
166208 $ErrorActionPreference = 'Stop'
167209 $signed = "$(Build.ArtifactStagingDirectory)\SigningDemo.signed.exe"
168210 Get-AuthenticodeSignature $signed | Format-List
169211 displayName : Verify signature
170212
213+ # Publish the signed binary as a build artifact.
171214- task : PublishBuildArtifacts@1
172215 displayName : Publish signed artifact
173216 inputs :
0 commit comments