Certificate private key permissions in .NET 6 - c#

I'm trying to import a certificate with private key into the Windows Certificate Store. I can successfully import the certificate using the below
X509Certificate2 certificate = new(certByteArray, certPassword, X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet);
X509Store store = new(StoreName.TrustedPeople, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadWrite);
store.Add(certificate);
But the problem I've got is, how to give a user access to the private key programmatically.
I've found these links helpful:
https://www.pkisolutions.com/accessing-and-using-certificate-private-keys-in-net-framework-net-core/
CngKey Assign permission to machine key
Set Certificate PrivateKey Permissions in .NET 5
I can grant access via the UI with certlm.msc > Drag certificate to Personal store > Right click certificate > All Tasks > Manage private keys > Add the user and permission
But I need to do this programmatically
There are changes from .NET Full Framework which is where the examples come from. I've spent more than a day on it, tried multiple certificates, certificate is definitely marked as exportable and running VS as administrator. I'm happy with a Windows only solution
This is about as close as I've got
const string NCRYPT_SECURITY_DESCR_PROPERTY = "Security Descr";
const CngPropertyOptions DACL_SECURITY_INFORMATION = (CngPropertyOptions)4;
X509Store trustedPeopleStore = new(StoreName.TrustedPeople, StoreLocation.LocalMachine);
trustedPeopleStore.Open(OpenFlags.ReadWrite);
var certificates = trustedPeopleStore.Certificates.Find(X509FindType.FindByThumbprint, "xxxxxxxxxxxxxxxxxxxxxx", false);
RSA rsa = certificates[0].GetRSAPrivateKey();
RSACng rsaCng = rsa as RSACng;
CngProperty prop = rsaCng.Key.GetProperty(NCRYPT_SECURITY_DESCR_PROPERTY, DACL_SECURITY_INFORMATION);
I can see the rsaCng.Key present in debug, but it fails on the next line (it definitely is exportable) getting the property with
Internal.Cryptography.CryptoThrowHelper.WindowsCryptographicException: 'Key not valid for use in specified state.'
I've also read comments that you shouldn't try setting the acl directly on the file, but not sure if that is correct or not

See this code project post for some example code that grants access programmatically (specifically look at the "AddAccessToCertificate" method).
Check this for more info: Programmatically adding certificate to personal store

Related

Identityserver fails to load selfsigned certificate

I'm trying to set a Certificate for identityserver and it keeps failing with a "no access to private key error".
Taking it out of identityserver, the following code throws an access denied error
static X509Certificate2 GetCertificateFromDisk()
{
using (var stream = File.Open(#"patht-to-pfx", FileMode.Open))
{
var cert = new X509Certificate2(ReadStream(stream), "password", X509KeyStorageFlags.MachineKeySet);
return cert;
}
}
When running the code as administrator it works fine, not when running it under my own account. Eventually I want to run it as localsystem.
I even added 'Everyone' under the certificates private key permissions in my local computer certificate store,
screenprint here
... still I get the exception.
What is wrong here? Going Crazy about it
Update
Great tips from CryptoGuy in the answer below. Important note: Opening the file is not correct only Identityserver3 still failed when getting the certificate from the store. What made it work was to regenerate the certificate using Keith Sparkjoy's tool SerfCert. My previous certificate was generated using powershell. So keep in mind that powershell certificates have issues with accessibility of private key. Thanks to Keith for the tool!
There are few things to consider.
1) you are performing write access to Local Machine store. X509KeyStorageFlags.MachineKeySet attempts to save private key to Local Machine store. Therefore, you need administrator permissions to write there. You should remove this flag to perform read-only access
2)
Documentation says that adding permissions in MMC (manage private key-option on a certificate) should allow this, but it doesn't seem to work
it works on an already saved private keys.
What you really should do is to import certificate and private key to Local Machine store and then configure your application to reference installed certificate.
3) if your application runs under unpriveleged account and the key don't need to be shared, then you should use Current User store.

Cannot import certificate pfx with private key to store from .net c# 3.5 on windows server 2012

I am attempting to load a certificate with private key from a pfx file and import it to the LocalMachine/My (Personal) certificate store. My code works fine except that when I view the certificate in the store it says
"The associated private key cannot be found"
and further, certutil says
"Cannot find the certificate and private key for decryption"
The strange part is that my code works fine on Windows 7 development box but not on Windows Server 2008 R2 or 2012. Also strange that if I manually import the pfx file using mmc, the private key seems to persist properly.
Here is the code I am using to load the file and import :
// load the certificate from pfx file
X509Certificate2 cert = new X509Certificate2(filePath, pfxPassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
// import to the store
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadWrite); //tried MaxAllowed as well
store.Add(caCert);
store.Close();
Any ideas? For background info, I am also generating the certificate in code at an earlier step using BouncyCastle. I ran into some problems persisting the private key during that step but was solved with the answer from this question. Also the code that is attempting the import is running as administrator.
A little time away from this problem helped me refine my investigation. I tracked down the private key file for the imported certificate in C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys
and found that the file had no owner and no permissions set (not even for Administrators or System). Modifying these permissions manually caused the "associated private key" message to go away when viewing the cert in the store.
That led me to modify my code to set permissions on the private key before attempting to import it into the store :
https://stackoverflow.com/a/4902009/332610
hooray!

Adding a private key to X509Certificate2 - C#

I'm new to 2WaySSL.
What I'm trying to do:
1. Retrieve a Client certificate which is stored in a certificate store,
I'm managing to do that by such code:
certificates = store.Certificates.Find(X509FindType.FindBySerialNumber, serialNumber, false);
When attaching the certificate to the request, the authentication by the server fails,
since I the certificate object does not contain the private key.
I'm trying to find a way of associating the private key to the certificate (in c#),
Once I'll retrieve the key from where it's stored.
something like:
certificate.PrivateKey = key;
But I found no easy way of either initiating the key object, or assigning it to the certificate without getting some exception, even when the key is null, I'm getting an access denied exception.
Any help , especially followed by a code sample, would be appreciated.
AFter .NET 4.7.2, If you have the certificate with only public key and an RSA private key handy you can perform the below code
certificate= certificate.CopyWithPrivateKey(key);
you have to import System.Security.Cryptography.X509Certificates;
You have 2 easy options
import certificate with private key (usually a pfx file) to certificate store. Then store.Certificates.Find will retrieve the certificate with private key.
Load pfx file into X509Certificate2 istance with one of its constructors new X509Certificate2(pfx_filename, password_to_pfx)

Netsh error 1312 after programatically adding certificate to the store

I'm trying to add a certificate to the store programatically using the following code:
var certPath = string.Format("{0}//{1}", Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),"fileName.pfx");
var cert = new X509Certificate2(certPath, "Password");
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadWrite);
store.Add(cert);
store.Close();
I check in MMC and the certificate is added.
If I now run in a command prompt with admin privileged:
netsh http add sslcert ipport=0.0.0.0:<port> certhash=<Thumbnail> appid={00000000-0000-0000-0000-000000000000}
Then it throws a 1312 error, "A specified log-on session does not exist. It may already have been terminated."
If I add the certificate via the import function in MMC, then the above command works.
Can anyone please help?
The issue is the way in which windows is storing the private key.
To do this programatically in .Net, change the following line of code:
X509Certificate2 cert = new X509Certificate2(path, "password",
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
As per this question: Inserting Certificate (with privatekey) in Root, LocalMachine certificate store fails in .NET 4
We ended up using WIX to inject the certificate into the store on installation. It seemed to work nicely.

Why does X509Certificate2 sometimes fail to create from a blob?

I have an ASP.NET web service which is receiving a byte array representing the contents of a .pfx file containing an X.509 certificate. The server-side code is using the System.Security.Cryptography.X509Certificate2 constructor to load the certificate from the bytes:
X509Certificate2 native_cert = new X509Certificate2(
pkcs12_buf /*byte array*/,
password,
X509KeyStorageFlags.PersistKeySet |
X509KeyStorageFlags.Exportable
);
Depending on who my service process is running as, this call will either succeed, or fail with an "internal error" exception. The last call on the exception stack is to X509Utils._LoadCertFromBlob, which is unmanaged code in mscore.dll.
This code succeeds when run from a console application in an interactive login using the service account's credentials. It fails when running under w3wp.exe in an application pool that uses the service account's credentials. Changing the app pool identity to an administrator fixes the problem, so it must be a privilege issue, but I have no idea what privilege could be necessary for this. The code does not touch either the filesystem or the Windows certificate stores.
[UPDATE: More Info]
This error appears in the Windows Event Log:
*Cryptographic Parameters:*
**Provider Name:** Microsoft Software Key Storage Provider
**Algorithm Name:** Not Available.
**Key Name:** {E182E13B-166D-472A-A24A-CBEF0808E9ED}
**Key Type:** User key.
*Cryptographic Operation:*
**Operation:** Open Key.
**Return Code:** 0x2
Any ideas?
I just found the solution to this one myself:
X509KeyStorageFlags flags = X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.MachineKeySet;
X509Certificate2 cert = new X509Certificate2(pkcs12_buf, password, flags);
The trick here is to use the local key store MachineKeySet flag instead of the user profile key store, which is the default if you don't specify an alternative location. Because the ASP.NET process identity doesn't load the user profile store, you can't access the store when importing a certificate programmatically, but you can access the machine store.
I think PersistKeySet just keeps the private key loaded, but I'm not sure exactly what it does - it's required if you need to access the private key for some reason though.
Try granting the ASP.NET account permissions to the following folder: C:\Documents And Settings\All Users\Microsoft\Crypto\RSA\MachineKeys\ (may vary according to your environment)

Categories

Resources