I have 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:
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 on a build server. I tried to use automated solution for the above command using following powershell script:
$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
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 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 private unprotected key file in source control.
So, I came up with the idea to dynamically extract key.snk using following powershell script:
# 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(
# 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 build definition:
CertPath = $(Build.SourcesDirectory)\key.pfx
CertPass = password (clicked on a lock to secure value of variable)
After that I added powershell task with following arguments:
-PfxFilePath "$(CertPath)" -PfxPassword "$(CertPass)"
This time the build passed and assemblies were signed successfully.