Introduction
Recently, I was developing a WPF desktop application. This application was to be available for download from a website so we needed an installer to install this app on customer computers. The app and the installer had to be code signed to avoid being blocked by SmartScreen and antivirus programs.
I decided to use ClickOnce technology because it seemed like the easiest approach to generate installer and it provides automatic updates. Unfortunately, it soon turned out that code signing a ClickOnce deployment is quite a difficult task. There are some tips on the Internet about how to sign a ClickOnce deployment, but they do not cover the whole subject so I decided to write how I solved this problem by myself.
Background
The ClickOnce deployment consists of four parts:
- The application files
- Application manifest
- Deployment manifest
- Boostrapper file (setup.exe)
It's important that these four elements have to be signed separately. Many developers are only signing application files or only manifests. It can lead to deployment errors and the SmartScreen filter will still block the improperly signed application.
Signing ClickOnce Deployment in Visual Studio
First of all, do not use this:
These options should theoretically sign your ClickOnce manifest automatically but they don't allow you to specify command line arguments for signtool so they just don't work with some electronic signatures. What you have to do is to manually edit your project file and add some deployment targets.
1. Sign the application file
Right click your project and select Unload project. Then right click on the unloaded project and select "Edit <projectname>.csproj". The code editor appears.
Scroll to the bottom and add a new target just before the last closing </Project>
tag:
<Target Name="AfterCompile" Condition="
'$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<Exec Command=""C:\Program Files (x86)\Microsoft SDKs\ClickOnce\SignTool\signtool.exe"
sign /n "InsertNameHere" /t http://yourtimestampuri /fd sha1
/v "$(ProjectDir)obj\$(ConfigurationName)\$(TargetFileName)"" />
</Target>
Of course, you have to replace "C:\Program Files (x86)\Microsoft SDKs\ClickOnce\SignTool\signtool.exe" with the correct signtool path in your OS and provide valid command line parameters for signtool. Usually electronic signature resellers provide tutorials on how to properly use signtool.
The above code snippet signs the application file. Now you have to sign two manifests and the boostrapper file.
2. Sign the application manifest
Add a second target to .csproj file:
<Target Name="SignManifest" AfterTargets="_DeploymentSignClickOnceDeployment"
Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
</Target>
Now we will add four commands to this target tag. First of all, we have to sign the application manifest:
<Exec Command=""C:\Program Files
(x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.2 Tools\mage.exe"
-Sign "$(_DeploymentApplicationDir)$(_DeploymentTargetApplicationManifestFileName)"
-CertHash INSERTVALIDHASHHERE -TimeStampUri http://yourtimestampuri" />
Mage.exe is a tool for editing and signing ClickOnce manifests. You will have to find it on your system and provide a valid path to that file.
3. Update deployment manifest to reference the signed version of application manifest
The above command signs the application manifest. You now have a signed application manifest and unsigned deployment manifest. There's no use to sign the deployment manifest now because it still references the unsigned version of the application manifest. You have to update the generated deployment manifest to reference the signed version of the application manifest:
<Exec Command=""C:\Program Files
(x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.2 Tools\mage.exe"
-Update "$(PublishDir)$(TargetDeployManifestFileName)"
-AppManifest "$(_DeploymentApplicationDir)$(_DeploymentTargetApplicationManifestFileName)"" />
4. Sign the deployment manifest
The deployment manifest now references the correct application manifest so we can sign it now:
<Exec Command=""C:\Program Files
(x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.2 Tools\mage.exe"
-Sign "$(PublishDir)$(TargetDeployManifestFileName)"
-CertHash INSERTVALIDHASHHERE -TimeStampUri http://yourtimestampuri" />
5. Sign the bootstrapper file
We have both manifests signed. Now we just have to sign the setup.exe file:
<Exec Command=""C:\Program Files
(x86)\Microsoft SDKs\ClickOnce\SignTool\signtool.exe"
sign /n "CertificateName" /t http://yourtimestampuri /fd sha1
/v "$(PublishDir)\setup.exe"" />
That's all. The all four elements of the ClickOnce deployment are now properly signed.
Summary
This is the full code that you have to insert to .csproj file:
<Target Name="AfterCompile" Condition="
'$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<Exec Command=""C:\Program Files (x86)\Microsoft SDKs\ClickOnce\SignTool\signtool.exe"
sign /n "InsertNameHere" /t http://yourtimestampuri /fd sha1
/v "$(ProjectDir)obj\$(ConfigurationName)\$(TargetFileName)"" />
</Target>
<Target Name="SignManifest" AfterTargets="_DeploymentSignClickOnceDeployment"
Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<Exec Command=""C:\Program Files
(x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.2 Tools\mage.exe"
-Sign "$(_DeploymentApplicationDir)$(_DeploymentTargetApplicationManifestFileName)"
-CertHash INSERTVALIDHASHHERE -TimeStampUri http://yourtimestampuri" />
<Exec Command=""C:\Program Files
(x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.2 Tools\mage.exe"
-Update "$(PublishDir)$(TargetDeployManifestFileName)"
-AppManifest "$(_DeploymentApplicationDir)$
(_DeploymentTargetApplicationManifestFileName)"" />
<Exec Command=""C:\Program Files
(x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.2 Tools\mage.exe"
-Sign "$(PublishDir)$(TargetDeployManifestFileName)"
-CertHash INSERTVALIDHASHHERE -TimeStampUri http://yourtimestampuri" />
<Exec Command=""C:\Program Files
(x86)\Microsoft SDKs\ClickOnce\SignTool\signtool.exe"
sign /n "CertificateName" /t http://yourtimestampuri /fd sha1
/v "$(PublishDir)\setup.exe"" />
</Target>
Points of Interest
Electronic signature resellers offer you two types of code signing certificates: standard code signing and extended validation code signing. The second type instantly removes SmartScreen warning when customers try to install your application. The first type of certificate removes SmartScreen after some time when your application gathers reputation points. In my opinion, Extended Validation certificates are not worth buying. We use Standard certificate and the SmartScreen filter message disappeared after about two weeks since the product release. We haven't seen a SmartScreen message since then.