Azure Local - WDAC - Part 6 - Sign policies to protect against tampering
- Intro
- Prerequisites
- Understand the signing requirement
- Step 1 — Add boot safety options to the policy
- Step 2 — Add an UpdatePolicySigner rule
- Step 3 — Remove the unsigned policy option
- Step 4 — Convert the policy to binary
- Step 5 — Sign the binary policy
- Step 6 — Verify the signature
- Step 7 — Deploy the signed policy
- Step 8 — Validate on a single node before cluster-wide rollout
- Signing supplemental policies
- What happens if something goes wrong
- Recommendation
Intro
This article is part of a series: Navigate to series page
The earlier parts of this series covered creating, deploying, and updating supplemental WDAC policies. All of those policies were unsigned. An unsigned policy can be modified or removed by any administrator — including malware running with admin privileges.
Signed WDAC policies raise the bar significantly. Windows detects any tampering and responds with a boot failure (BSOD) rather than silently allowing a policy bypass. Once a signed policy is applied and the node reboots, it cannot be removed or overwritten without the signing certificate.
This part shows how to sign your base and supplemental policies and what you must prepare before enabling signature protection.
Warning: A misconfigured signed policy can cause a boot loop. Always validate on a single node first and keep an emergency recovery path available (e.g., BitLocker recovery key access and ability to boot from WinPE).
Prerequisites
- A code signing certificate with an RSA key of 2048, 3072, or 4096 bits. ECDSA is not supported.
- SHA-256 as the digest algorithm (SHA-384 and SHA-512 are supported on Windows 11 / Server 2022 with November 2022 CU or later).
- The certificate must be signed according to the PKCS #7 standard (RFC 5652).
signtool.exefrom the Windows SDK, accessible on the machine where you sign.- SecureBoot enabled on the Azure Local nodes (required for anti-tampering protection to take effect after reboot).
- An existing unsigned base policy and any supplemental policies you want to protect.
Understand the signing requirement
When you sign a base policy, all supplemental policies must also be signed. The base policy must explicitly list each supplemental signing certificate via a <SupplementalPolicySigner> rule.
If you sign the base without also signing the supplementals, Windows will not load the unsigned supplementals after the reboot that activates signature enforcement.
Step 1 — Add boot safety options to the policy
Before converting to a signed policy, enable two safety options that give you a recovery path if the policy causes a boot issue:
# The policy you are about to sign
$policyPath = "C:\wdac\BasePolicy.xml"
# Option 9: Show advanced boot menu (lets you boot with WDAC disabled for troubleshooting)
Set-RuleOption -FilePath $policyPath -Option 9
# Option 10: Boot audit on failure instead of hard block
Set-RuleOption -FilePath $policyPath -Option 10
Keep these options in place until you have fully validated the signed policy in production. You can remove them in a later version increment once you are confident.
Step 2 — Add an UpdatePolicySigner rule
An <UpdatePolicySigner> rule tells Windows which certificate is allowed to update or replace this signed policy. This step is mandatory. If you skip it, you will not be able to update or remove the policy after signing, and the only recovery is a WinPE/BitLocker recovery path.
$policyPath = "C:\wdac\BasePolicy.xml"
$certPath = "C:\certs\MyWdacSigningCert.cer" # exported public certificate (.cer)
# -Update allows this cert to replace the base policy
# -Supplemental allows this cert to sign supplemental policies for this base
Add-SignerRule -FilePath $policyPath -CertificatePath $certPath -Update -Supplemental
Export your signing certificate from your PKI or personal certificate store:
# Export from the personal store (if imported as .pfx)
$cert = Get-ChildItem Cert:\CurrentUser\My | Where-Object { $_.Subject -match "WdacSigning" }
Export-Certificate -Cert $cert -FilePath "C:\certs\MyWdacSigningCert.cer"
Step 3 — Remove the unsigned policy option
Remove Option 6 (Unsigned System Integrity Policy). This tells Windows that the policy must be signed from this point on:
Set-RuleOption -FilePath $policyPath -Option 6 -Delete
Step 4 — Convert the policy to binary
$policyPath = "C:\wdac\BasePolicy.xml"
$policyID = Set-CIPolicyIdInfo -FilePath $policyPath -ResetPolicyID
$policyID = $policyID.Substring(11)
$cipBinPath = "C:\wdac\$policyID.cip"
ConvertFrom-CIPolicy -XmlFilePath $policyPath -BinaryFilePath $cipBinPath
Step 5 — Sign the binary policy
Import your .pfx signing certificate into the current user’s personal store if it is not already there, then sign the binary policy using signtool.exe:
# Adjust the path to signtool.exe based on your Windows SDK installation
$signtool = "C:\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\signtool.exe"
& $signtool sign `
-v `
-n "MyWdacSigningCert" ` # Subject name of certificate in personal store
-p7 . ` # PKCS#7 output to current directory
-p7co 1.3.6.1.4.1.311.79.1 `
-fd sha256 `
$cipBinPath
This produces a file with a .p7 extension. Rename it to match the policy GUID:
Rename-Item -Path "C:\wdac\$policyID.cip.p7" -NewName "$policyID.cip"
Step 6 — Verify the signature
Before deploying, confirm the signature algorithm and chain are correct:
certutil.exe -asn "C:\wdac\$policyID.cip"
Look for sha256 in the signatureAlgorithm field. If you see sha1, the certificate or signing command needs adjustment.
Step 7 — Deploy the signed policy
Copy the signed .cip file to the Azure Local policy store and refresh:
$cipDest = "C:\Windows\System32\CodeIntegrity\CiPolicies\Active\$policyID.cip"
Copy-Item -Path "C:\wdac\$policyID.cip" -Destination $cipDest -Force
# Refresh policies without a reboot (where supported)
& C:\Windows\System32\RefreshPolicy.exe
Alternatively, use the Azure Local orchestrated cmdlet if you are deploying a supplemental policy:
Add-ASWDACSupplementalPolicy -Path "C:\wdac\SupplementalPolicy.xml"
Note: Anti-tampering protection only activates after the first reboot following deployment of the signed policy.
Step 8 — Validate on a single node before cluster-wide rollout
After deploying on one node, reboot and verify:
-
The node boots successfully.
-
WDAC policy mode is still Enforced:
Get-AsWdacPolicyMode -
The signed policy appears in the active policy list:
Get-ASLocalWDACPolicyInfo -
Applications covered by supplemental policies still run as expected.
Only proceed to the remaining nodes after confirming the above on the first node.
Signing supplemental policies
Each supplemental policy must also be signed with the same certificate (or a certificate listed in <SupplementalPolicySigner> rules of the base).
Follow the same steps 3–7 for each supplemental policy XML. The Add-SignerRule step with -Update -Supplemental is only needed on the base policy.
What happens if something goes wrong
If a signed policy causes a boot failure, recovery requires:
- Boot from WinPE or recovery environment.
- Use BitLocker recovery key to access the system volume.
- Delete or replace the
.cipfile inC:\Windows\System32\CodeIntegrity\CiPolicies\Active\.
This is why enabling Options 9 and 10 before signing is critical. With Option 9 active, you can access the advanced boot menu and select “Disable Windows Defender Application Control Policy” to get the system running while you fix the policy.
Recommendation
Treat signing as the final hardening step — do it after your unsigned policies have been stable and validated in production for at least one update cycle. Keep the signing certificate in a protected location (ideally an HSM or an offline PKI), and document the recovery procedure in your operational runbook.
Official Microsoft reference: Use signed policies to protect App Control for Business against tampering
Have feedback on this post?
Send me a message and I'll get back to you.