通过MSBuild使用Authenticode证书对每个可执行文件进行签名

时间:2009-08-28 13:44:32

标签: msbuild certificate team-build authenticode

我有一个Authenticode证书(.pfx),用于签署可执行文件。

如何配置Team Build以便在构建项目时自动签署每个可执行文件(.exe,.dll,...)?

4 个答案:

答案 0 :(得分:16)

以下是我们使用的方法:

  1. 卸载WiX项目并选择编辑

  2. 滚动到底部,您可以在其中找到<Import Project="$(WixTargetsPath)" />

  3. 在其上方添加一个新行:<Import Project="ProjectName.custom.targets" /> 我们使用命名约定“ProjectName.custom.targets”,但文件可以命名为任何名称。

  4. 创建一个名为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="&quot;$(_SignToolPath)&quot; sign /f &quot;$(AuthenticodeCertFile)&quot; /p &quot;$(AuthenticodePassword)&quot; /t $(AuthenticodeTimestamp) /v &quot;%(_SourceAssemblyToSign.Identity)&quot;" Condition="'$(AuthenticodeTimestamp)' != ''" />
        <Exec Command="&quot;$(_SignToolPath)&quot; sign /f &quot;$(AuthenticodeCertFile)&quot; /p &quot;$(AuthenticodePassword)&quot; /v &quot;%(_SourceAssemblyToSign.Identity)&quot;" 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="&quot;$(_SignToolPath)&quot; sign /f &quot;$(AuthenticodeCertFile)&quot; /p &quot;$(AuthenticodePassword)&quot; /t $(AuthenticodeTimestamp) /v &quot;$(_MsiFileToSign)&quot;" Condition="'$(AuthenticodeTimestamp)' != ''" />
        <Exec Command="&quot;$(_SignToolPath)&quot; sign /f &quot;$(AuthenticodeCertFile)&quot; /p &quot;$(AuthenticodePassword)&quot; /v &quot;$(_MsiFileToSign)&quot;" Condition="'$(AuthenticodeTimestamp)' == ''" />
      </Target>
    </Project>
    
  5. 创建测试authenticode证书(我们将其命名为AuthenticodeTest.pfx)并将其放在源代码控制中 - 在AuthenticodeCertFile属性中设置它的路径。要测试它,在命令行运行msbuild并更改OutDir属性 - 即/ msbuild Test.sln / p:OutDir = C:\ Test

    如果符合以下条件,则需要进行一些自定义:

    • 如果您不想使用“私人”项目组(我被骗)
    • 如果您不使用WiX项目参考
    • 如果您的cab文件与MSI分开,则还需要签名

    要运行最终构建,请在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="&quot;$(SignToolPath)&quot; sign /f &quot;$(AuthenticodeCertFile)&quot; /p &quot;$(AuthenticodePassword)&quot; /t $(AuthenticodeTimestamp) /v &quot;$(_MsiFileToSign)&quot;"  Condition="'$(PropsInOneLine)' != ''" />
  </Target>
  <Target Name="SignMsi"
          DependsOnTargets="CreateProperties"  >
        <PropertyGroup>

            <_MsiFileToSign>$(TargetPath)</_MsiFileToSign>
        </PropertyGroup>

        <Message Text=" File Name to sign= $(_MsiFileToSign)" />
        <Exec Command="&quot;$(SignToolPath)&quot; sign /f &quot;$(AuthenticodeCertFile)&quot; /p &quot;$(AuthenticodePassword)&quot; /t $(AuthenticodeTimestamp) /v &quot;$(_MsiFileToSign)&quot;"  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签名。

在这种情况下,您应该在签名后进行手动验证步骤。因此,手动签名不会在发布过程中插入额外的手动步骤,并且自动化可以节省很少的时间。作为交换,在您的组织中浮动的签名文件将会少得多,您可以对这些文件做出更强有力的保证。