Some Definitions
Digital signature: A digital signature is used to help authenticate the identity of the creator of digital information — such as documents, e-mail messages, and macros — by using cryptographic algorithms.
What does it accomplish?
Digital signatures help establish the following authentication measures:
- Authenticity: The digital signature helps ensure that the signer is whom he or she claims to be. This helps prevent others from pretending to be the originator of a particular document (the equivalent of forgery on a printed document).
- Integrity: The digital signature helps ensure that the content has not been changed or tampered with since it was digitally signed. This helps prevent documents from being intercepted and changed without knowledge of the originator of the document.
- Non-repudiation: The digital signature helps prove to all parties the origin of the signed content. "Repudiation" refers to the act of a signer's denying any association with the signed content. This helps prove that the originator of the document is the true originator and not someone else, regardless of the claims of the signer. A signer cannot repudiate the signature on that document without repudiating his or her digital key, and therefore other documents signed with that key.
ClickOnce: ClickOnce is a Microsoft technology that enables the user to install and run a Windows-based smart client application by clicking a link in a web page. ClickOnce is a component of Microsoft .NET Framework 2.0 and later, and supports deploying applications made with Windows Forms or Windows Presentation Foundation. It is similar to Java Web Start for the Java Platform or Zero Install for Linux.
Scenario
We have a security policy enforced where dlls can only be downloaded from a url if they are originating from a trusted publisher, i.e. they are digitally signed.
What is the issue?
There were two issues that we came across:
1. Dlls are not digitally signed as part of the ClickOnce process.
2. Even if you sign dlls as part of the process, ClickOnce manifest that store "hash value" of the dlls for validation, is no more valid as that "hash value" for each dll changes.
3. ClickOnce changes the extension of each file in the package to ".deploy", e.g. if there is a file called SomeFile.dll, it changes to SameFile.dll.deploy
3. ClickOnce changes the extension of each file in the package to ".deploy", e.g. if there is a file called SomeFile.dll, it changes to SameFile.dll.deploy
Steps to resolve the issue?
To resolve this issue, we have to follow the following steps:
1. Call "Publish" target on the client application project as part of the build pipeline.
2. In "AfterPublish" task (when the manifest and files are genterted for clickonce package) we follow rest of the steps.
3. Rename "*.dll.deploy" to "*.dll" (similarly files with other extensions)
4. Sign the dlls/exes using SignFile task
5. Update "hash values" for dlls which is stored in application manifest file (having extension ".manifest") using mage.exe tool. (You can have this mage tool the part of your source code)
6. Update deployment manifest (having extension ".application") which stores sign value of the application manifest.
7. Rename "*.dll" back to "*.dl.deploy" (files renamed in step 3)
1. Call "Publish" target on the client application project as part of the build pipeline.
2. In "AfterPublish" task (when the manifest and files are genterted for clickonce package) we follow rest of the steps.
3. Rename "*.dll.deploy" to "*.dll" (similarly files with other extensions)
4. Sign the dlls/exes using SignFile task
5. Update "hash values" for dlls which is stored in application manifest file (having extension ".manifest") using mage.exe tool. (You can have this mage tool the part of your source code)
6. Update deployment manifest (having extension ".application") which stores sign value of the application manifest.
7. Rename "*.dll" back to "*.dl.deploy" (files renamed in step 3)
Code and Steps for implementing the solution
Here is the MSbuild code that goes in the AfterPublish target (Please update the values as directed in the code):
<PropertyGroup>
<!-- $(CertificateThumbprint) is the thumbrint of the
certificate to sign the dlls -->
<CertificateThumbprint>xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx
xx</CertificateThumbprint>
<!-- $(CertificateHash) is the thumbrint of the
certificate to sign the manifest files -->
<CertificateHash>"xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx"</CertificateHash>
<!-- $(PublisherName) put publisher name for this
property -->
<PublisherName>Your Publisher Name</PublisherName>
<!-- $(MageCurrentPath) is the absolute path to
mage.exe -->
<MageCurrentPath>"PathToUpdate\mage.exe"</MageCurrentPath>
<!-- $(PublishDir) is the publish folder while buliding
the application - please update this -->
<PublishDir>Publish
Directory for the package</PublishDir>
<TimestampUrl>http://timestamp.verisign.com/scripts/timstamp.dll</TimestampUrl>
</PropertyGroup>
<Target Name="SignClickOnceAssemblies" AfterTargets="AfterPublish">
<ItemGroup>
<ApplicationManifest Include="$(PublishDir)**\*.manifest;"/>
</ItemGroup>
<ItemGroup>
<DeplymentManifest Include="$(PublishDir)**\*.application;"/>
</ItemGroup>
<PropertyGroup>
<AppManifestFullPath>%(ApplicationManifest.FullPath)</AppManifestFullPath>
<AppDir>%(ApplicationManifest.RootDir)%(ApplicationManifest.Directory)</AppDir>
<DeploymentManifestFullPath>%(DeplymentManifest.FullPath)</DeploymentManifestFullPath>
<DeplyDir>%(DeplymentManifest.RootDir)%(DeplymentManifest.Directory)</DeplyDir>
</PropertyGroup>
<Message Text="AppManifestFullPath :
$(AppManifestFullPath)" />
<Message Text="AppDir : $(AppDir)" />
<Message Text="MageCurrentPath : $(MageCurrentPath)" />
<Message Text="DeploymentManifestFullPath :
$(DeploymentManifestFullPath)" />
<!-- Application Manifest -->
<ItemGroup>
<FilesToCopy Include="$(AppDir)**\*.*;" Exclude="$(AppManifestFullPath)"/>
</ItemGroup>
<Copy SourceFiles="@(FilesToCopy)"
DestinationFiles="%(FilesToCopy.RootDir)%(FilesToCopy.Directory)%(FilesToCopy.Filename)"/>
<Delete Files="@(FilesToCopy)" />
<ItemGroup>
<FilesToSign Include="$(PublishDir)**\*.dll;$(PublishDir)\$(ProjectName).exe;"/>
</ItemGroup>
<Message Text="FilesToSign : @(FilesToSign)" />
<SignFile
CertificateThumbprint="$(CertificateThumbprint)"
SigningTarget="%(FilesToSign.FullPath)"
TargetFrameworkVersion="v4.5"
TimestampUrl="$(TimestampUrl)" />
<Exec Command='$(MageCurrentPath) -Update
"$(AppManifestFullPath)" -CertHash $(CertificateHash) -Algorithm
sha256RSA -TimestampUri $(TimestampUrl)' />
<ItemGroup>
<FilesToCopyDeploy Include="$(AppDir)**\*.*;" Exclude="$(AppManifestFullPath)"/>
</ItemGroup>
<Copy SourceFiles="@(FilesToCopyDeploy)"
DestinationFiles="%(FilesToCopyDeploy.RootDir)%(FilesToCopyDeploy.Directory)%(FilesToCopyDeploy.Filename)%(FilesToCopyDeploy.Extension).deploy"/>
<Delete Files="@(FilesToCopyDeploy)" />
<!-- Deployment Manifest -->
<Exec Command='$(MageCurrentPath) -Update
"$(DeploymentManifestFullPath)" -AppManifest
"$(AppManifestFullPath)" -Publisher "$(PublisherName)"
-CertHash $(CertificateHash) -Algorithm sha256RSA -TimestampUri
$(TimestampUrl)' />
</Target>