Skip to content
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

Loading PFX without admin causes CryptographicException #110217

Open
timmac-qmc opened this issue Nov 27, 2024 · 18 comments
Open

Loading PFX without admin causes CryptographicException #110217

timmac-qmc opened this issue Nov 27, 2024 · 18 comments

Comments

@timmac-qmc
Copy link

timmac-qmc commented Nov 27, 2024

Description

After upgrading to .Net 9.0 on a API project the following codes throws the error "System.Security.Cryptography.CryptographicException: 'Access denied.'" when not run as Admin. This works fine without admin on .net 8.0.

builder.WebHost.UseKestrel(options =>
{
    options.ListenAnyIP(443, builder =>
    {
            builder.UseHttps("D:\\private_cert.pfx", "password");
    });
});

Reproduction Steps

  1. Create a new .Net 9.0 API project.
  2. In Program.cs load a certifcate using the code provided

Expected behavior

Project runs without issue

Actual behavior

Exception thrown: System.Security.Cryptography.CryptographicException: 'Access denied.'

 	System.Security.Cryptography.dll!System.Security.Cryptography.X509Certificates.X509CertificateLoader.ImportPfx(System.ReadOnlySpan<byte> data, System.ReadOnlySpan<char> password, System.Security.Cryptography.X509Certificates.X509KeyStorageFlags keyStorageFlags)	Unknown
 	System.Security.Cryptography.dll!System.Security.Cryptography.X509Certificates.X509CertificateLoader.LoadPkcs12NoLimits(System.ReadOnlyMemory<byte> data, System.ReadOnlySpan<char> password, System.Security.Cryptography.X509Certificates.X509KeyStorageFlags keyStorageFlags, ref System.Security.Cryptography.X509Certificates.X509CertificateLoader.Pkcs12Return earlyReturn)	Unknown
 	System.Security.Cryptography.dll!System.Security.Cryptography.X509Certificates.X509CertificateLoader.LoadPkcs12(System.ReadOnlyMemory<byte> data, System.ReadOnlySpan<char> password, System.Security.Cryptography.X509Certificates.X509KeyStorageFlags keyStorageFlags, System.Security.Cryptography.X509Certificates.Pkcs12LoaderLimits loaderLimits)	Unknown
 	System.Security.Cryptography.dll!System.Security.Cryptography.X509Certificates.X509CertificateLoader.LoadFromFile<System.Security.Cryptography.X509Certificates.X509CertificateLoader.Pkcs12Return>(string path, System.ReadOnlySpan<char> password, System.Security.Cryptography.X509Certificates.X509KeyStorageFlags keyStorageFlags, System.Security.Cryptography.X509Certificates.Pkcs12LoaderLimits loaderLimits, System.Security.Cryptography.X509Certificates.X509CertificateLoader.LoadFromFileFunc<System.Security.Cryptography.X509Certificates.X509CertificateLoader.Pkcs12Return> loader)	Unknown
 	System.Security.Cryptography.dll!System.Security.Cryptography.X509Certificates.X509CertificateLoader.LoadPkcs12PalFromFile(string path, System.ReadOnlySpan<char> password, System.Security.Cryptography.X509Certificates.X509KeyStorageFlags keyStorageFlags, System.Security.Cryptography.X509Certificates.Pkcs12LoaderLimits loaderLimits)	Unknown
 	System.Security.Cryptography.dll!System.Security.Cryptography.X509Certificates.CertificatePal.FromBlobOrFile(System.ReadOnlySpan<byte> rawData, string fileName, Microsoft.Win32.SafeHandles.SafePasswordHandle password, System.Security.Cryptography.X509Certificates.X509KeyStorageFlags keyStorageFlags)	Unknown
 	System.Security.Cryptography.dll!System.Security.Cryptography.X509Certificates.X509Certificate.X509Certificate(string fileName, string password, System.Security.Cryptography.X509Certificates.X509KeyStorageFlags keyStorageFlags)	Unknown
 	Microsoft.AspNetCore.Server.Kestrel.Core.dll!Microsoft.AspNetCore.Hosting.ListenOptionsHttpsExtensions.UseHttps(Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions listenOptions, string fileName, string password)	Unknown
>	Program.<Main>$.AnonymousMethod__0_18(Microsoft.AspNetCore.Server.Kestrel.Core.ListenOptions builder) Line 52	C#

Regression?

Yes, works on .Net 8.0

Known Workarounds

No response

Configuration

.Net 9.0
Windows 11 26100.2314 x64

Other information

No response

@dotnet-policy-service dotnet-policy-service bot added the untriaged New issue has not been triaged by the area owner label Nov 27, 2024
Copy link
Contributor

Tagging subscribers to this area: @dotnet/area-system-security, @bartonjs, @vcsjones
See info in area-owners.md if you want to be subscribed.

@rabotond
Copy link

I am facing the same. Works on .net8 but exception on .net9. Running it with admin mode solves it.

@barrie-poultryplan
Copy link

Same issue here.

@jimitndiaye
Copy link

Same issue here: dotnet/aspnetcore#59300

@jeffhandley jeffhandley added this to the 9.0.x milestone Dec 5, 2024
@dotnet-policy-service dotnet-policy-service bot removed the untriaged New issue has not been triaged by the area owner label Dec 5, 2024
@jeffhandley
Copy link
Member

@krwq Please investigate this. The repro in dotnet/aspnetcore#59300 looks like a good way to get started.

@DierkDroth
Copy link

DierkDroth commented Dec 8, 2024

I have a different scenario which is running into trouble with .NET9 and which is failing for potentially the same reason.

I understand that

X509Certificate2 certificate = new X509Certificate2(byte[] data, string password)

... now is obsolete with .NET9, which is why I replaced it by

X509Certificate2 certificate = X509CertificateLoader.LoadPkcs12(byte[] data, string password)

The certificate itself is used by the Kestrel server:

builder.WebHost.ConfigureKestrel(kestrel =>
{
	kestrel.ListenAnyIP(port, portOptions =>
	{
		portOptions.UseHttps(certificate);
	});
});

This works fine if the Kestrel server is running on the target (Linux) server. However, for development and debugging purposes I'm running the Kestrel server on my local Windows 11 machine as well where a SSH script reroutes the remote server port to my local machine.

Code above only works on my local machine if I'm running Visual Studio as administrator. The local Kestrel server can't be reached if running as non-admin.

Can you please confirm/deny that the source of trouble is the same as reports above?
Thanks in advance

@krwq
Copy link
Member

krwq commented Dec 9, 2024

@DierkDroth it's likely related to the same piece of code but IMO failing for different reason - it will be beneficial if you could share steps how to create cert similar to yours.

The simple repro for this issue is just this (ASP.NET is not needed):
new X509Certificate2("cert.pfx", "1234") (creating cert as described in dotnet/aspnetcore#59300) and it can be worked around simply with X509CertificateLoader.LoadPkcs12(File.ReadAllBytes("cert.pfx"), "1234"). I noticed that this is another way to repro:

Pkcs12LoaderLimits limits = new Pkcs12LoaderLimits()
{
    PreserveStorageProvider = true // true => seeing access denied; false => works
};

X509CertificateLoader.LoadPkcs12(File.ReadAllBytes("cert.pfx"), "1234", loaderLimits: limits);

which seems inconsistent with what you're describing. I'd be still interested in seeing steps how to create a cert you're describing. We also noticed that for some reason this didn't repro for everyone.

This most likely regressed with: #107005 but it was also fixing another issue so we need to figure out solution which makes this work with both scenarios and most likely your test case as well (and therefore my request to share steps to create your cert). With that PR reverted locally I wasn't able to repro this problem anymore but I've additionally seen some more tests failing locally with Access Denied (but it was less tests failing with this reverted) and that doesn't repro on CI for some reason. Given our main expert in this area @bartonjs is out of office and holidays season approaching I'm not sure if we will be able to make any fix this year but collecting more information and test cases will be very valuable to fix this correctly in the next iteration.

@DierkDroth
Copy link

DierkDroth commented Dec 9, 2024

@krwq additional details:

  • I don't need to apply Pkcs12LoaderLimits to reproduce the issue
  • I get the exception as reported by the OP when not replacing the obsolete code but going with the X509Certificate2.ctor
  • I don't get any exception at all when replacing the obsolete code as per my post. Not even Kestrel throws any exception, it's just not working.
    Please let me know if you needed anything else.

I can't comment on how this regression was introduced to the .NET code, since I'm not part of the .NET9 dev team

@krwq
Copy link
Member

krwq commented Dec 9, 2024

@DierkDroth thanks for clarification - when you say it doesn't work on Windows does Kestrel log look normal as if it started correctly but it doesn't let you connect?

How did you create a certificate a pfx/PKCS12 you're passing in?

@DierkDroth
Copy link

@krwq sorry I'm neither a Kestrel expert nor a certificate expert. I did not notice any Kestrel errors nor logs in VS. The certificate is a PFX format certificate which I loaded as embedded resource to a C# byte array (not sure why that would be relevant though...).

@krwq
Copy link
Member

krwq commented Dec 9, 2024

@DierkDroth pfx can be created in multiple ways and format has some slight variations between implementations. Knowing how you created it (i.e. command line) will tell us more what we need to fix.

@DierkDroth
Copy link

@krwq I purchased the PFX from a certificate provider. Unfortunately I don't recall the details.

@dstj
Copy link

dstj commented Jan 1, 2025

@krwq

I tried upgrading to .NET 9 and am facing the exact same issue. Here is how the certificate is created and loaded:

appsettings.Development.json:

	"Kestrel": {
		"Certificates": {
			"Default": {
				"Path": "../data/certs/wildcard.local.pfx",
				"Password": "P@ssw0rd",
				"AllowInvalid": true
			}
		}
	},

Local dev wildcard SSL certificate

  • Create Certificate from Powershell

    $devCert = New-SelfSignedCertificate -CertStoreLocation Cert:\LocalMachine\My -Subject "*.mydomain.local" -DnsName "mydomain.local", "*.mydomain.local" -FriendlyName "LocalWildcard" -NotAfter (Get-Date).AddYears(10)
    
    Export-PfxCertificate -Cert $devCert -FilePath 'C:\dev\data\certs\wildcard.mydomain.local.pfx' -Password (ConvertTo-SecureString -AsPlainText 'P@ssw0rd' -Force)
    
    wsl
    cd /mnt/c/dev/data/certs
    openssl pkcs12 -in wildcard.mydomain.local.pfx -nocerts -nodes -out mydomain.local.key
    openssl pkcs12 -in wildcard.mydomain.local.pfx -nokeys -out mydomain.local.crt
  • Configure permissions in Windows Certificate Store

    1. Goto to Run and type mmc.exe.
    2. Click on files and then Add Certificates Snap-In
    3. Click on Certificates and Add. Select Computer Account and then Local Computer and Finish.
    4. Now go into the Certificate Store you added. Go to Personal then Certicicates and you can see mydomain.local.
    5. Right click, go to All Tasks / Manager Private Keys. Add Users with "Read Access".
    6. Copy this certificate into Trusted People / Certificates

Previous "Access Denied" resolution (but not working here)

You need Read/Write on Everyone. See MS documentation and Stack Overflow Post, otherwise you will get the following error in the console when running the Web Server when running as non-Admin:

System.Security.Cryptography.CryptographicException: Access denied.
   at System.Security.Cryptography.X509Certificates.CertificatePal.FilterPFXStore(ReadOnlySpan`1 rawData, SafePasswordHandle password, PfxCertStoreFlags pfxCertStoreFlags)
   at System.Security.Cryptography.X509Certificates.CertificatePal.FromBlobOrFile(ReadOnlySpan`1 rawData, String fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
   at System.Security.Cryptography.X509Certificates.X509Certificate..ctor(String fileName, String password, X509KeyStorageFlags keyStorageFlags)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Certificates.CertificateConfigLoader.LoadCertificate(CertificateConfig certInfo, String endpointName)
   at Microsoft.AspNetCore.Server.Kestrel.Core.TlsConfigurationLoader.LoadDefaultCertificate(ConfigurationReader configurationReader)
   ...

But the current .NET 9 error is:

"Exception": "System.Security.Cryptography.CryptographicException: Access denied.
   at System.Security.Cryptography.X509Certificates.X509CertificateLoader.ImportPfx(ReadOnlySpan`1 data, ReadOnlySpan`1 password, X509KeyStorageFlags keyStorageFlags)
   at System.Security.Cryptography.X509Certificates.X509CertificateLoader.LoadPkcs12NoLimits(ReadOnlyMemory`1 data, ReadOnlySpan`1 password, X509KeyStorageFlags keyStorageFlags, Pkcs12Return& earlyReturn)
   at System.Security.Cryptography.X509Certificates.X509CertificateLoader.LoadPkcs12(ReadOnlyMemory`1 data, ReadOnlySpan`1 password, X509KeyStorageFlags keyStorageFlags, Pkcs12LoaderLimits loaderLimits)
   at System.Security.Cryptography.X509Certificates.X509CertificateLoader.LoadFromFile[T](String path, ReadOnlySpan`1 password, X509KeyStorageFlags keyStorageFlags, Pkcs12LoaderLimits loaderLimits, LoadFromFileFunc`1 loader)
   at System.Security.Cryptography.X509Certificates.X509CertificateLoader.LoadPkcs12PalFromFile(String path, ReadOnlySpan`1 password, X509KeyStorageFlags keyStorageFlags, Pkcs12LoaderLimits loaderLimits)
   at System.Security.Cryptography.X509Certificates.CertificatePal.FromBlobOrFile(ReadOnlySpan`1 rawData, String fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
   at System.Security.Cryptography.X509Certificates.X509Certificate..ctor(String fileName, String password, X509KeyStorageFlags keyStorageFlags)

"Workarounds"

  1. When running my web server as admin, no error.

OR

Non-admin by loading from the Certificate Store in appsettings.Development.json:

	"Kestrel": {
		"Certificates": {
			"Default": {
				"Store": "My",
				"Location": "LocalMachine",
				"Subject": "*.mydomain.local",
				"Password": "P@ssw0rd",
				"AllowInvalid": true
			}
		}
	},

@kerenor23
Copy link

same issue, but when I was trying to create a self-signed cert.
I have the following piece of code, which worked fine on .NET8

            X509Certificate2? newCert = null;
            using (RSA parent = RSA.Create(2048))
            {
                CertificateRequest parentReq = new CertificateRequest("cn=SelfSignedName", parent, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);

                parentReq.CertificateExtensions.Add(item: new X509BasicConstraintsExtension(certificateAuthority: false, hasPathLengthConstraint: false, pathLengthConstraint: 0, critical: false));
                parentReq.CertificateExtensions.Add(item: new X509SubjectKeyIdentifierExtension(key: parentReq.PublicKey, critical: false));

                using (X509Certificate2 parentCert = parentReq.CreateSelfSigned(TimeProvider.GetUtcNow().AddDays(-5), TimeProvider.GetUtcNow().AddDays(365)))
                {
                    newCert = new X509Certificate2(parentCert.Export(X509ContentType.Pkcs12), string.Empty, 
X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
                }
                return newCert;
            }

Currently on .NET9, creation of newCert fails with the following stack -

StackTrace:System.Security.Cryptography.CryptographicException: Access denied.
   at SafeCertStoreHandle System.Security.Cryptography.X509Certificates.X509CertificateLoader.ImportPfx(ReadOnlySpan<byte> data, ReadOnlySpan<char> password, X509KeyStorageFlags keyStorageFlags)
   at void System.Security.Cryptography.X509Certificates.X509CertificateLoader.LoadPkcs12NoLimits(ReadOnlyMemory<byte> data, ReadOnlySpan<char> password, X509KeyStorageFlags keyStorageFlags, ref Pkcs12Return earlyReturn)
   at Pkcs12Return System.Security.Cryptography.X509Certificates.X509CertificateLoader.LoadPkcs12(ReadOnlyMemory<byte> data, ReadOnlySpan<char> password, X509KeyStorageFlags keyStorageFlags, Pkcs12LoaderLimits loaderLimits)
   at ICertificatePal System.Security.Cryptography.X509Certificates.X509CertificateLoader.LoadPkcs12Pal(ReadOnlySpan<byte> data, ReadOnlySpan<char> password, X509KeyStorageFlags keyStorageFlags, Pkcs12LoaderLimits loaderLimits)
   at CertificatePal System.Security.Cryptography.X509Certificates.CertificatePal.FromBlobOrFile(ReadOnlySpan<byte> rawData, string fileName, SafePasswordHandle password, X509KeyStorageFlags keyStorageFlags)
   at new System.Security.Cryptography.X509Certificates.X509Certificate(byte[] rawData, string password, X509KeyStorageFlags keyStorageFlags)

I verified that running it as admin solves this but sadly, I cannot run this code as admin as the nominal flow. Any other workarounds?

@LukasHazlehurst-Xennox
Copy link

I was able to get the file method working by converting the pfx file to a p12 file.

This approach was mentioned in on this comment https://stackoverflow.com/a/73095429.

I don't understand enough about the pfx/p12 difference to know whether this is a good approach or not but it resolved my problem without changing system folder permissions and without running as admin.

.\openssl pkcs12 -in c:\certs\cert.pfx -out c:\certs\cert.pem -passin pass:FooBlaa -passout pass:FooBlaa
.\openssl pkcs12 -export -in c:\certs\cert.pem -out c:\certs\cert.p12 -passin pass:FooBlaa -passout pass:FooBlaa

Update the path to reference the cert.p12 file.

{
  "Kestrel": {
    "EndPoints": {
      "Https": {
        "Url": "https://thehostname:7269"
      }
    },
    "Certificates": {
      "Default": {
        "Path": "C:\\certs\\cert.p12",
        "Password": "FooBlaa"
      }
    }
  }
}

The app then ran ok without admin privileges and used the specified cert in the HTTPS connection.

In case it's relevant in terms of reproducing the error - we run as non admin users and use a distinct account to elevate to admin.

@krwq
Copy link
Member

krwq commented Feb 7, 2025

AFAIK p12 and pfx should be both PKCS#12. The only reason I can see this could help is re-export - I'm guessing something changed in the inner structure which causes it to now work.

@ykarpeev
Copy link

ykarpeev commented Feb 7, 2025

In my case I was generating a self signed certificate in code and changing below in my cert generation method solved the issue.

return new X509Certificate2(certificate.Export(X509ContentType.Pfx, password), password, X509KeyStorageFlags.MachineKeySet);

to

return X509CertificateLoader.LoadPkcs12(certificate.Export(X509ContentType.Pfx, password), password);

@gautamdsheth
Copy link

gautamdsheth commented Mar 13, 2025

@krwq - any update on this ?

We have this code, works fine in .NET 8 (admin and normal mode).
Moving to .NET 9 , it works in admin mode. In normal mode , getting access denied.

Edit: It works fine on MacOS , is specific to Windows

internal static X509Certificate2 CreateSelfSignedCertificate(string commonName, string country, string state, string locality, string organization, string organizationUnit, SecureString password, string friendlyName, DateTimeOffset from, DateTimeOffset to, string[] sanNames = null)
{
	SubjectAlternativeNameBuilder sanBuilder = new SubjectAlternativeNameBuilder();
	if (sanNames != null)
	{
		foreach (var sanName in sanNames)
		{
			sanBuilder.AddDnsName(sanName);
		}
	}
	else
	{
		sanBuilder.AddDnsName("localhost");
		sanBuilder.AddDnsName(Environment.MachineName);
	}

	var x500Values = new List<string>();
	if (!string.IsNullOrWhiteSpace(commonName)) x500Values.Add($"CN={commonName}");
	if (!string.IsNullOrWhiteSpace(country)) x500Values.Add($"C={country}");
	if (!string.IsNullOrWhiteSpace(state)) x500Values.Add($"S={state}");
	if (!string.IsNullOrWhiteSpace(locality)) x500Values.Add($"L={locality}");
	if (!string.IsNullOrWhiteSpace(organization)) x500Values.Add($"O={organization}");
	if (!string.IsNullOrWhiteSpace(organizationUnit)) x500Values.Add($"OU={organizationUnit}");

	string distinguishedNameString = string.Join("; ", x500Values);

	X500DistinguishedName distinguishedName = new X500DistinguishedName(distinguishedNameString);

	using (RSA rsa = RSA.Create(2048))
	{                
		var request = new CertificateRequest(distinguishedName, rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);

		request.CertificateExtensions.Add(
			new X509KeyUsageExtension(X509KeyUsageFlags.DataEncipherment | X509KeyUsageFlags.KeyEncipherment | X509KeyUsageFlags.DigitalSignature, false));

		request.CertificateExtensions.Add(
		   new X509EnhancedKeyUsageExtension(
			   new OidCollection { new Oid("1.3.6.1.5.5.7.3.1") }, false));

		request.CertificateExtensions.Add(sanBuilder.Build());

		var certificate = request.CreateSelfSigned(from, to);

		if (Platform.IsWindows)
		{
			certificate.FriendlyName = friendlyName;
		}

		return new X509Certificate2(certificate.Export(X509ContentType.Pfx, password), password, X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
	}
}

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

No branches or pull requests