msbuild: `SignFile` task using dotnet cli tries to use `signtool.exe` from wrong location

Issue Description

Thanks to issue #6098, the SignFile task is now enabled in dotnet msbuild 16.11.

However, it does not actually seem to work.

I was under the impression that SignFile did the signing itself, using core framework functionality.; however, it turns out it wants to use signtool.exe, but does not actually locate it correctly.

So when using SignFile as part of dotnet build, I get:

error MSB3482: An error occurred while signing: SignTool.exe was not found at path xxx\signtool.exe.

where xxx is the project directory. When building a solution it wants signtool.exe to be present in every project’s directory separately.

MSBuild should be able to locate it properly; I had my own lookup in place before, using

    <SignToolPath Condition=" '$(SignToolPath)' == '' and '$(WindowsSdkVerBinPath)' != '' and '$(PROCESSOR_ARCHITECTURE)' == 'AMD64' and Exists('$(WindowsSdkVerBinPath)x64\signtool.exe') ">$(WindowsSdkVerBinPath)x64\signtool.exe</SignToolPath>
    <SignToolPath Condition=" '$(SignToolPath)' == '' and '$(WindowsSdkVerBinPath)' != '' and '$(PROCESSOR_ARCHITECTURE)' == 'AMD64' and Exists('$(WindowsSdkVerBinPath)x86\signtool.exe') ">$(WindowsSdkVerBinPath)x86\signtool.exe</SignToolPath>
    <SignToolPath Condition=" '$(SignToolPath)' == '' and '$(WindowsSdkVerBinPath)' != '' and '$(PROCESSOR_ARCHITECTURE)' == 'x86'   and Exists('$(WindowsSdkVerBinPath)x86\signtool.exe') ">$(WindowsSdkVerBinPath)x86\signtool.exe</SignToolPath>

and that would have led to using C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64\signtool.exe just fine.

Steps to Reproduce

  • create a simple project:
    dotnet new classlib -n CodeSigning
    cd CodeSigning
    
  • generate a self-signed certificate
    New-SelfSignedCertificate -Type CodeSigningCert -Subject CN=CodeSigning -CertStoreLocation Cert:\CurrentUser\My
    
    This will produce output like:
       PSParentPath: Microsoft.PowerShell.Security\Certificate::CurrentUser\My
    
    Thumbprint                                Subject              EnhancedKeyUsageList
    ----------                                -------              --------------------
    B77064C7175EF732F534B8D28C337CA2FB87E9D2  CN=CodeSigning       Code Signing
    
    make a note of that thumbprint value; it’s needed in the next step.
  • Set up code signing in the project by adding this target to CodeSigning.csproj:
    <Target Name="_SignAssemblies" AfterTargets="Compile" DependsOnTargets="CreateSatelliteAssemblies;$(RazorCompileDependsOn)">
      <PropertyGroup>
        <SigningCertificate>B77064C7175EF732F534B8D28C337CA2FB87E9D2</SigningCertificate>
      </PropertyGroup>
      <ItemGroup>
        <_AssembliesToSign Include="$(IntermediateOutputPath)$(TargetFileName)" />
        <_AssembliesToSign Include="@(IntermediateSatelliteAssembliesWithTargetPath)" />
        <_AssembliesToSign Include="@(RazorIntermediateAssembly)" />
      </ItemGroup>
      <Message Importance="high" Text="Signing assemblies: @(_AssembliesToSign)" />
      <SignFile SigningTarget="%(_AssembliesToSign.Identity)" CertificateThumbprint="$(SigningCertificate)" />
    </Target>
    
    making sure that the value of $(SigningCertificate) is the thumbprint of the certificate you generated.
  • build using msbuild; this should succeed, with output including
    _SignAssemblies:
      Signing assemblies: obj\Debug\net5.0\CodeSigning.dll
    CopyFilesToOutputDirectory:
    
  • build using dotnet build -v:n; this will fail with an error like
           _SignAssemblies:
           Signing assemblies: obj\Debug\net5.0\CodeSigning.dll
       1>...\CodeSigning\CodeSigning.csproj(17,5): error MSB3482: An error occurred while signing: SignTool.exe was not found at path ...\CodeSigning\signtool.exe.
    

Expected Behavior

The SignFile task works, signing the assemblies, when using either dotnet build or msbuild.

Actual Behavior

The SignFile task works only when using msbuild.

Analysis

The SignFile task implementation does not seem to locate SignTool correctly when using dotnet build. (And in addition, I thought that it was doing the signing itself (which would potentially make it work on Linux as well, which would be very convenient for CI/CD scenarios), using corefx functionality, not using an external utility from a Windows Kit.)

Versions & Configurations

Tested using VS2019 16.11.2 and .NET SDK 5.0.400, i.e. MSBuild 16.11.0.36601 on x64 Windows.

I’m not sure how to set up a certificate on Linux (no New-SelfSignedCertificate in pwsh there), but that would only matter if SignFile did the signing itself and not via SignTool.

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Comments: 18 (14 by maintainers)

Most upvoted comments

@rainersigwald Thanks for the reply.

So is there a plan for making signed binaries a first-class concept in the .NET SDK? So this task was just all about ClickOnce. To me though, since all operating systems have signed binaries, in this day an age, and where the .NET SDK is and going in the future, it’d seem prudent to have a universal way of signing binaries with the SDK.

Outside of ClickOnce, Windows has just good old Authenticode, signed DLLs, EXEs, MSIs, etc. It seems like Linux also has signed ELF binaries. It’s odd that in 2023 we’re still having to hunt for a copy of signtool on our build machines, and it’s limited to just Windows.

It’d be amazing if this task could find new life as a generic cross-platform signing task. 😃

I suppose it would be nice to document the task better then. It is not described as “an internal task intended for signing click-once artefacts only. Requires either Visual Studio and/or a Windows 8.1 SDK to be installed”. Instead, it suggests it performs general authenticode signing, but then does not, and has additional dependencies that make sense for msbuild.exe but not dotnet msbuild.

It certainly feels like there is little to no support for Authenticode in a .NET context - if that’s the intent, fine.

@BenVillalobos It thought it worked in your case because it found the ClickOnce install by looking in the right registry key when run in 64-bit mode, which is apparently a fix in 17 but not 16.11.

It makes no sense to me to have to install an obsolete SDK to make this work. We’re signing all built assemblies, not just netfx, so I’m not sure I follow the reasoning that because .NET Framework 4.8 may be associated with VS15, that’s all that needs to be looked at.

I guess I’ll just look at including signtool.exe in our build support package, so we can have it available that way without needing a VS/WindowsKit installed.

Visual Studio Enterprise 2019 16.11.2

HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\ClickOnce\SignTool exists and contains C:\Program Files (x86)\Microsoft SDKs\ClickOnce\SignTool\. That path exists and does contain signtool.exe.

So it looks like it should find it, but as you say, it’ll likely be because dotnet.exe is running in 64-bit mode and does not see that registry key.

Note that if SignFile requires Visual Studio (Build Tools or otherwise) to be installed, that kind of defeats the purpose of enabling it. The whole point for me was to enable builds using CI without requiring installs, i.e. just doing a zip-based deployment of a .NET SDK. Any reason why signtool can’t simply ship with Windows versions of the .NET SDK?