我有一个Authenticode证书(.pfx),用于签署可执行文件。
如何配置Team Build以便在构建项目时自动签署每个可执行文件(.exe,.dll,...)?
答案 0 :(得分:16)
以下是我们使用的方法:
卸载WiX项目并选择编辑
滚动到底部,您可以在其中找到<Import Project="$(WixTargetsPath)" />
在其上方添加一个新行:<Import Project="ProjectName.custom.targets" />
我们使用命名约定“ProjectName.custom.targets”,但文件可以命名为任何名称。
创建一个名为ProjectName.custom.Targets的新XML文件,并将以下代码放入其中:
<?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>
创建测试authenticode证书(我们将其命名为AuthenticodeTest.pfx)并将其放在源代码控制中 - 在AuthenticodeCertFile属性中设置它的路径。要测试它,在命令行运行msbuild并更改OutDir属性 - 即/ msbuild Test.sln / p:OutDir = C:\ Test
如果符合以下条件,则需要进行一些自定义:
要运行最终构建,请在TFS中选择“Queue New Build”。单击“参数”,然后展开“高级”。在“MSBuild Arguments”下添加/p:AuthenticodeCertFile=ProductionCertFile.pfx /p:AuthenticodePassword=Secret
。请注意,这可能不是完全安全的 - 让构建代理在不检入PFX文件的情况下查找PFX文件并且可以在构建输出中记录密码可能会很棘手。或者,您可以为此创建一个特殊的锁定构建代理,或者在命令行本地运行构建 - 但显然这不是一个“干净的房间”环境。可能值得专门为此目的创建一个特殊的锁定“干净”服务器。
答案 1 :(得分:3)
我不同意。由于代码签名证书必须安装在构建计算机上才能执行签名,为什么不在每次构建时在该计算机上构建所有内容?计算机“存在风险”,因为它安装了代码签名证书,因此需要以某种方式保护计算机(物理安全性和系统安全性)。如果它受到保护,为什么不让它做它想要做的工作,每次都一致,可重复地准备文件以便交付?
不幸的是,答案“不”似乎也是微软的标准答案,因为它们似乎几乎没有在MSBuild中提供循环遍历文件名列表,为每个文件名调用一次程序名单。我已经找到了将通配符生成的文件列表传递给Signtool.exe程序的方法,但它一次只能处理一个文件。
我担心(对我而言)它正在回写一个批处理文件,它循环遍历其参数并为每个参数调用signtool。为编写构建输出的常见任务编写批处理文件让我觉得MSBuild确实不像它应该的那样成熟。要么是,要么signtool有错误的界面。在任何一种情况下,签署多个文件而不枚举要签名的每个文件的名称对于MSBuild来说似乎是“不行”。
答案 2 :(得分:2)
我的回答建立在ShadowChaser的答案之上。我的MSBuild目标将签署指定文件的区别,如果没有指定,它将签署调用目标的项目的构建输出。
将以下内容保存在目标文件中,让我们说sign.custom.targets。保存后,只需将其包含在csproj或wixproj中,然后使用目标对输出进行签名。这也适用于本地开发机器上的sign.properties(它是一个混合开发环境,所以我们不想指定两次的cert属性)文件在本地开发盒上不存在。
使用此命令从命令行签名,您可以
MSBuild.exe signing.custom.targets /t:SignAssembly /p:_MsiFileToSign="..\Builds\Release\Setups\yourFileToSign.msi
signing.custom.targets定义
<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>
将其包含在您的csproj
中<Import Project="$(SolutionDir)signing.custom.targets" />
<Target Name="AfterBuild" DependsOnTargets="SignAssembly">
</Target>
将其包含在您的wixproj
中<Import Project="$(SolutionDir)signing.custom.targets" />
<Target Name="AfterBuild" DependsOnTargets="SignAssembly">
</Target>
答案 3 :(得分:-5)
别。
您不想自动签署版本。大多数构建都不需要签名;它们仅用于自动化测试。有些构建可能会交给您的内部测试人员。但是,只有您实际在组织外部发布的构建才需要Authenticode签名。
在这种情况下,您应该在签名后进行手动验证步骤。因此,手动签名不会在发布过程中插入额外的手动步骤,并且自动化可以节省很少的时间。作为交换,在您的组织中浮动的签名文件将会少得多,您可以对这些文件做出更强有力的保证。