Sam's Blog

08 Oct

Testing strong names of NuGet-distributed assemblies

Assembly versioning for strong-named assemblies is a notoriously difficult topic, but once understood becomes straightforward to work with. In this post, I’ll cover two features I recently started incorporating into my projects to ensure the versioning rules for the project are properly observed prior to releasing packages.

  1. Distribution of Strong Name signing files (*.snk) in open source projects
  2. Validating the strong name of assemblies prior to publication

Distribution of keys

tl;dr

Install the Rackspace.KeyReporting NuGet package for improved error reporting when a strong name signing file is missing from the build computer.

Complete details

For the open source projects I work with on a daily basis, the easiest way to ensure strong name keys are not accidentally included in source control is storing the keys outside the directory structure of the project. For new users of the project, this often means a freshly downloaded copy of the project’s source code will not compile, and the error message resembles the following:

error CS7027: Error signing output with public key from file ‘..\..\..\..\..\..\keys\antlr\antlr-net45.snk’ — File not found.

My current approach to resolving this centers around clearly stating the manner in which this problem can be resolved. For example, the following error message immediately informs the user of the necessary step.

error : This project references a strong name key that is missing on this computer. Run ‘sn -k D:\github\keys\antlr\antlr-net45.snk’ to generate the file.

This feature was reasonably easy to provide by creating a custom KeyReporting.targets file, and including it in the project. However, to make it even easier it is now available for installation as an open-source NuGet package. Simply use NuGet to install the Rackspace.KeyReporting NuGet package and your build will report these improved error messages.

Validating strong names prior to publication

tl;dr

A complete example of the steps required to implement this feature is available in rackerlabs/dotnet-threading#46.

Complete Details

As every package owner should know, changing the strong name of an assembly is a breaking change. One component of the strong name is the signing key, which is responsible for the PublicKeyToken component of the final strong name. For most of the open-source projects I contribute to, the strong name used for “official” releases is not publicly distributed. However, as described above, developers are encouraged to create their own *.snk files in order to build local copies of the project for testing and ongoing development. To make sure the assemblies which are published to NuGet use the correct strong name keys, the build script was updated to only create NuGet packages when the strong name key of the assemblies it contains match the expected values.

Create a script for checking keys

Create a separate PowerShell script responsible for checking the strong name of an assembly. Since the use reflection to load the assembly directly would prevent the file from being updated without restarting PowerShell, this separate script is used to execute the check in a separate process.

param(
[string]$Assembly,
[string]$ExpectedKey,
[string]$Build = $null
)
function Get-PublicKeyToken() {
param([string]$assembly = $null)
if ($assembly) {
$bytes = $null
$bytes = [System.Reflection.Assembly]::ReflectionOnlyLoadFrom($assembly).GetName().GetPublicKeyToken()
if ($bytes) {
$key = ""
for ($i=0; $i -lt $bytes.Length; $i++) {
$key += "{0:x2}" -f $bytes[$i]
}
$key
}
}
}
if (-not $Build) {
$Build = $Assembly
}
$actual = Get-PublicKeyToken -assembly $Assembly
if ($actual -ne $ExpectedKey) {
$host.ui.WriteErrorLine("Invalid publicKeyToken for '$Build'; expected '$ExpectedKey' but found '$actual'")
exit 1
}

Define the expected keys for your assemblies

The next step is defining the expected public key token for the assemblies. Note: to determine the public key token for the first time, use 'placeholder' originally and observe the error message produced by the build at the end of this post.

# Note: this value may only change during major release
$ExpectedPublicKeyToken = '8b3790928cb57ea0'

Check signing keys before creating NuGet packages

The actual key checking is straightforward. I prefer to include a parameter to the build script called $SkipKeyCheck which allows developers to selectively disable the key check.

# By default, do not create a NuGet package unless the expected strong name key files were used
function Resolve-FullPath() {
  param([string]$Path)
  [System.IO.Path]::GetFullPath((Join-Path (pwd) $Path))
}
 
if (-not $SkipKeyCheck) {
  $assembly = Resolve-FullPath -Path "..\Project\bin\$BuildConfig\AssemblyName.dll"
  # Run the actual check in a separate process or the current process will keep the assembly file locked
  powershell -Command ".\check-key.ps1 -Assembly '$assembly' -ExpectedKey '$ExpectedPublicKeyToken' -Build 'AssemblyName'"
  if ($LASTEXITCODE -ne 0) {
    Exit $p.ExitCode
  }
}

Leave a Reply

© 2024 Sam's Blog | Entries (RSS) and Comments (RSS)

Your Index Web Directorywordpress logo