Подпишите каждый исполняемый файл сертификатом Authenticode через MSBuild

У меня есть сертификат Authenticode (.pfx), который я использую для подписи исполняемых файлов.

Как настроить Team Build так, чтобы он подписывал каждый исполняемый файл (.exe, .dll , ...) автоматически при сборке проекта?


person Mephisztoe    schedule 28.08.2009    source источник
comment
DLL - это сборка, а не исполняемый файл. EXE - это исполняемая сборка. Итак, вы действительно хотите знать, как подписывать сборки, а не исполняемые файлы. Разница в ключевых словах может помочь - хотя не могу сказать, что знаю много о подписи.   -  person Matt Ball    schedule 28.08.2009
comment
Справедливости ради OP - MS использует термин исполняемый файл (.exe, .dll ...) в программах с логотипом Windows.   -  person Martin Clarke    schedule 28.05.2012
comment
Кроме того, собственные / неуправляемые библиотеки DLL и EXE не являются сборками. Вызов сборок EXE и DLL является терминологией, специфичной для .Net, и применяется только к CLI. Неуправляемые библиотеки DLL не могут входить в GAC и не могут быть подписаны строгим именем, но они могут быть подписаны аутентификационным кодом. Ссылка: Assembly CLI, wikipedia   -  person Andrew Domaszek    schedule 20.07.2016


Ответы (4)


Вот метод, который мы используем:

  1. Выгрузите проект WiX и выберите Edit.

  2. Прокрутите вниз, где вы найдете <Import Project="$(WixTargetsPath)" />

  3. Добавьте новую строку непосредственно над ним: <Import Project="ProjectName.custom.targets" /> Мы используем соглашение об именах «ProjectName.custom.targets», но файл можно назвать как угодно.

  4. Создайте новый XML-файл с именем ProjectName.custom.Targets и поместите в него следующий код:

    <?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>
    

Создайте тестовый сертификат аутентификации (мы назвали наш AuthenticodeTest.pfx) и поместите его в систему управления версиями - путь к нему задается в свойстве AuthenticodeCertFile. Чтобы проверить это, запустите msbuild в командной строке и измените свойство OutDir - т.е. / msbuild Test.sln / p: OutDir = C: \ Test

Некоторые настройки потребуются, если:

  • Если вы не хотите использовать "приватную" группу предметов (я схитрил)
  • Если вы не используете ссылки на проекты WiX
  • Если ваши CAB-файлы отделены от MSI, их также необходимо подписать.

Чтобы запустить финальную сборку, выберите «Очередь новой сборки» в TFS. Щелкните «Параметры» и разверните «Дополнительно». В разделе «Аргументы MSBuild» добавьте /p:AuthenticodeCertFile=ProductionCertFile.pfx /p:AuthenticodePassword=Secret. Обратите внимание, что это может быть не совсем безопасно - может быть сложно заставить агент сборки найти файл PFX, не проверяя его, и пароль может быть зарегистрирован в выходных данных сборки. В качестве альтернативы вы можете создать для этого специальный заблокированный агент сборки или запустить сборку локально из командной строки, но, очевидно, это не будет среда «чистой комнаты». Возможно, стоит создать специальный заблокированный «чистый» сервер специально для этой цели.

person ShadowChaser    schedule 07.06.2011
comment
Способ избежать использования пароля в виде обычного текста в файле проекта / MSBuild описан в вопросе о переполнении стека Как надежно сохранить пароль .pfx для использования в MSBuild?. По сути, он использует хранилище сертификатов Windows и параметр / sha1 для signtool.exe. Затем безопасность привязывается к учетной записи пользователя, которая получает хранилище сертификатов с рассматриваемым сертификатом. - person Peter Mortensen; 31.12.2013

Я не согласен. Поскольку сертификат подписи кода должен быть установлен на компьютере сборки для выполнения подписи, почему бы не подписывать все, что создается на этом компьютере каждый раз, когда он создается? Компьютер находится в «опасности», потому что на нем установлен сертификат подписи кода, поэтому его необходимо каким-либо образом защитить (физическая безопасность и безопасность системы). Если он защищен, почему бы не позволить ему выполнять работу, для которой он был предназначен, готовить файлы к доставке, последовательно, с возможностью повторения, каждый раз?

К сожалению, ответ «не надо» также кажется стандартным ответом Microsoft, поскольку они, похоже, почти не поддерживают в MSBuild цикл по списку имен файлов, вызывая программу один раз для каждого имени файла в списке. Я нашел способы передать сгенерированный с помощью подстановочных знаков список файлов в программу Signtool.exe, но она может обрабатывать только один файл за раз.

Я боюсь (для меня), что он вернулся к написанию командного файла, который перебирает свои аргументы и вызывает signtool для каждого аргумента. Написание командных файлов для общей задачи подписания выходных данных сборки заставляет меня думать, что MSBuild действительно не настолько зрелая система сборки, какой должна быть. Либо так, либо у signtool неправильный интерфейс. В любом случае подписывание нескольких файлов без перечисления имени каждого файла для подписи кажется неприемлемым для MSBuild.

person Mark Waite    schedule 29.10.2009
comment
Я нашел способ позволить signtool перебирать список файлов внутри команды. Поскольку это должно быть сделано на уровне решения, единственным недостатком является то, что вам нужно указать фильтр для включения / исключения файлов (например, вы не захотите подписывать сторонние библиотеки, и вам нужно будет включать только файлы exe и dll) . Если вам интересно, я могу опубликовать несколько советов в нашем блоге разработчиков (который, однако, немецкий ...) - person Mephisztoe; 04.11.2009
comment
Мне был бы интересен ваш метод итерации signtool по списку файлов, и я счастлив прочитать его на немецком языке. - person Mark Waite; 16.11.2009
comment
Мы просто используем командный файл, который мы ‹Exec› из MSBuild. Две строки кода (тривиальный оператор for), которые рекурсивно перебирают все exes и dll в целевой папке, запуская signtool на каждой. - person Jason Williams; 23.07.2010
comment
Я не знаю, изменилось ли что-то в MSBuild с 2009 года, но вы определенно можете перебрать список файлов и запустить инструмент для каждого файла в списке. Вам нужна пакетная обработка задач. - person Daniel Yankowsky; 24.03.2016

Мой ответ основан на ответе ShadowChaser. разница в том, что моя цель MSBuild будет подписывать указанный файл или, если ничего не указано, она будет подписывать выходные данные сборки проекта, из которого была вызвана цель.

сохраните следующее в файле целей, скажем, signed.custom.targets. как только вы его сохраните, просто включите его в свой csproj или wixproj и используйте цель для подписи вашего вывода. это также будет работать на локальной машине разработчика, поскольку файл sign.properties (это гибридная среда разработки, поэтому мы не хотели указывать свойства сертификата дважды) не существует в локальном поле разработчика.

чтобы подписать файл из командной строки с помощью этого, вы можете

MSBuild.exe signing.custom.targets /t:SignAssembly /p:_MsiFileToSign="..\Builds\Release\Setups\yourFileToSign.msi

Определение signature.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>
person Dhawalk    schedule 20.11.2015

Не надо.

Вы не хотите автоматически подписывать сборки. Большинство сборок в любом случае не нужно подписывать; они используются только для автоматизации тестов. Некоторые сборки могут быть переданы вашим штатным тестерам. Но только сборки, которые вы действительно выпускаете за пределами своей организации, нуждаются в подписях Authenticode.

В этом случае у вас все равно должен быть этап проверки вручную после подписания. Таким образом, подписание вручную не добавляет дополнительных ручных действий в процесс выпуска, а автоматизация очень мало экономит время. В обмен на это в вашей организации будет гораздо меньше подписанных файлов, и вы сможете дать гораздо более надежные гарантии относительно имеющихся файлов.

person MSalters    schedule 28.08.2009
comment
Разумно не подписывать все каждый раз. Однако нам необходимо установить автоматизированный процесс подписания окончательной версии программного обеспечения. Также для проверки посредством тестирования платформы для Microsoft все файлы .exe, .dll и .msi должны быть подписаны. У нас есть автоматизированный процесс, который получает текущие источники, автоматически обновляет их, создает их, создает лицензии и структуру каталогов, упаковывает все в MSI, помещает документы в папки и, наконец, заархивирует все для распространения. Мы бы просто добавили еще один сценарий сборки (MS Build - ›Team Build) для подписи. Но как? - person Mephisztoe; 28.08.2009
comment
О, я согласен, что вы должны подписать релизную версию. И я согласен с тем, что процесс в основном автоматизирован. Просто поставьте паузу в процессе выпуска перед упаковкой двоичных файлов в MSI. На этом этапе вы можете легко подписать двоичные файлы, которые хотите подписать. Все они созданы на этом этапе, поэтому на них легко ссылаться, используя цикл FOR или, возможно, даже просто подстановочный знак. - person MSalters; 31.08.2009
comment
Я не согласен с тем, чтобы не подписывать. Мы отправляем более 100 подписанных сборок в нашем продукте, и они упакованы в установщик, который также подписан. Не автоматизировать это было бы безумием. Гораздо лучше протестировать фактический выпускаемый продукт, чем что-то похожее на него, и безопаснее, если все ваши установщики обфусцированы и подписаны, чтобы незащищенная копия не могла просочиться в дикую природу. У нас также есть Agile-магазин, поэтому любая сборка (которая хороша) потенциально является выпуском. (Мы не подписываем нашу сборку CI, но она не создает exe, который на самом деле используется для чего-либо) - person Jason Williams; 23.07.2010
comment
Очевидно, вы бы так приспособили свой процесс выпуска, чтобы подписание всех 100 сборок выполнялось вручную. Но это часть процесса выпуска, а не процесса сборки. - person MSalters; 26.07.2010
comment
Я не согласен. Код должен быть подписан на каждом этапе, включая сборки без выпуска. Создайте тестовый сертификат, иначе вы не сможете проверить поведение Authenticode в своих системах. Рассмотрим .NET, где сборки Authenticode ведут себя иначе. Одно из наших приложений очень плохо работало для пользователей модема из-за поиска в списке отзыва во время сбора доказательств. Вы также заявляете, что сборки должны быть подписаны в процессе выпуска. В случае установщика для этого потребуется разобрать, подписать и собрать установщик. Безумный. - person ShadowChaser; 07.06.2011
comment
Извините, но ваш ответ не имеет смысла. Зачем нужно разбирать установщик? Как и исполняемый файл, он создается как часть процесса выпуска. Исполняемый файл подписывается, добавляется в программу установки, и затем программа установки создается. (Процесс выпуска обязательно включает сборку исполняемого файла из помеченного состояния системы управления версиями, чтобы обеспечить воспроизводимость. Таким образом, любые последующие шаги также являются частью процесса выпуска.) - person MSalters; 08.06.2011