New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

X509Certificate2: Access Denied opening pfx store, if logged on using PowerShell remoting #14745

Closed
qmfrederik opened this Issue Dec 28, 2016 · 10 comments

Comments

Projects
None yet
6 participants
@qmfrederik
Copy link
Collaborator

qmfrederik commented Dec 28, 2016

Scenario:

You're running a .NET Core program on a remote server. You are connected to that server using PowerShell remoting (i.e. via Enter-PSSession (...)). The remote server is not joined to a domain. In this scenario, the X505Certificate2(string, string) constructor fails to open a .pfx file with an Access Denied error message.

I ended up in this scenario because I'm running a Jenkins agent on a Windows Nano Server installation, and I've launched the Jenkins agent via remoting.

The following program (given you have a valid . pfx file and update the password):

using System;
using System.Security.Cryptography.X509Certificates;

class Program
{
    static void Main(string[] args)
    {
        var cert = new X509Certificate2("certificate.pfx", "password" );
    }
}

results in the following output:

[localhost]: PS C:\Users\jenkins\test2> ..\dotnet\dotnet.exe run
..\dotnet\dotnet.exe :
    + CategoryInfo          : NotSpecified: (:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

Unhandled Exception:

Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: Access denied
   at Internal.Cryptography.Pal.CertificatePal.FilterPFXStore(Byte[] rawData, String password, PfxCertStoreFlags pfxCertStoreFlags)
   at Internal.Cryptography.Pal.CertificatePal.FromBlobOrFile(Byte[] rawData, String fileName, String password, X509KeyStorageFlags keyStorageFlags)
   at System.Security.Cryptography.X509Certificates.X509Certificate..ctor(String fileName, String password, X509KeyStorageFlags keyStorageFlags)
   at System.Security.Cryptography.X509Certificates.X509Certificate2..ctor(String fileName, String password, X509KeyStorageFlags keyStorageFlags)
   at Program.Main(String[] args)

This looks similar to #6629 which was closed because it could not be reproduced.

/cc @bartonjs

@bartonjs

This comment has been minimized.

Copy link
Member

bartonjs commented Feb 2, 2017

@qmfrederik My initial instinct is that the remote context doesn't have a user profile loaded. By not specifying X509KeyStorageFlags.MachineKeySet (which requires some machine-level permissions, such as being an administrator) the private key is trying to be saved into the user profile... which doesn't exist.

Another option may be to set X509KeyStorageFlags.EphemeralKeySet, I don't know if that will bypass the profile loading requirement or not... but my gut instinct says it should.

By the way, does this scenario work (same machines/users/access restrictions) with .NET Framework?

@stephentoub stephentoub added this to the 2.0.0 milestone Apr 12, 2017

@tlbdk

This comment has been minimized.

Copy link

tlbdk commented Apr 13, 2017

We are loading our certificate from a byte array and ran into the same issue running our application under a service account on Windows 2012. The services account does not have a user profile so the below code will fail with a WindowsCryptographicException and "The system cannot find the file specified" that is a more than a bit misleading. I would be nice to if the exception could have said that "The user you are running under does not have a profile to store the certificate in, try using MachineKeySet" or even better just defaulted to a behavior where things just work without special flags and I could blissfully forget everything about the platform details.

var certificate = new X509Certificate2(certificateBytes, password, X509KeyStorageFlags.Exportable);
Application startup exception: Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: The system cannot find the file specified
   at Internal.Cryptography.Pal.CertificatePal.FilterPFXStore(Byte[] rawData, String password, PfxCertStoreFlags pfxCertStoreFlags)
   at Internal.Cryptography.Pal.CertificatePal.FromBlobOrFile(Byte[] rawData, String fileName, String password, X509KeyStorageFlags keyStorageFlags)
   at System.Security.Cryptography.X509Certificates.X509Certificate..ctor(Byte[] rawData, String password, X509KeyStorageFlags keyStorageFlags)
   at System.Security.Cryptography.X509Certificates.X509Certificate2..ctor(Byte[] rawData, String password, X509KeyStorageFlags keyStorageFlags)
   at MobileLife.Authentication.Backend.Common.JwtSigner..ctor(String base64PfxCertificate, String password) in C:\repos\git\wealth-obco-backend\src\MobileLife.Authentication.Backend\Common\JwtSigner.cs:line 25
   at MobileLife.Authentication.Backend.Startup.ConfigureServices(IServiceCollection services) in C:\repos\git\wealth-obco-backend\src\MobileLife.Authentication.Backend\Startup.cs:line 60
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Microsoft.AspNetCore.Hosting.ConventionBasedStartup.ConfigureServices(IServiceCollection services)
   at Microsoft.AspNetCore.Hosting.Internal.WebHost.EnsureApplicationServices()
   at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()

Sadly X509KeyStorageFlags.EphemeralKeySet is not available in 1.1.1 so the fix was to make it use the machine store:

new X509Certificate2(certificateBytes, password, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable);
``` csharp
@bartonjs

This comment has been minimized.

Copy link
Member

bartonjs commented Apr 18, 2017

@tlbdk While we know a couple of things that can cause the exception, we don't know all of them, and we don't know which one tripped. All we know is we called crypt32!PFXImportCertStore and it failed, signalling ERROR_FILE_NOT_FOUND. (Actually, we don't even "know" that much, we know it failed, and so we threw an exception whose message was Windows message API value for the value Win32GetLastError()).

Trying to reinterpret the internal state of the PFX loader based off of just the HRESULT isn't something that I think I'd be comfortable with.

@tlbdk

This comment has been minimized.

Copy link

tlbdk commented Apr 19, 2017

I understand the issue with interpreting the errors, but from an cross platform API perspective it kind of sucks. There will be a lot of know error scenarios on Windows with rather wierd limitations, fx the profile requirements, key store naming, etc. That I guess will be non existent on other platforms where Openssl is used instead.

So if wrapping crypt32 in a nice way is out of the question, then allowing to use openssl on all platforms might be a better option, would that be possible?

@bartonjs

This comment has been minimized.

Copy link
Member

bartonjs commented Apr 19, 2017

then allowing to use openssl on all platforms might be a better option, would that be possible

Not... really. The problem stems from X509Certificate2 not only exposing, but accepting, an IntPtr. On Windows we assume that this is a PCERT_CONTEXT; on Linux an OpenSSL X509*, and on macOS a SecCertificateRef or SecIdentityRef.

It also adds a world of pain when certs are passed to SslStream or X509Store... both of those are (for the most part) system resources that expect pointers.

If you come up with a clever idea for integrating it I'd be amenable.

I understand the issue with interpreting the errors, but from an cross platform API perspective it kind of sucks.

Yeah; this is, unfortunately, one of the areas where you need to be cognizant of the underlying system.

Windows:

  • Storage
    • MachineKeySet: Requires admin rights
    • UserKeySet: Requires a loaded user profile
    • (not asserting either): Does what the PFX says to do, so has either the User or Machine requirements, depending.
  • Persistence
    • PersistKeySet: Key material never gets deleted (aka it's persisted)
    • EphemeralKeySet: Nothing gets written down (yay), may fail to interoperate with things that expect persisted key references (boo)
    • (not asserting either): Still requires a writeable filesystem with the storage requirements above.

Linux:

  • None of these bits mean anything, everything's ephemeral.

macOS:

  • EphemeralKeySet: Throws, Apple doesn't support an ephemeral cert+key pair.
@karelz

This comment has been minimized.

Copy link
Member

karelz commented Apr 21, 2017

Looks like there is nothing reasonable we can do in .NET Core. Please let us know if you have evidence otherwise.

@karelz karelz closed this Apr 21, 2017

@tlbdk

This comment has been minimized.

Copy link

tlbdk commented Apr 21, 2017

@bartonjs thanks for the answer and see your point, but it would be nice if that bullet point list would be added to the documentation as it would save other people the guess work.

@karelz

This comment has been minimized.

Copy link
Member

karelz commented Apr 21, 2017

Do you have suggestion for the documentation changes? Which pages would you change?
Could you please submit PR (in dotnet/docs repo) and tag @bartonjs to review your proposed wording? Thanks!

@tlbdk

This comment has been minimized.

Copy link

tlbdk commented Apr 24, 2017

@karelz Had a look at the docs repo and I'm a bit lost on where to put the documentation, it's seems most of the information is in xml/System.Security.Cryptography.X509Certificates/X509Certificate2.xml but I guess this is generated from the sources, so is this the base place to add it?

What I think would be nice in the documentation is to explain that X509Certificate2, SslStream, HttpClient, etc. are just thin wrappers over the native implementations and that there are some limitations on each platform as @bartonjs noted. Fx as part of adding support for client certificates in my application I ran into an issue on Mac because the default libcurl that HttpClient is wrapping does not support overwriting ServerCertificateCustomValidationCallback. This is because the underlaying ssl implementation only supports files on disk or labels for they keychain, something that has not been implemented in corefx yet(I will make another issue for this).

I would be more than happy to writing something, but I might be that this would be better in a blog entry or in the howto section, what do you think?

@karelz

This comment has been minimized.

Copy link
Member

karelz commented Apr 24, 2017

I let @bartonjs decide what is the appropriate level of documentation we should have in docs.

@mairaw can help point where/how to change it. AFAIK nothing in docs repo is generated from source. It is the primary source of docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment