I have an Authenticode certificate (.pfx) which I use to sign executables.
How can I configure Team Build so that it signs every single executable (.exe, .dll, ...) automatically while building the project?
I have an Authenticode certificate (.pfx) which I use to sign executables.
How can I configure Team Build so that it signs every single executable (.exe, .dll, ...) automatically while building the project?
Here's the method we use:
Unload the WiX project and select Edit
Scroll to the bottom, where you can find <Import Project="$(WixTargetsPath)" />
Add a new line immediately above it: <Import Project="ProjectName.custom.targets" />
We use the naming convention "ProjectName.custom.targets", but the file can be named anything you want.
Create a new XML file named ProjectName.custom.Targets and place the following code into it:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!-- replace the contents of this with your private test authenticode certificate -->
<AuthenticodeCertFile Condition="'$(AuthenticodeCertFile)' == ''">$(MSBuildProjectDirectory)\AuthenticodeTest.pfx</AuthenticodeCertFile>
</PropertyGroup>
<!-- this gets the path to signtool.exe and places it in the _SignToolSdkPath property -->
<Target Name="_GetSignToolPath">
<GetFrameworkSdkPath>
<Output TaskParameter="Path" PropertyName="_SignToolSdkPath" />
</GetFrameworkSdkPath>
<PropertyGroup>
<_SignToolPath>$(_SignToolSdkPath)bin\signtool.exe</_SignToolPath>
</PropertyGroup>
</Target>
<!-- This gets a list of all of the "referenced" assembies used by the installer project -->
<!-- Unfortunately, I cheated and used an "internal" item list - you could replace this with each specific assembly but it gets complicated if your build output is redirected -->
<Target Name="_GetSourceAssembliesToSign" DependsOnTargets="ResolveReferences">
<!-- Kludge - not supposed to target internal items, but there are no other options -->
<CreateItem Include="@(_ResolvedProjectReferencePaths)">
<Output ItemName="_SourceAssemblyToSign" TaskParameter="Include" />
</CreateItem>
</Target>
<!-- This signs the assemblies in the @(_SourceAssemblyToSign) item group -->
<!-- Note that it only executes when build output is redirected ie/ on TFS Build or when OutDir is changed -->
<!-- Authenticode timestamp is optional - doesn't make sense to timestamp the test certificate -->
<Target Name="_AuthenticodeSignSourceAssemblies" AfterTargets="BeforeBuild" DependsOnTargets="_GetSignToolPath;_GetSourceAssembliesToSign" Condition="'$(AuthenticodeCertFile)' != '' and '$(OutDir)' != '$(OutputPath)'">
<Exec Command=""$(_SignToolPath)" sign /f "$(AuthenticodeCertFile)" /p "$(AuthenticodePassword)" /t $(AuthenticodeTimestamp) /v "%(_SourceAssemblyToSign.Identity)"" Condition="'$(AuthenticodeTimestamp)' != ''" />
<Exec Command=""$(_SignToolPath)" sign /f "$(AuthenticodeCertFile)" /p "$(AuthenticodePassword)" /v "%(_SourceAssemblyToSign.Identity)"" Condition="'$(AuthenticodeTimestamp)' == ''" />
</Target>
<!-- This signs the MSI file itself -->
<!-- Note that additional changes may be needed if your CAB files are separate - those would need to be signed as well -->
<!-- Note that it only executes when build output is redirected ie/ on TFS Build or when OutDir is changed -->
<Target Name="_AuthenticodeSignMsi" AfterTargets="SignMsi" DependsOnTargets="_GetSignToolPath" Condition="'$(AuthenticodeCertFile)' != '' and '$(OutDir)' != '$(OutputPath)'">
<PropertyGroup>
<_MsiFileToSign>$(TargetDir)%(CultureGroup.OutputFolder)$(TargetName)$(TargetExt) </_MsiFileToSign>
</PropertyGroup>
<Exec Command=""$(_SignToolPath)" sign /f "$(AuthenticodeCertFile)" /p "$(AuthenticodePassword)" /t $(AuthenticodeTimestamp) /v "$(_MsiFileToSign)"" Condition="'$(AuthenticodeTimestamp)' != ''" />
<Exec Command=""$(_SignToolPath)" sign /f "$(AuthenticodeCertFile)" /p "$(AuthenticodePassword)" /v "$(_MsiFileToSign)"" Condition="'$(AuthenticodeTimestamp)' == ''" />
</Target>
</Project>
Create a test authenticode certificate (we named ours AuthenticodeTest.pfx) and place it in source control - the path to it is set in the AuthenticodeCertFile property. To test it out, run msbuild at command line and change the OutDir property - ie/ msbuild Test.sln /p:OutDir=C:\Test
Some customizations will be needed if:
To run your final build select "Queue New Build" in TFS. Click "Parameters" and expand "Advanced". Under "MSBuild Arguments" add /p:AuthenticodeCertFile=ProductionCertFile.pfx /p:AuthenticodePassword=Secret
. Note that this may not be entirely secure - it could be tricky to have the build agent find the PFX file without checking it in and the password could be logged in the build output. Alternately you could create a special locked down build agent for this, or run the build locally at command line - but obviously that wouldn't be a "clean room" environment. It may be worth creating a special locked down "clean" server specifically for that purpose.
Since the code signing certificate must be installed on the build computer in order to perform signing, why not sign everything that is built on that computer every time it is built? The computer is "at risk" because it has the code signing certificate installed, so it will need to be protected in some fashion (physical security and system security). If it is protected, why not let it do the work it was intended to do, prepare the files for delivery, consistently, repeatably, every time?
Unfortunately, the answer "don't" also seems to be the standard Microsoft answer, since they seem to provide almost no support in MSBuild to loop over a list of file names, calling a program once for each file name in the list. I've found ways to pass a wildcard generated list of files to the Signtool.exe program, but it can only handle one file at a time.
I fear (for me) that it is back to writing a batch file which loops over its arguments and calls signtool for each argument. Writing batch files for the common task of signing a build output makes me think MSBuild really isn't as mature a build system as it should be. Either that, or signtool has the wrong interface. In either case, signing multiple files without enumerating the name of every file to sign appears to be a "no go" with MSBuild.
My answer builds on top of ShadowChaser's answer. the difference that my MSBuild target will sign a specified file or if none is specified, it will sign the build output of the project from which the target was invoked.
save the following in a targets file let's say signing.custom.targets. once you save it just include it in your csproj or wixproj and use the target to sign your output. this will also work on local dev machine as the sign.properties (its a hybrid dev env so we didn't want to specify cert properties twice) file does not exist on local dev box.
to sign a file from command line using this, you can
MSBuild.exe signing.custom.targets /t:SignAssembly /p:_MsiFileToSign="..\Builds\Release\Setups\yourFileToSign.msi
signing.custom.targets definition
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<CertPath Condition= "'$(CertPath)' == ''">c:\dev\sign\</CertPath>
<Config Condition="'$(Config)' == ''">Release</Config>
<MSIProductVersion Condition ="'$(MSIProductVersion)' ==''">16.3</MSIProductVersion>
<MSIBuildNumber Condition ="'$(MSIBuildNumber)' ==''">3207</MSIBuildNumber>
</PropertyGroup>
<PropertyGroup Condition="'$(OutputType)'=='Library'">
<_MsiFileToSign Condition="'$(_MsiFileToSign)' ==''" >$(OutputPath)$(AssemblyName).dll</_MsiFileToSign>
</PropertyGroup>
<PropertyGroup Condition="'$(OutputType)'=='Exe'">
<_MsiFileToSign Condition="'$(_MsiFileToSign)' ==''">$(OutputPath)$(AssemblyName).exe</_MsiFileToSign>
</PropertyGroup>
<Target Name="ReadProperties">
<ReadLinesFromFile File="$(CertPath)sign.properties">
<Output TaskParameter="Lines" PropertyName="PropsInOneLine" />
</ReadLinesFromFile>
</Target>
<Target Name="CreateProperties" DependsOnTargets="ReadProperties">
<PropertyGroup>
<SignToolPath>$(CertPath)signtool.exe</SignToolPath>
<AuthenticodeCertFile Condition="'$(PropsInOneLine)' != ''">$(CertPath)$([System.String]::Copy($(PropsInOneLine)).Split(';')[0].Split('=')[1])</AuthenticodeCertFile>
<AuthenticodePassword Condition="'$(PropsInOneLine)' != ''">$([System.String]::Copy($(PropsInOneLine)).Split(';')[2].Split('=')[1])</AuthenticodePassword>
<AuthenticodeTimestamp Condition="'$(PropsInOneLine)' != ''">$([System.String]::Copy($(PropsInOneLine)).Split(';')[4].Split('=')[1])</AuthenticodeTimestamp>
</PropertyGroup>
</Target>
<Target Name="SignAssembly"
DependsOnTargets="CreateProperties" >
<Message Text=" File Name to sign= $(_MsiFileToSign)" />
<Exec Command=""$(SignToolPath)" sign /f "$(AuthenticodeCertFile)" /p "$(AuthenticodePassword)" /t $(AuthenticodeTimestamp) /v "$(_MsiFileToSign)"" Condition="'$(PropsInOneLine)' != ''" />
</Target>
<Target Name="SignMsi"
DependsOnTargets="CreateProperties" >
<PropertyGroup>
<_MsiFileToSign>$(TargetPath)</_MsiFileToSign>
</PropertyGroup>
<Message Text=" File Name to sign= $(_MsiFileToSign)" />
<Exec Command=""$(SignToolPath)" sign /f "$(AuthenticodeCertFile)" /p "$(AuthenticodePassword)" /t $(AuthenticodeTimestamp) /v "$(_MsiFileToSign)"" Condition="'$(PropsInOneLine)' != ''" />
</Target>
</Project>
including it in your csproj
<Import Project="$(SolutionDir)signing.custom.targets" />
<Target Name="AfterBuild" DependsOnTargets="SignAssembly">
</Target>
to include it in your wixproj
<Import Project="$(SolutionDir)signing.custom.targets" />
<Target Name="AfterBuild" DependsOnTargets="SignAssembly">
</Target>
Don't.
You do not want to automatically sign builds. Most builds don't need signing anyway; they're only used for automating tests. Some builds may be handed to your in-house testers. But only builds that you actually release outside your organization need Authenticode signatures.
In that case, you should have a manual verification step after signing anyway. So, signing manually doesn't insert an extra manual step in the release process, and automating it saves very little time. In exchange, there will be far fewer signed files floating around in your organization, and you can make much stronger guarantees about the files that are.
© 2022 - 2024 — McMap. All rights reserved.