How to sign assembly with as strong name in Visual Studio Team Services?
I have a self-generated password-protected key.pfx. That key.pfx file is used to sign some assemblies in my solution. I tried to build that solution in Visual Studio Team Services and it failed right away with the following error:
Error MSB3325: Cannot import the following key file: key.pfx. The key file may be password protected. To correct this, try to import the certificate again or manually install the certificate to the Strong Name CSP with the following key container name: VS\_KEY\_9000008CC1777
Quick googling shows that I need to run:
sn -i key.pfx VS_KEY_9000008CC1777
However this command will prompt a password, so this would not work for a build server. I tried to use an automated solution for the above command using the following PowerShell script:
param($PfxFilePath, $Password)
$absolutePfxFilePath = Resolve-Path -Path $PfxFilePath
Write-Output "Importing store certificate '$absolutePfxFilePath'..."
Add-Type -AssemblyName System.Security
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.Import($absolutePfxFilePath, $Password, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]"PersistKeySet")
$store = new-object system.security.cryptography.X509Certificates.X509Store -argumentlist "MY", CurrentUser
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::"ReadWrite")
$store.Add($cert)
$store.Close()
However, this didn’t work for Visual Studio Teams Services hosted build agent. I suspect that hosted build server has some kind of environment protection and the above certificate installation fails silently. Then I decided to extract key.snk file from key.pfx and use that file to sign assemblies. This approach works, but it is not secure, since I would need to store a private unprotected key file in source control. So, I came up with the idea to dynamically extract key.snk using the following PowerShell script:
Param(
[Parameter(Mandatory=$True,Position=1)]
[string] $PfxFilePath,
[string] $PfxPassword
)
# The path to the snk file we're creating
[string] $snkFilePath = [IO.Path]::GetFileNameWithoutExtension($PfxFilePath) + ".snk";
# Read in the bytes of the pfx file
[byte[]] $pfxBytes = Get-Content $PfxFilePath -Encoding Byte;
# Get a cert object from the pfx bytes with the private key marked as exportable
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2(
$pfxBytes,
$PfxPassword,
[Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable);
# Export a CSP blob from the cert (which is the same format as an SNK file)
[byte[]] $snkBytes = ([Security.Cryptography.RSACryptoServiceProvider]$cert.PrivateKey).ExportCspBlob($true);
# Write the CSP blob/SNK bytes to the snk file
[IO.File]::WriteAllBytes([IO.Path]::Combine([IO.Path]::GetDirectoryName($PfxFilePath), $snkFilePath), $snkBytes);
Then I added two variables to the build definition:
CertPath = $(Build.SourcesDirectory)\key.pfx
CertPass = password (clicked on a lock to secure value of variable)
After that I added a PowerShell task with the following arguments:
This time the build passed and assemblies were signed successfully.