Error MSB3325: Cannot import the following key file: mycertificate.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_F287A1878DD02205
Problem background
Certificates (in pfx containers) are password protected and for MSBuild to be able to use them, they must be installed in a key container. (MSBuild uses the "Microsoft Strong Cryptographic Provider", MSC)Normally, when selecting a certificate in Visual Studio, you are prompted for the password and Visual studio creates the container for you. When the container is created, you do not need the password as long as the container exists. If you would like to build the project on another machine you will get the error above since the container is not present.
Manual solutions
If you go to another machine, one simple way to create the container is to deselect and select the certificate again (in visual studio, in the properties/signing window) which will prompt you for the password.Another way is to use sn.exe and pass the certificate and key container name as parameters
Sn.exe -i mycertificate.pfx VS_KEY_F287A1878DD02205
This command will then prompt you for the password and create the container
https://docs.microsoft.com/en-us/dotnet/framework/tools/sn-exe-strong-name-tool
Running on Azure pipelines
To be able to build this on Azure Pipelines you need to create the container beforehand.One way is to create a self-hosted agent that you install the container on.
https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/v2-windows?view=azure-devops
If you would like to use a Microsoft-hosted agent, then you must include the creation of the container in the pipeline itself. Using sn.exe seems like a promising way.
Automated solutions
If we would like to automate the process we have two problems that need solving1. We need to calculate the name of the container
2. We need to pass the password to the sn.exe tool somehow.
The container name is determined by a class in Microsoft.Build.Tasks.v4.0.dll named ResolveKeySource. It will base the name upon the contents of the certificate, plus the user's domain and username that are running the MSBuild command. kdrapel posted an answer regarding this on StackOverflow
The other issue, passing the password to the sn.exe tool seemed a bit harder. There were some examples where piping it to the exe or writing a wrapper around stdin but none of them worked for me.
SnInstallPfx
Thanks to honzajscz, who also had this issue, the GitHub project SnInstallPfx solved both issues in a nice way. Both calculating the container name and allowing us to enter the password ended us up with a simple command lineSnInstallPfx.exe mycertificate.pfx mypassword
Final solution in Azure Pipelines
To include it in the pipeline I used the library functionhttps://docs.microsoft.com/en-us/azure/devops/pipelines/library/?view=azure-devops
In the library, I uploaded both the certificate and the sninstallpfx.exe tool.
The password I added in a variable group.
In the YML I first added the variable group
variables: - group: 'My password'
Then the files needed to be copied to the target host using DownloadSecureFile task
- task: DownloadSecureFile@1 displayName: Download Pfx name: myCertificatePfx inputs: secureFile: MyCertificate.pfx - task: DownloadSecureFile@1 displayName: Download sni name: snInstallPfx inputs: secureFile: SnInstallPfx.exe
Then added a PowerShell task where I imported the file paths and password as an environment variable and just invoked the exe
- task: PowerShell@2 env: SN_INSTALL_PFX: $(snInstallPfx.secureFilePath) MYCERTIFICATE_PFX: $(myCertificatePfx.secureFilePath) MYCERTIFICATE_PFX_PASSWORD: $(myCertificatePfxPassword) inputs: targetType: 'inline' script: '&"$($ENV:SN_INSTALL_PFX)" "$($ENV:MYCERTIFICATE_PFX)" "$($ENV:MYCERTIFICATE_PFX_PASSWORD)"'
After a long research, I was finally able to build the project on a Microsoft hosted pipeline.
Hi Dan!
ReplyDeleteThanks a lot for your post, it helped a lot to my build.
I run my pipelines on self-hosted Agent pool implemented on Azure VMSS, and can't import the certificate locally on every VM Azure will create in the set to build components.
SnInstallPfx helped, but the PS script output from the tool ("SnInstallPfx.exe : The key pair is already installed in the strong name CSP key container 'VS_KEY_XXXXXXXX'" or "The key pair has been installed into the strong name CSP key container 'VS_KEY_XXXXXXXXXXX'.") returns to stage log as an error.
Maybe you are familiar with the ways of making that output recognizible as a success, or at least not as an Error?
When I run it on azure pipelines it does not get interpreted as an error. Supplying my output from the pipeline task if you want to compare.
ReplyDelete2020-08-14T09:18:49.6768386Z ##[section]Starting: PowerShell
2020-08-14T09:18:49.6938376Z ==============================================================================
2020-08-14T09:18:49.6938760Z Task : PowerShell
2020-08-14T09:18:49.6939057Z Description : Run a PowerShell script on Linux, macOS, or Windows
2020-08-14T09:18:49.6939360Z Version : 2.170.1
2020-08-14T09:18:49.6939600Z Author : Microsoft Corporation
2020-08-14T09:18:49.6939951Z Help : https://docs.microsoft.com/azure/devops/pipelines/tasks/utility/powershell
2020-08-14T09:18:49.6940362Z ==============================================================================
2020-08-14T09:18:53.1779936Z Generating script.
2020-08-14T09:18:53.2622737Z ========================== Starting Command Output ===========================
2020-08-14T09:18:53.2983892Z ##[command]"C:\windows\System32\WindowsPowerShell\v1.0\powershell.exe" -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command ". 'D:\a\_temp\27b747f6-83ae-4b0d-b916-e2fba95b5bb3.ps1'"
2020-08-14T09:18:55.4443633Z The key pair has been installed into the strong name CSP key container 'VS_KEY_56968BA016C083FC'.
2020-08-14T09:18:55.4445567Z VS_KEY_56968BA016C083FC
2020-08-14T09:18:56.9263469Z ##[section]Finishing: PowerShell
Thanks Dan!
DeleteForgot to update you on this, sorry.
I've set the inline PS script to run silently. In case of any issue with a certificate I'll recieve the Error in MSBuild task log anyway.
Thanks again for your post, it was helpful, now I don't need to install certificate manually on every instance I want to build and sign apps on.
Hi Dan,
ReplyDeleteThanks for the details. But can you please elaborate on how powershell was able to read YAML format? I am trying to replicate the below step:
Then added a PowerShell task where I imported the file paths and password as an environment variable and just invoked the exe
- task: PowerShell@2
env:
SN_INSTALL_PFX: $(snInstallPfx.secureFilePath)
MYCERTIFICATE_PFX: $(myCertificatePfx.secureFilePath)
MYCERTIFICATE_PFX_PASSWORD: $(myCertificatePfxPassword)
inputs:
targetType: 'inline'
script: '&"$($ENV:SN_INSTALL_PFX)" "$($ENV:MYCERTIFICATE_PFX)" "$($ENV:MYCERTIFICATE_PFX_PASSWORD)"'
I selected powershell task and selected inline and added the YAML to it, but it does not detect it as a YAML and hence throwing error.
env: : The term 'env:' is not recognized as the name of a cmdlet, function, script file, or operable program. Check
the spelling of the name, or if a path was included, verify that the path is correct and try again.
At D:\a\_temp\2dd4a17b-5bdf-4a3a-bf71-5ed565ba8f27.ps1:3 char:1
Ok I made some changes and see the script run ##[debug]INPUT_SCRIPT: 'Start-Process -FilePath "D:\a\_temp\SnInstallPfx.exe" -ArgumentList "D:\a\_temp\test.pfx ***"' but it seems to still fail?
Deleteerror MSB3325: Cannot import the following key file: test.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_.......
Delete##[debug]Loaded 11 strings.
##[debug]INPUT_ERRORACTIONPREFERENCE: 'stop'
##[debug]INPUT_SHOWWARNINGS: 'false'
##[debug] Converted to bool: False
##[debug]INPUT_FAILONSTDERR: 'false'
##[debug] Converted to bool: False
##[debug]INPUT_IGNORELASTEXITCODE: 'false'
##[debug] Converted to bool: False
##[debug]INPUT_PWSH: 'false'
##[debug] Converted to bool: False
##[debug]INPUT_WORKINGDIRECTORY: 'D:\a\1\s'
##[debug]Asserting container path exists: 'D:\a\1\s'
##[debug]INPUT_TARGETTYPE: 'inline'
##[debug]INPUT_SCRIPT: ''&"$($ENV:SN_INSTALL_PFX)" "$($ENV:MYCERTIFICATE_PFX)" "$($ENV:MYCERTIFICATE_PFX_PASSWORD)"''
##[debug]INPUT_RUNSCRIPTINSEPARATESCOPE: 'false'
##[debug] Converted to bool: False
Generating script.
##[debug]AGENT_VERSION: '2.187.2'
##[debug]AGENT_TEMPDIRECTORY: 'D:\a\_temp'
##[debug]Asserting container path exists: 'D:\a\_temp'
##[debug]Asserting leaf path exists: 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe'
========================== Starting Command Output ===========================
##[debug]Entering Invoke-VstsTool.
##[debug] Arguments: '-NoLogo -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command ". 'D:\a\_temp\2f524d82-33a6-43b8-a88e-771d86cec62a.ps1'"'
##[debug] FileName: 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe'
##[debug] WorkingDirectory: 'D:\a\1\s'
"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command ". 'D:\a\_temp\2f524d82-33a6-43b8-a88e-771d86cec62a.ps1'"
&"$($ENV:SN_INSTALL_PFX)" "$($ENV:MYCERTIFICATE_PFX)" "$($ENV:MYCERTIFICATE_PFX_PASSWORD)"
##[debug]$LASTEXITCODE is not set.
##[debug]Exit code: 0
##[debug]Leaving Invoke-VstsTool.
##[debug]Leaving D:\a\_tasks\PowerShell_e213ff0f-5d5c-4791-802d-52ea3e7be1f1\2.186.0\powershell.ps1.
Finishing: PowerShell Script copy
Hi.
ReplyDeleteFor the first error, i suspect a formatting error. In yaml, whitespace is very important. The env must be aligned with the t in task, as in my example above.
For the second problem, the output above seem to indicate that the script is never executed. In fact, the error you see, says that the pfx was not installed and the output only echoes the inlined script, not executing it.
From the output it seems that the inline script contains extra quotes around the script. (two apostrophes before and after the script, should only be one)
For reference, I'll provide the same output from my script so you can compare
##[debug]Loaded 11 strings.
##[debug]INPUT_ERRORACTIONPREFERENCE: 'stop'
##[debug]INPUT_SHOWWARNINGS: 'false'
##[debug] Converted to bool: False
##[debug]INPUT_FAILONSTDERR: 'false'
##[debug] Converted to bool: False
##[debug]INPUT_IGNORELASTEXITCODE: 'false'
##[debug] Converted to bool: False
##[debug]INPUT_PWSH: 'false'
##[debug] Converted to bool: False
##[debug]INPUT_WORKINGDIRECTORY: 'D:\a\1\s'
##[debug]Asserting container path exists: 'D:\a\1\s'
##[debug]INPUT_TARGETTYPE: 'inline'
##[debug]INPUT_SCRIPT: '&"$($ENV:SN_INSTALL_PFX)" "$($ENV:IMAGEVAULT_PFX)" "$($ENV:IMAGEVAULT_PFX_PASSWORD)"'
##[debug]INPUT_RUNSCRIPTINSEPARATESCOPE: 'false'
##[debug] Converted to bool: False
Generating script.
##[debug]AGENT_VERSION: '2.187.2'
##[debug]AGENT_TEMPDIRECTORY: 'D:\a\_temp'
##[debug]Asserting container path exists: 'D:\a\_temp'
##[debug]Asserting leaf path exists: 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe'
========================== Starting Command Output ===========================
##[debug]Entering Invoke-VstsTool.
##[debug] Arguments: '-NoLogo -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command ". 'D:\a\_temp\7f7f1972-33a8-4cc9-a78b-5a9aa4859ee0.ps1'"'
##[debug] FileName: 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe'
##[debug] WorkingDirectory: 'D:\a\1\s'
"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Unrestricted -Command ". 'D:\a\_temp\7f7f1972-33a8-4cc9-a78b-5a9aa4859ee0.ps1'"
The key pair has been installed into the strong name CSP key container 'VS_KEY_78CDD94BD2B2D3C2'.
VS_KEY_78CDD94BD2B2D3C2
##[debug]$LASTEXITCODE: 0
##[debug]Exit code: 0
##[debug]Leaving Invoke-VstsTool.
##[debug]Leaving D:\a\_tasks\PowerShell_e213ff0f-5d5c-4791-802d-52ea3e7be1f1\2.186.0\powershell.ps1.
Finishing: PowerShell
What does your yaml script look like (the install pfx script part)?
Hi Dan,
DeleteThank you for the response. My Yaml looks like:
- task: PowerShell@2
env:
SN_INSTALL_PFX: $(snInstallPfx.secureFilePath)
MYCERTIFICATE_PFX: $(myCertificatePfx.secureFilePath)
MYCERTIFICATE_PFX_PASSWORD: $(myCertificatePfxPassword)
inputs:
targetType: 'inline'
script: '&"$($ENV:SN_INSTALL_PFX)" "$($ENV:MYCERTIFICATE_PFX)" "$($ENV:MYCERTIFICATE_PFX_PASSWORD)"'
Sorry formatting in this post is not showing correctly.
The other way I tried was:
Start-Process -FilePath "$(SnInstallPfx.secureFilePath)" -ArgumentList "$(RMS.secureFilePath) $(myCertificatePfxPassword)"
both seem to fail
Well, it looks correct. I cannot help you further. I would suggest that you contact devops support to get help with this issue.
DeleteNice post, I already tried to do this and the certificate is added correctly, but seems that the PowerShell task runs in a different container than the build task and thats why is not recognized.
ReplyDeleteThis is the output of the PowerShell task.
The key pair has been installed into the strong name CSP key container 'VS_KEY_2211CE6F0D92996F'
Then I have a task for the build:
- task: VSBuild@1
displayName: 'Build .csproj file'
inputs:
solution: '$(solution)'
platform: '$(buildPlatform)'
configuration: '$(buildConfiguration)'
And I get for the 2 projects different errors referencing different containers names:
Error MSB3325: Cannot import the following key file: ***cert.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_71452E506F1E61FB
Error MSB3325: Cannot import the following key file: ***cert.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_1789CEF560C2266D
Any clue?
Hi Alex. I am having an identical problem and trying to find a way around it.
DeleteI've used sninstallpfx.exe to install the pfx cert. I am not getting the same message as you but I suspect it might be something very similar. My error is
The key file may be password protected. To correct this, try to import the certificate again or import the certificate manually into the current user’s personal certificate store.
Wondering if you ever found a way around this?
Well, the key point to this problem is that the certificate needs to be installed in a container before the build takes place. Since you have created a container earlier in the pipeline but it was created with a different name than the ones the msbuild process tries to retrieve, then something that the container name is calculated from, has changed.
DeleteThe container name is built upon the contents of the certificate plus the user that runs the build.
If you use the same certificate, then the user running the build must have changed. Make sure that the pipeline tasks not being run in paralell or that the powershell task that installs the certificate and the msbuild process is run in the same context.
Also having the same problem where the container names do not match between the save location and where msbuild is expecting it. Is there any solution for getting this to work on with microsoft agent?
DeleteNon besides ensuring that the user creating the container is the same as the user running msbuild.
Deletehello this is my dockerfile and still get
ReplyDeleteC:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin\Microsoft.Common.CurrentVersion.targets(3222,5): error MSB3325: Cannot import the following key file: igakey.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_1E532C4C0BA55C05 [C:\inetpub\wwwroot\DTOPrestation\DTOPrestation.csproj]
C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin\Microsoft.Common.CurrentVersion.targets(3222,5): error MSB3321: Importing key file "igakey.pfx" was canceled. [C:\inetpub\wwwroot\DTOPrestation\DTOPrestation.csproj]
dockerfile :
FROM mcr.microsoft.com/dotnet/framework/sdk:4.8-windowsservercore-ltsc2019 AS base
ARG source
ENV COMPLUS_NGenProtectedProcess_FeatureEnabled=0
ENV ASPNETCORE_ENVIRONMENT Development
SHELL ["cmd", "/S", "/C"]
RUN powershell -Command $ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue'; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -UseBasicParsing -Uri https://dot.net/v1/dotnet-install.ps1 -OutFile dotnet-install.ps1; ./dotnet-install.ps1 -InstallDir '/Program Files/dotnet' -Channel 6.0 -Runtime aspnetcore; Remove-Item -Force dotnet-install.ps1
SHELL ["powershell", "-Command"]
WORKDIR /app
EXPOSE 80
EXPOSE 8001
FROM mcr.microsoft.com/dotnet/framework/sdk:3.5-20191008-windowsservercore-ltsc2019 AS build
RUN net user mqAdmin /ADD
RUN net localgroup Administrators /add mqAdmin
USER mqAdmin
RUN whoami
SHELL ["cmd", "/S", "/C"]
RUN powershell -Command $ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue'; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12;Invoke-WebRequest -UseBasicParsing -Uri https://dot.net/v1/dotnet-install.ps1 -OutFile dotnet-install.ps1; ./dotnet-install.ps1 -InstallDir '/Program Files/dotnet' -Channel 2.2; Remove-Item -Force dotnet-install.ps1
RUN powershell setx /M PATH '%PATH%;/Program Files/dotnet'
SHELL ["powershell", "-Command"]
WORKDIR /inetpub/wwwroot
COPY ["nuget.config", "."]
COPY ["SAGILEA_WEB/WebApplication/WebApplication.csproj", "SAGILEA_WEB/WebApplication/"]
RUN dotnet restore "SAGILEA_WEB/WebApplication/WebApplication.csproj"
COPY . .
RUN ls
SHELL ["cmd", "/S", "/C"]
RUN powershell -NoProfile -Command \
$Secure_String_Pwd = ConvertTo-SecureString "****** " -AsPlainText -Force ; \
Import-PfxCertificate -FilePath ./CommonDataV2/igaKey.pfx -CertStoreLocation Cert:\LocalMachine\Root -Exportable -Password $Secure_String_Pwd
SHELL ["powershell", "-Command"]
RUN dotnet tool install pfxtool -g
RUN pfxtool import --file ./CommonDataV2/igaKey.pfx --password ****** --scope user
SHELL ["cmd", "/S", "/C"]
RUN powershell -Command $ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue'; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12; Invoke-WebRequest https://github.com/honzajscz/SnInstallPfx/releases/download/0.1.2-beta/SnInstallPfx.exe -OutFile SnInstallPfx.exe
RUN SnInstallPfx.exe ./DTOPrestation/igakey.pfx ******
RUN SnInstallPfx.exe ./CommonDataV2/igaKey.pfx ******
RUN SnInstallPfx.exe ./SAGILEA_WEB/Resources/SagileaWeb.pfx ******
RUN SnInstallPfx.exe ./SEPAVirement/igaKey.pfx ******
RUN SnInstallPfx.exe ./Normes/DSN_Parametrage/DSN_ParamDAL/igaKey.pfx ******
SHELL ["powershell", "-Command"]
WORKDIR /inetpub/wwwroot/SAGILEA_WEB/WebApplication/
RUN msbuild ./WebApplication.csproj /property:Configuration=Release
FROM base AS final
WORKDIR /app
#COPY --from=build /app/publish .
COPY --from=build /inetpub/wwwroot/SAGILEA_WEB/WebApplication/bin/Release/Publish/. .
ENTRYPOINT ["dotnet", "WebApplication.dll"]
#ENTRYPOINT ["WebApplication.exe"]
#ENTRYPOINT ["WebApplication.csprog"]