Securely Signing ClickOnce Applications in Azure D

2019-05-07 14:13发布

I'm trying to do CI/CD in Azure DevOps with a ClickOnce application. How can I securely make my code signing certificate available during the build when using a hosted agent?

Note I'm aware you can use a script as suggested at Visual studio team services deploymen/buildt certificate error. However this approach is not secure. The certificate would be loaded into the certificate store of the account the hosted agent is running under. This would allow the agent, and hence other Azure DevOps accounts, to potentially access and use the certificate.

1条回答
看我几分像从前
2楼-- · 2019-05-07 14:42

The solution to the issue is to override the built in task SignFile. Interestingly enough the task SignFile uses a built in function in Microsoft.Build.Tasks.Deployment.ManifestUtilities.SecurityUtilities.SignFile which has two overloads, one that takes a thumbprint, and one that takes a file and password.

The solution is then to create a new Task that can reference the other overload. Since we cannot change the calling SignFile we need to maintain the same signature, and place the appropriate variables in the environment variables. In this case "CertificateFile" and "CertificatePassword".

Then reference those two in the overwritten SignFile. What I did was to create a new targets file (filesign.targets) and place the code there. Checked that in to my repository and referenced it from the main project file(s). <Import Project="filesign.targets" />

This way we can also hold our key files in an Azure Key Vault, load them at built and give them a unique password just for that build.

The targets file holds the new FileSign task:

<?xml version="1.0" encoding="Windows-1252"?>
<!--
***********************************************************************************************
Microsoft.VisualStudio.Tools.Office.targets

WARNING:  DO NOT MODIFY this file unless you are knowledgeable about MSBuild and have
created a backup copy.  Incorrect changes to this file will make it
impossible to load or build your projects from the command-line or the IDE.

This file defines the steps in the standard build process specific for Visual Studio Tools for 
Office projects.

Copyright (C) Microsoft Corporation. All rights reserved.
***********************************************************************************************
-->

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

    <UsingTask TaskName="SignFile" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll">

    <ParameterGroup>
      <SigningTarget Required="true" ParameterType="Microsoft.Build.Framework.ITaskItem" />
      <CertificateThumbprint ParameterType="System.String" />
      <TargetFrameworkVersion ParameterType="System.String" />
      <TimestampUrl ParameterType="System.String" />
      <CertificateFile ParameterType="System.String" />
      <CertificatePassword ParameterType="System.String" />
    </ParameterGroup>
    <Task>
      <Reference Include="mscorlib" />
      <Reference Include="Microsoft.Build.Tasks.Core" />
      <Using Namespace="System" />
      <Code Type="Fragment" Language="cs">
        <![CDATA[
                var EnvCertFile = System.Environment.GetEnvironmentVariable("CertificateFile");

                Log.LogMessage("CertFile:!!" + EnvCertFile);

                if (string.IsNullOrWhiteSpace(CertificateFile) && string.IsNullOrWhiteSpace(EnvCertFile)) {
                    var signFile = new Microsoft.Build.Tasks.SignFile();
                    signFile.CertificateThumbprint = CertificateThumbprint;
                    signFile.SigningTarget = SigningTarget;
                    signFile.TargetFrameworkVersion = TargetFrameworkVersion;
                    signFile.TimestampUrl = TimestampUrl;
                    return signFile.Execute();
                } else {

                    var certificate = string.IsNullOrWhiteSpace(CertificateFile) ? EnvCertFile : CertificateFile;
                    var EnvCertPassword = System.Environment.GetEnvironmentVariable("CertificatePassword");
                    var certificatePassword = string.IsNullOrWhiteSpace(CertificatePassword) ? EnvCertPassword : CertificatePassword;
                    var testString = new System.Security.SecureString();
                    // Use the AppendChar method to add each char value to the secure string.
                    if (!string.IsNullOrWhiteSpace(certificatePassword))
                        foreach (char ch in certificatePassword)
                            testString.AppendChar(ch);
                    Microsoft.Build.Tasks.Deployment.ManifestUtilities.SecurityUtilities.SignFile(certificate, testString,
                        TimestampUrl == null ? null : new Uri(TimestampUrl),
                        SigningTarget.ItemSpec);
                    return true;
                }

]]>
      </Code>
    </Task>
  </UsingTask>
</Project>

Code based on: https://gist.github.com/KirillOsenkov/4cd32c40bffd3045f77e

References: https://github.com/Microsoft/msbuild/blob/fc10ea8ce260b764bb9fa5033b327af9fefcaabe/src/Tasks/ManifestUtil/SecurityUtil.cs https://github.com/Microsoft/msbuild/blob/master/src/Tasks/SignFile.cs

查看更多
登录 后发表回答