Error "Keyset does not exist" from creating a RSACryptoServiceProvider - c#

My code
csp = new RSACryptoServiceProvider(cspParms);
causes the error: "Keyset does not exist".
string keyContainerName = "abcd";
CspParameters cspParms = new CspParameters(24, "Microsoft Enhanced RSA and AES Cryptographic Provider", keyContainerName);
cspParms.Flags = CspProviderFlags.UseExistingKey | CspProviderFlags.UseMachineKeyStore;
RSACryptoServiceProvider csp = null;
csp = new RSACryptoServiceProvider(cspParms);
csp = new RSACryptoServiceProvider(cspParms);
causes error: "Object already exists".
Then I added the three lines below before calling
csp = new RSACryptoServiceProvider(cspParms);
CryptoKeyAccessRule rule = new CryptoKeyAccessRule("everyone", CryptoKeyRights.FullControl, AccessControlType.Allow);
cspParms.CryptoKeySecurity = new CryptoKeySecurity();
cspParms.CryptoKeySecurity.SetAccessRule(rule);
Then I got the error: "Keyset does not exist".
Even I removed the added three lines, I still got the error: Keyset does not exist.

I found the reason.
If I run as an admin, then there is no these error.
I was using windows 10, and I made everyone could read/write my users folder. But recently my windows was auto upgraded to win 11. The security setting for the users changed, this caused the trouble.

Related

Signing PDF document by certificate

Im trying digitaly sign PDF document with Syncfusion. (Library for generating pdf document)
Entire document, not just field.
I don't know mutch about signing or certificates. I know that certificate is type of HSM, so i used:
Syncfusion - Externally sign a pdf document
It works on my development PC, but doesn't in production. Certificate is find, but at signing document it causes:
CryptographicException: Unknown error „-1073741823“ at System.Security.Cryptography.Pkcs.SignedCms.Sign(CmsSigner signer, Boolean silent)
My code:
using Syncfusion.Licensing;
using Syncfusion.Pdf;
using Syncfusion.Pdf.Graphics;
using Syncfusion.Pdf.Parsing;
using Syncfusion.Pdf.Security;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.Pkcs;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
namespace SyncfusionTest3
{
class Program
{
public static X509Certificate2 infoCertifikat = null;
static void Main(string[] args)
{
var store = new System.Security.Cryptography.X509Certificates.X509Store(StoreLocation.LocalMachine);
string thumbprint = "9F.."; //Production
store.Open(OpenFlags.ReadOnly);
foreach (var mCert in store.Certificates)
{
if (mCert.Thumbprint.ToUpper().Equals(thumbprint.ToUpper()))
infoCertifikat = mCert;
}
if (infoCertifikat == null)
{
Console.WriteLine("404 Certificate not found");
Console.ReadKey();
return;
}
string licenceKey = "LicenceKey";
SyncfusionLicenseProvider.RegisterLicense(licenceKey);
using (var pdfDoc = new PdfLoadedDocument("document.pdf"))
{
pdfDoc.DocumentInformation.Creator = "Me";
pdfDoc.DocumentInformation.Author = "Naxi";
PdfCertificate pdfCertificate = new PdfCertificate(infoCertifikat);
//Normal signing
//Syncfusion.Pdf.Security.PdfSignature signature1 = new Syncfusion.Pdf.Security.PdfSignature(pdfDoc, pdfDoc.Pages[0], pdfCertificate, "DigitalSign");
//External signing becouse of HSM type of certificate
Syncfusion.Pdf.Security.PdfSignature signature1 = new Syncfusion.Pdf.Security.PdfSignature(pdfDoc, pdfDoc.Pages[0], null, "DigitalSign");
signature1.ComputeHash += Signature_ComputeHash1;
signature1.Bounds = new System.Drawing.RectangleF((6 * 25.4f / 0.352777778f), (9.3f * 25.4f / 0.352777778f), 65, 25);
signature1.ContactInfo = "Contact";
signature1.LocationInfo = "World";
signature1.Reason = "I want it";
PdfStandardFont font = new PdfStandardFont(PdfFontFamily.Helvetica, 3.8f);
float row_height = 4.2f;
signature1.Appearance.Normal.Graphics.DrawString("Digitally Signed by " + signature1.ContactInfo, font, PdfBrushes.Black, 0, row_height * 1);
signature1.Appearance.Normal.Graphics.DrawString("Reason: " + signature1.Reason, font, PdfBrushes.Black, 0, row_height * 2);
signature1.Appearance.Normal.Graphics.DrawString("Location: " + signature1.LocationInfo, font, PdfBrushes.Black, 0, row_height * 3);
signature1.Appearance.Normal.Graphics.DrawString((DateTime.Now).ToString(), font, PdfBrushes.Black, 0, row_height * 4);
pdfDoc.Save("document_signed.pdf");
pdfDoc.Close(true);
}
}
private static void Signature_ComputeHash1(object sender, PdfSignatureEventArgs ars)
{
//Get the document bytes
byte[] documentBytes = ars.Data;
SignedCms signedCms = new SignedCms(new ContentInfo(documentBytes), detached: true);
var cmsSigner = new CmsSigner(infoCertifikat);
//Set the digest algorithm SHA256
//cmsSigner.DigestAlgorithm = new Oid("1.3.6.1.4.1.311.10.3.12"); //Document signing – just tried
//cmsSigner.DigestAlgorithm = new Oid("1.2.840.113549.1.1.11"); //SHA256RSA
//cmsSigner.DigestAlgorithm = new Oid("1.2.840.113549.1.1.5"); //SHA1RSA
//cmsSigner.DigestAlgorithm = new Oid("1.3.14.3.2.26"); //SHA1
cmsSigner.DigestAlgorithm = new Oid("2.16.840.1.101.3.4.2.1"); //SHA256
cmsSigner.IncludeOption = X509IncludeOption.EndCertOnly; //Without this it throws CryptographicException: A certificate chain could not be bulit to a trusted root authority. (only in production)
signedCms.ComputeSignature(cmsSigner);
//Embed the encoded digital signature to the PDF document
ars.SignedData = signedCms.Encode();
}
}
}
I tried use different DigestAlgorithm. (commented in code) But i dont't know which i should use.
Im building it for Platform target x64 because without it it causes:
CryptographicException: The keyset is not defined. at System.Security.Cryptography.Pkcs.PkcsUtils.CreateSignerEncodeInfo(CmsSigner signer, Boolean silent, DafeCryptoProvHandle)
There is censored certificate used on production:
My certificates on develop have Key Usage: Signing document ,which is missing on production.
Or Enhanced Key Usage is missing something?
Thank you for any sugesstion.
UPDATE:
So, I made i little steps in different few ways which look hopefully, but not mutch.
I tried Normal signing of Syncfusion and set CryptographicStandard.CADES
Syncfusion.Pdf.Security.PdfSignature signature1 = new Syncfusion.Pdf.Security.PdfSignature(pdfDoc, pdfDoc.Pages[0], pdfCertificate, "DigitalSign");
signature1.Settings.CryptographicStandard = CryptographicStandard.CADES;
Its make signed document and crash the program. Like without any exception.
I looked on way, which was used some time ago and i find out, that it was signing with hashSHA1, RSA + PKCS#1.
So, i tried in external way use oid:
cmsSigner.DigestAlgorithm = new Oid("1, 3, 36, 3, 3, 1, 1"); //RSA + SHA1
And that causes: CryptographicsException: The object identifier is poorly formatted. at SignedCms.Sign(...)
And another way Im triying is invating colleague, so we can suffer colletively.
I dont know what Im doing.
Update 2:
So, the right way is probably using external with SHA1. Signing document works, but when program ends, the program don't close like normal but stops working.
The same thing do microsoft application certutil.
After lists certificates it stops working.
This is new info about that certificate. Its in HSM provided by Luna.
================ Certificate 0 ================
Serial Number: ...
Issuer: ...
NotBefore: ...
NotAfter: ...
Subject: ...
Non-root Certificate
Cert Hash(sha1): 9f
Key Container = ...
Provider = Luna enhanced RSA and AES provider for Microsoft Windows
Private key is NOT exportable
ERROR: Could not verify certificate public key against private key
Revocation check skipped -- server offline
Certificate is valid
Make sure the certificate is installed in the correct store (you code shows StoreLocation.LocalMachine). So let's first check if the certificate in the store is fine and the key is associated and accessible.
Run the following command to list and verify all certificates in the store:
certutil -verifystore MY
The output should show something like this:
================ Certificate X ================
Serial Number: ...
Issuer: ...
NotBefore: ...
NotAfter: ...
Subject: ...
Non-root Certificate
Cert Hash(sha1): ...
Key Container = ...
Unique container name: ...
Provider = ...
Private key is NOT exportable
Signature test passed
...
This will show you the Private Key Association and its Provider and will create a signature to check if the key is accessible.
So ensure the permission to access the key is given for the user running the command, as well as your application. You might need to clarify this by reading the HSM user manual or contact someone the responsible person (in-house or the manufacturer support)
You SignedCms code looks good - for testing keep it as simple as possible (no digest algorithm) and step forward in small steps.
EDIT
Additionally, of curse, you need to have your certificate trusted. This means that the certificate trust chain is installed to the certificate store.
The certificates can be found here: http://crt.postsignum.cz/certifikaty_autorit.html
Run certlm.msc
PostSignum Root QCA 4 root certification authority Needs to be added to the Trusted Root Certificate Authorities by right clicking the directory and selecting All tasks and * Import*
PostSignum Qualified CA 4 Needs to be added to the Intermediate Certificate Authorities by right clicking the directory and selecting All tasks and * Import*

How do I use the private key from a PFX certificate stored in Azure Key Vault in .NET Core 2?

I've written an ASP.NET Core 2.0 website in C# and have Facebook authentication enabled, so it requires HTTPS. I'm using the native Kestrel web server to host the site and have a listener set to take the PFX certificate per MS' documentation. I can't seem to find a way for Kestrel to recognize the private key after recall from Key Vault. I know it's present, as I wrote two debug statements that indicate it is, in fact present.
This is the function that I'm using to retrieve the secret, which is working.
public static async Task<X509Certificate2> GetKeyVaultCert()
{
X509Certificate2 pfx;
try
{
var kvClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetToken));
var secret = await kvClient
.GetSecretAsync("https://macscampvault.vault.azure.net/secrets/letsencrypt").ConfigureAwait(false);
byte[] bytes;
if(secret.ContentType == "application/x-pkcs12")
bytes = Convert.FromBase64String(secret.Value);
else
{
bytes = new byte[0];
Console.WriteLine("secret is not PFX!!");
throw new ArgumentException("This is not a PFX string!!");
}
var password = new SecureString();
var coll = new X509Certificate2Collection();
coll.Import(bytes, null, X509KeyStorageFlags.Exportable);
pfx = coll[0];
// File output added in case I end up needing to write cert to container
// File.WriteAllBytes(Directory.GetCurrentDirectory().ToString() + "/Macs.pfx", bytes);
Console.WriteLine(pfx.HasPrivateKey);
Console.WriteLine(pfx.GetRSAPrivateKey());
}
catch (Exception ex)
{
Console.WriteLine($"There was a problem during the key vault operation\n{ex.Message}");
throw;
}
return pfx;
}
The debug statements after the assignment call pfx = coll[0]; tell me that this private key exists, but when I try to connect to the website using lynx https://localhost I receive the following exception:
System.NotSupportedException: The server mode SSL must use a certificate with the associated private key.
So, how do I use the private key? Here's a gist to the file in question.
I already was helped by How to serialize and deserialize a PFX certificate in Azure Key Vault? but after following it, I got to this state.
In your gist you have the following code:
var keyVaultCert = GetKeyVaultCert().Result ??
throw new ArgumentNullException("GetKeyVaultCert().Result");
pfx = new X509Certificate2(keyVaultCert.RawData);
The second line there removes the private key, because the RawData property just returns the DER encoded X.509 object.
keyVaultCert is already an X509Certificate2 with a private key, you probably want to just use it.
pfx = GetKeyVaultCert().Result ?? throw etc;

.Net Crypto Service Provider error using nShield HSM

I am trying to use nShield from Thales to generate pair of asymmetric keys on it.
I have found the following example on msdn:
CspParameters csp = new CspParameters(1, "eToken Base Cryptographic Provider");
csp.Flags = CspProviderFlags.UseDefaultKeyContainer;
try
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(csp);
key = rsa.ToXmlString(true);
}
catch(Exception ex )
{
string s = ex.Message;
}
I can use KeySafe to succesfully connect and generate key-pairs on the HSM.
The code above throws the following exception:
System.Security.Cryptography.CryptographicException
"Invalid Signature." System.Security.Cryptography.CryptographicException
I have the feeling that I am not setting the correct second parameter in the CspParameters constructor. This is what it says in the example:
// The 1st parameter comes from HKEY_LOCAL_MACHINE\Software\Microsoft\Cryptography\Defaults\Provider Types.
// The 2nd parameter comes from HKEY_LOCAL_MACHINE\Software\Microsoft\Cryptography\Defaults\Provider.
I don't see any nCipher or nShield or Thales or anything like that there.
Edit:
Working test:
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(csp);
byte[] data = Encoding.ASCII.GetBytes("string");
byte[] enc = rsa.Encrypt(data, false);
String dec = Encoding.ASCII.GetString(rsa.Decrypt(enc, false));
key = rsa.ToXmlString(true);
You need to run nCipher CSP install wizard which is located under Start > All Programs > nCipher in order to register nCipher CSP in your operating system. After that mentioned registry entries will be available and you will be able to read exact CSP name from them.

How to edit permissions on CryptoKeySecurity?

I have posted about this already but no luck since then I have more information I thought I would try again I really hope someone can help. Basically I am reading an XML file and verifying the fact that it has been signed. This code works perfectly when run as an adminitrator but not as network service, the final line resolves to 'true' but when not run as admin doesnt.
NOTE: this is not a problem with reading the XML file this opens fine. The problem is with one of the objects in memory. I 'think' the problem is to do with access control lists on the CryptoKeyRights object.
I have used the following (in the below code) to try and grant everyone access to the CspParams object:
CryptoKeyRights rightsForall = CryptoKeyRights.FullControl;
CryptoKeyAccessRule everyone = new CryptoKeyAccessRule(#"Everyone", CryptoKeyRights.FullControl, AccessControlType.Allow);
cspParams.CryptoKeySecurity = new CryptoKeySecurity();
cspParams.CryptoKeySecurity.AddAccessRule(everyone);
The above code
The code is:
// Verify the signature of an XML file against an asymmetric
// algorithm and return the result.XmlDocument Doc, RSA Key
public static Boolean VerifyLicenceFile(string xmlLicFilePathArg)
{
bool isVerified = false;
try
{
CspParameters cspParams = new CspParameters();
cspParams.KeyContainerName = containerName;
RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider(cspParams);
// Create a new XML document.
XmlDocument xmlDoc = new XmlDocument();
// Load an XML file into the XmlDocument object.
xmlDoc.PreserveWhitespace = true;
xmlDoc.Load(xmlLicFilePathArg);
// Check arguments.
if (xmlDoc == null)
throw new ArgumentException("Doc");
if (rsaKey == null)
throw new ArgumentException("Key");
// Create a new SignedXml object and pass it
// the XML document class.
SignedXml signedXml = new SignedXml(xmlDoc);
// Find the "Signature" node and create a new
// XmlNodeList object.
XmlNodeList nodeList = xmlDoc.GetElementsByTagName("Signature");
// Throw an exception if no signature was found.
if (nodeList.Count <= 0)
{
throw new CryptographicException("Verification failed: No Signature was found in the document.");
}
// This example only supports one signature for
// the entire XML document. Throw an exception
// if more than one signature was found.
if (nodeList.Count >= 2)
{
throw new CryptographicException("Verification failed: More that one signature was found for the document.");
}
// Load the first <signature> node.
signedXml.LoadXml((XmlElement)nodeList[0]);
// Check the signature and return the result.
isVerified = signedXml.CheckSignature(rsaKey);
}
catch (Exception ex)
{
}
return isVerified;
}
This sounds more like permissions on the root CA, or the signing cert. So what I'd check is where the certificates in the chain are in the certificate store - if they're in the User store (which would explain it working under Administrator) or the machine store (where they should work for everyone)
The anser for this was not to use the machine to store the keys in...export them and load them independantly...
Is it possible to sign an xml document without having to use KeyContainerName?
This is a problem with the Mandatory Profiles and Temporary profiles.
These profiles are not full users, and do not have their own key stores. You need to use an ephemeral key, or avoid triggering keystore access.
See http://blogs.msdn.com/b/alejacma/archive/2007/10/23/rsacryptoserviceprovider-fails-when-used-with-mandatory-profiles.aspx for details.
You can try setting RSACryptoServiceProvider.UseMachineKeyStore = true.
This might avoid using the user profile's keystore.
If you are using .net 4.0 you can use the new CspParameters.flags CreateEphemeralKey to indicate that the key is independent of the keystore. i.e. it is an in-memory key, not read or saved to the keychain.

How to set read permission on the private key file of X.509 certificate from .NET

Here is the code to add a pfx to the Cert store.
X509Store store = new X509Store( StoreName.My, StoreLocation.LocalMachine );
store.Open( OpenFlags.ReadWrite );
X509Certificate2 cert = new X509Certificate2( "test.pfx", "password" );
store.Add( cert );
store.Close();
However, I couldn't find a way to set permission for NetworkService to access the private key.
Can anyone shed some light? Thanks in advance.
This answer is late but I wanted to post it for anybody else that comes searching in here:
I found an MSDN blog article that gave a solution using CryptoKeySecurity here, and here is an example of a solution in C#:
var rsa = certificate.PrivateKey as RSACryptoServiceProvider;
if (rsa != null)
{
// Modifying the CryptoKeySecurity of a new CspParameters and then instantiating
// a new RSACryptoServiceProvider seems to be the trick to persist the access rule.
// cf. http://blogs.msdn.com/b/cagatay/archive/2009/02/08/removing-acls-from-csp-key-containers.aspx
var cspParams = new CspParameters(rsa.CspKeyContainerInfo.ProviderType, rsa.CspKeyContainerInfo.ProviderName, rsa.CspKeyContainerInfo.KeyContainerName)
{
Flags = CspProviderFlags.UseExistingKey | CspProviderFlags.UseMachineKeyStore,
CryptoKeySecurity = rsa.CspKeyContainerInfo.CryptoKeySecurity
};
cspParams.CryptoKeySecurity.AddAccessRule(new CryptoKeyAccessRule(sid, CryptoKeyRights.GenericRead, AccessControlType.Allow));
using (var rsa2 = new RSACryptoServiceProvider(cspParams))
{
// Only created to persist the rule change in the CryptoKeySecurity
}
}
I'm using a SecurityIdentifier to identify the account but an NTAccount would work just as well.
In case this helps anyone else out, I wrote Jim Flood's answer in Powershell
function Set-PrivateKeyPermissions {
param(
[Parameter(Mandatory=$true)][string]$thumbprint,
[Parameter(Mandatory=$false)][string]$account = "NT AUTHORITY\NETWORK SERVICE"
)
#Open Certificate store and locate certificate based on provided thumbprint
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My","LocalMachine")
$store.Open("ReadWrite")
$cert = $store.Certificates | where {$_.Thumbprint -eq $thumbprint}
#Create new CSP object based on existing certificate provider and key name
$csp = New-Object System.Security.Cryptography.CspParameters($cert.PrivateKey.CspKeyContainerInfo.ProviderType, $cert.PrivateKey.CspKeyContainerInfo.ProviderName, $cert.PrivateKey.CspKeyContainerInfo.KeyContainerName)
# Set flags and key security based on existing cert
$csp.Flags = "UseExistingKey","UseMachineKeyStore"
$csp.CryptoKeySecurity = $cert.PrivateKey.CspKeyContainerInfo.CryptoKeySecurity
$csp.KeyNumber = $cert.PrivateKey.CspKeyContainerInfo.KeyNumber
# Create new access rule - could use parameters for permissions, but I only needed GenericRead
$access = New-Object System.Security.AccessControl.CryptoKeyAccessRule($account,"GenericRead","Allow")
# Add access rule to CSP object
$csp.CryptoKeySecurity.AddAccessRule($access)
#Create new CryptoServiceProvider object which updates Key with CSP information created/modified above
$rsa2 = New-Object System.Security.Cryptography.RSACryptoServiceProvider($csp)
#Close certificate store
$store.Close()
}
Note that the account parameter can be in the form of "DOMAIN\USER" as well (not just built in names) - I tested this in my environment and it automatically converted it to the appropriate SID
To do it programmatically, you have to do three things:
Get the path of the private key folder.
Get the file name of the private key within that folder.
Add the permission to that file.
See this post for some example code that does all three (specifically look at the "AddAccessToCertificate" method).
You can use the WinHttpCertCfg.exe tool that ships as part of the Windows Server 2003 Resource Kit Tools.
Example:
winhttpcertcfg -g -c LOCAL_MACHINE\My -s test -a NetworkService
Alternatively, you could use the Find Private Key tool that ships with the WCF SDK, to find the location on disk of the certificate's private key file. Then you can simply use ACL to set the right privileges on the file.
Example:
FindPrivateKey My LocalMachine -n "CN=test"
Based on #russ's answer,
This version copes with both Key Storage Provider & the Legacy Crypto Service Provider.
function Set-PrivateKeyPermissions {
param(
[Parameter(Mandatory=$true)]
[string]$thumbprint,
[Parameter(Mandatory=$true)]
[string]$account
)
#Open Certificate store and locate certificate based on provided thumbprint
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store("My","LocalMachine")
$store.Open("ReadWrite")
$cert = $store.Certificates | where {$_.Thumbprint -eq $thumbprint}
if ($cert.PrivateKey -Eq $null) {
# Probably using Key Storage Provider rather than crypto service provider
$rsaCert = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
if ($rsaCert -Eq $null) {
throw "Private key on certificate $($cert.Subject) not available"
}
$fileName = $rsaCert.key.UniqueName
$path = "$env:ALLUSERSPROFILE\Microsoft\Crypto\Keys\$fileName"
$permissions = Get-Acl -Path $path
$access_rule = New-Object System.Security.AccessControl.FileSystemAccessRule($account, "FullControl", "Allow")
$permissions.AddAccessRule($access_rule)
Set-Acl -Path $path -AclObject $permissions
} else {
#Create new CSP object based on existing certificate provider and key name
$csp = New-Object System.Security.Cryptography.CspParameters($cert.PrivateKey.CspKeyContainerInfo.ProviderType, $cert.PrivateKey.CspKeyContainerInfo.ProviderName, $cert.PrivateKey.CspKeyContainerInfo.KeyContainerName)
# Set flags and key security based on existing cert
$csp.Flags = "UseExistingKey","UseMachineKeyStore"
$csp.CryptoKeySecurity = $cert.PrivateKey.CspKeyContainerInfo.CryptoKeySecurity
$csp.KeyNumber = $cert.PrivateKey.CspKeyContainerInfo.KeyNumber
# Create new access rule - could use parameters for permissions, but I only needed GenericRead
$access = New-Object System.Security.AccessControl.CryptoKeyAccessRule($account,"GenericRead","Allow")
# Add access rule to CSP object
$csp.CryptoKeySecurity.AddAccessRule($access)
#Create new CryptoServiceProvider object which updates Key with CSP information created/modified above
$rsa2 = New-Object System.Security.Cryptography.RSACryptoServiceProvider($csp)
}
#Close certificate store
$store.Close()
}
This is the solution I found for windows server 2008 if anyone was interested: http://technet.microsoft.com/en-us/library/ee662329.aspx
Basically, I had to grant permissions to the service that needs to access the certificate using the MMC tool. Works like a charm.
If the PrivateKey of Certificate is of type RSACng you can go this route:
For LocalMachine:
var rsaPrivateKey = certificate.GetRSAPrivateKey();
var privateKey = rsaPrivateKey as RSACng;
var keyUniqueName = privateKey.Key.UniqueName;
var folderPath = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
var keyPath = Path.Combine(folderPath, "Microsoft", "Crypto", "RSA", "MachineKeys", keyUniqueName);
var fileInfo = new FileInfo(keyPath);
var accessControl = fileInfo.GetAccessControl();
accessControl.AddAccessRule(new FileSystemAccessRule(new NTAccount("<account>"), FileSystemRights.FullControl, AccessControlType.Allow));
fileInfo.SetAccessControl(accessControl);
If it is a User Certificate use Environment.SpecialFolder.ApplicationData and find the sid in the folder roaming\microsoft\crypto\rsa instead.

Categories