There are many ways to fend off malware. Perhaps the most effective way to accomplish this is via whitelisting. An organization can designate which assemblies can be executed in a corporate environment. Everything that is not in that designated list is then not allowed to execute.
Whitelisting is an excellent approach, but can pose problems for development teams. Every time that a developer builds an assembly, someone would need to whitelist that assembly. This can be a nightmare, but there is an option that provides a little better experience.
Instead of whitelisting an assembly’s signature, you can also choose to whitelist a digital certificate. Then, every assembly that is signed with that digital certificate is, in turn, whitelisted.
You can easily sign a .NET assembly using ClickOnce technology. But what if you are building an assembly that targets .NET Core?
.NET Core doesn’t support ClickOnce. Because of this, there is no trivial way to digitally sign an assembly with a certificate via the project properties. To be honest, this makes perfect sense, as .NET Core is meant to be cross platform. The second you sign an assembly, you lose benefits of being a cross-platform assembly.
Even if you are developing a cross-platform solution using .NET Core, you may still want the assemblies you plan on deploying to a Windows environment digitally signed for the reasons outlined above. In this article, I plan on showing you how to use SignTool.exe to sign your assemblies manually. I will then show you how to sign your assembly by adding a post-build event to your project. Finally, I will show you how to sign your assembly by modifying your project’s MSBuild file.
Setting Up the Project
First, let’s create a brand new project so that we have something to work with. I’m going to use Visual Studio 2019 to create a solution with a single project. This single project will be a .NET Core Console Application.
Next, I’ll give the project a name and click the Create button.
Visual Studio opens my new solution. As you can see, there is a single file, Program.cs that does nothing more than output “Hello World” to the console.
If you build and run the application, you’ll see that that’s exactly what the program does. Nothing fancy here:
Let’s take a quick look at the assembly. If you right-click the project and click on “Open Folder in File Explorer,” you can drill into the directory containing the assembly.
Right-clicking on the .dll and choosing Properties opens the File Properties dialog. As you can see, there isn’t a tab for the digital certificate. The assembly is not currently signed.
In order to sign the assembly, we are going to use SignTool.exe. This assembly is part of the Microsoft Windows SDK. However, if you have Visual Studio installed, it is quite possible that SignTool.exe is already on your machine.
If you don’t have Visual Studio installed, you can install the Windows 10 SDK by visiting Microsoft’s website, or by following the link below:
If you do have Visual Studio installed, you can use Visual Studio Installer to install the Windows 10 SDK. First, open the Visual Studio Installer.
Next, click the Modify button for the respective version of Visual Studio you want to modify. Activate the Individual Components tab and scroll all the way down until you find the entries for the Windows 10 SDK. Select the latest version noting the version number that you have selected. You’re going to need to recall that version number shortly. Then click the Modify command button.
The installer should place SignTool.exe somewhere in your Program Files (x86) directory. It is a tad difficult to find out exactly where. This is where the version number you selected above comes in handy. SignTool.exe should be in the directory that matches:
C:\Program Files (x86)Windows Kits\bin\<version>\<architecture>
- version is the version number of the Windows 10 SDK you specified in the Visual Studio Installer above.
- architecture is the CPU architecture that you are targeting. This will most likely by either x86 or x64, but could also be arm or arm64.
In my case, I installed Windows 10 SDK version 10.0.17763.0. I am targeting a 64-bit CPU. Therefore, SignTool.exe exists in the following directory:
C:\Program Files (x86)\Windows Kits\10\bin\10.0.17763.0\x64
If you still cannot locate SignTool.exe after performing these steps, open an elevated command prompt and execute the following commands:
> cd \ > dir /s signtool.exe
This will spit out all directories containing SignTool.exe.
Digitally Signing the Console App Manually
Now that we have located SignTool.exe, we can sign our console app with a digital certificate. I have a digital certificate located in my personal certificate store.
Creating a digital certificate is outside the scope of this post. There are a several ways of creating a cert. My organization requires developers to request a new certificate using Microsoft Management Console’s Certificates snap-in. You should follow your organization’s process. The end result is that you should have a code-signing certificate installed in your personal certificate store.
In order to sign, you must know the Common Name of the digital certificate with which you intend to sign your assembly. The Common Name is also called the Subject, and it is listed in the “Issued To” column in your list of personal certificates. The MMC Certificates snap-in shows my certificate below:
In my case, my certificate’s Subject is my name – “Bryan Burman”.
With this information, we’re armed with all of the information we need in order to sign the assembly. Open a command prompt and navigate to to your project’s directory. Note that your project’s directory is not the same as your solution’s directory.
There are several extensions that enable you to open a command prompt from a context menu within Visual Studio. My favorite extension is written by Mads Kristensen and it is called “Open Command Line.” Once you have this extension installed, you can simply right-click your project and choose Open Command Line | Default. Or, you could choose to open a PowerShell prompt (which is my personal preference).
Once you have a command line open and you are in your project’s directory, execute the following command, replacing <subject> with your digital certificate’s subject and <path> with the relative path to your .dll.
> "C:\Program Files (x86)\Windows Kits\10\bin\10.0.17763.0\x64\signtool.exe" sign /n "<subject>" "<path>"
If you typed everything correctly, then you should see a message indicating that your assembly was successfully signed:
Now, when we look at the .dll’s File Properties dialog, we can see that it is in fact digitally signed:
Digitally Signing the Console App with a Post-Build Event
Having to execute a command manually after each build is a laborious process. What we need now is a method of automating this process. Thankfully, Microsoft has given us an easy mechanism to do this – Build Events.
You can access Build Events from the project’s property pages. Just right-click the project and choose “Properties” and activate the “Build Events” tab. On this page, you’ll see two text boxes – one for pre-build events and one for post-build events. Since we want to sign our assembly after it has been built, we’ll enter our command line from above in the post-build event command line.
The positive with this approach is that we can utilize some MSBuild properties to remove the hard-coded <path> that we specified above. Instead, all you need to do is specify the path as $(TargetDir)$(TargetFileName). TargetDir will contain the fully qualified path of the output directory. TargetFileName will contain the name of the output assembly, including its extension.
Now, after you build the application, you’ll note in the output window that the assembly was successfully signed.
Digitally Signing the Console App With a Custom MSBuild Target
We can get a little more sophisticated by altering the project file itself. Doing so, we can introduce a new MSBuild target that performs the signing for us. Since we are working with a .NET Core project, we don’t have access to the SignFile task, but that’s fine. We can still execute an Exec task that will perform the command described above.
The first thing that you need to do in this approach is to remove the post-build event you created in the previous section.
Next, you’ll want to edit the project file but right-clicking on it in Solution Explorer and choosing “Edit …”
We have raw, unfettered access to all of MSBuild from this file. Add the following text to the <Project /> element immediately underneath the existing <PropertyGroup /> element.
<PropertyGroup> <SignToolPath>C:\Program Files (x86)\Windows Kits\10\bin\10.0.17763.0\x64\signtool.exe</SignToolPath> <SigningCertificateSubjectName>your-subject</SigningCertificateSubjectName> </PropertyGroup> <Target Name="SignAssembly" DependsOnTargets="Build"> <Message Text="Signing assembly '$(TargetDir)$(TargetFileName)'" Importance="high" /> <Exec Command=""$(SignToolPath)" sign /n "$(SigningCertificateSubjectName)" "$(TargetDir)$(TargetFileName)"" /> </Target>
Simply replace the SigningCertificateSubjectName’s value from ‘your-subject’ to the digital certificate’s subject you have used above. Your file should look similar to this:
Now, if you open a command prompt from your project’s directory, you can use the dotnet command to build and sign your assembly:
> dotnet msbuild -target:SignAssembly
Executing this command will cause the Build command to be invoked first. After Build is invoked, MSBuild will invoke the SignAssembly target, which will spit out a message and execute the command line above. If all works according to plan, you should see a message indicating that the assembly was signed.
A consequence of this action is that you are, once again, relegated to the command line to compile the project. When you compile your project, Visual Studio issues a “dotnet build” command. This command is equivalent to executing “dotnet msbuild -restore -target:Build”. There is no trivial way to override the Build target. Therefore, there is no trivial way to automatically kick off the SignAssembly target.
In this article, I showed you three different ways in which you can use SignTool.exe to sign your .NET Core assemblies. Unfortunately, none of these options scales well with medium to large development teams. If you have an Azure DevOps build and want to code sign an assembly, I encourage you to read this post.