installing my WCF service I also install certificate with private key.
Since I will be running service as a different user, that user needs access to the private key. I extensively read other stackoverflow questions and they all suggest permission on private key file in file system needs to adjusted.
I do this by,
private static void AddUserPermissions(X509Certificate2 certificate, NTAccount user, StoreLocation storeLocation)
{
RSACryptoServiceProvider rsaProvider = (RSACryptoServiceProvider)certificate.PrivateKey;
// Find file
string keyPath = FindKeyLocation(rsaProvider.CspKeyContainerInfo.UniqueKeyContainerName, storeLocation);
FileInfo keyFileInfo = new FileInfo(keyPath);
// Create new FileSecurity
FileSecurity keyFileSecurity = keyFileInfo.GetAccessControl();
keyFileSecurity.AddAccessRule(new FileSystemAccessRule(user, FileSystemRights.Read, AccessControlType.Allow));
// Apply file security to the file
keyFileInfo.SetAccessControl(keyFileSecurity);
}
When I run my program and inspect the private key file I can see, for example "Network Service" has been added to the permissions list.
Great, that's working, but when WCF tries to use private key, it cannot access it.
Looking at certlm, certificate -> All Tasks -> Manage Private Keys..
I can see that my user is not on the list. Adding my user through GUI solves the issue, but I need to do it in code!!
Crypto Service Provider (Microsoft RSA SChannel Cryptographic Provider)
The keys are located in C:\ProgramData\Application Data\Microsoft\Crypto\RSA\MachineKeys and setting a normal file permission here is reflected in certlm.msc.
Crypto Next Generation (Microsoft Key Storage Provider)
The keys are located in C:\ProgramData\Application Data\Microsoft\Crypto\Keys and setting a normal file permission here is reflected in certlm.msc.
Summary
Ensure you modify the permissions on the right file in the right location.
Anyone who gets this far looking for a solution for ECDSA certificate keys, I found a way!
string keyUniqueName = (certificate.GetECDsaPrivateKey() as ECDsaCng)?.Key.UniqueName
?? (certificate.GetRSAPrivateKey() as RSACng)?.Key.UniqueName
?? throw new NotSupportedException("No ECDSA or RSA key found");
In the example code from the question, the FindKeyLocation gets passed rsaProvider.CspKeyContainerInfo.UniqueKeyContainerName, instead you would pass keyUniqueName as taken from my example.
One more good note, the accepted answer from Daniel Fisher lennybacon correctly documents where keys are stored based on where they were generated/installed. But you should use Environment.SpecialFolder.CommonApplicationData or Environment.SpecialFolder.ApplicationData for key file paths.
Also, I found my keys in c:\ProgramData\Microsoft\Crypto\, not c:\ProgramData\Application Data\Microsoft\Crypto\.
Related
I want to create web of trust support in my application, allowing my users to use their private keys, to sign other user's public keys - Using C# and Bouncy Castle.
I've got most things figured out, such as creating PGP keys, submitting them to key servers using HTTP REST, encrypting MIME messages and cryptographically signing them (using MimeKit) - But the one remaining hurdle, is to figure out some piece of code that can use my private key, to sign for another person's public key, using Bouncy Castle.
Since the documentation for BC is horrendous, figuring out these parts, have previously proven close to impossible ...
For the record, I'm using GnuPG as my storage for keys.
If anybody wants to look at my code so far for what I have done, feel free to check it out here.
I am probably not supposed to ask this here, but I'd also love it if some BC gurus out there could have a general look at my code so far, and check if I've made a fool of myself with the stuff I've done so far ...
Found the answer after a lot of trial and error, here it is ...
private static byte[] SignPublicKey(
PgpSecretKey secretKey,
string password,
PgpPublicKey keyToBeSigned,
bool isCertain)
{
// Extracting private key, and getting ready to create a signature.
PgpPrivateKey pgpPrivKey = secretKey.ExtractPrivateKey (password.ToCharArray());
PgpSignatureGenerator sGen = new PgpSignatureGenerator (secretKey.PublicKey.Algorithm, HashAlgorithmTag.Sha1);
sGen.InitSign (isCertain ? PgpSignature.PositiveCertification : PgpSignature.CasualCertification, pgpPrivKey);
// Creating a stream to wrap the results of operation.
Stream os = new MemoryStream();
BcpgOutputStream bOut = new BcpgOutputStream (os);
sGen.GenerateOnePassVersion (false).Encode (bOut);
// Creating a generator.
PgpSignatureSubpacketGenerator spGen = new PgpSignatureSubpacketGenerator();
PgpSignatureSubpacketVector packetVector = spGen.Generate();
sGen.SetHashedSubpackets (packetVector);
bOut.Flush();
// Returning the signed public key.
return PgpPublicKey.AddCertification (keyToBeSigned, sGen.Generate()).GetEncoded();
}
I'm having a problem with my WCF push notification service.
Every time it sends a push notification to apple, a new RSA is generated inside the "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys" folder.
I think that the problem causing this might be related to the Apple p12 Certificate.
Why does that happen? Is there any way to prevent the generating of those RSA?
I'm using PushSharp library and my code looks like this:
pushBroker = new PushBroker();
pushBroker.RegisterAppleService(
new PushSharp.Apple.ApplePushChannelSettings(false, appleCert, p12Pass, false)
, null);
The temporary file is where Windows has put the RSA private key that it loaded out of the appleCert PFX blob.
If the file appears, then disappears (since you called it a "temp" file), then everything's working as expected. If it appears and stays forever then PushSharp is loading the certificate with PersistKeySet, which says that the private key shouldn't be deleted.
I don't know if https://github.com/has-taiar/PushSharp.Web/blob/master/PushSharp.Apple/ApplePushChannelSettings.cs is the real source of ApplePushChannelSettings, but it'll do. Looking at that source, they seem to be using PersistKeySet, making the file permanent.
You have two practical approaches:
1) Load the certificate into an X509Store as a one-time operation (using PersistKeySet on load because you need to for store persistence). The file becomes permanent, but gets used for forever, so it's cool.
2) Load the certificate yourself from the PFX and (ideally) Dispose the certificate when you're done. I don't know if you have any sort of on-shutdown notification that you can more-or-less reliably use, but calling cert.Dispose() (or, on older Framework versions, cert.Close()) is more reliable than waiting on the GC and Finalizer to do the cleanup for you.
If you do have a good shutdown event:
// Note that I'm not specifying any X509KeyStorageFlags here.
// You don't want it PersistKeySet (that's the whole point) and you don't
// need it to be exportable (unless you get exceptions without Exportable, in which case
// PushSharp is doing something naughty).
private X509Certificate _cert = new X509Certificate2(appleCert, p12CerPass);
...
_broker.RegisterAppleService(new PushSharp.Apple.ApplePushChannelSettings(_cert, false));
...
// In some OnShutdown type location
_cert.Dispose();
If not:
// Still don't specify any load flags. Especially not PersistKeySet.
var cert = new X509Certificate2(appleCert, p12CerPass);
_broker.RegisterAppleService(new PushSharp.Apple.ApplePushChannelSettings(cert, false));
No matter what you'll still end up with a permanently leaked file every time your process abnormally terminates, since the file-deletion code won't get a chance to kick in.
A solution exists to completely avoid creating the temporary file, and that is to use the EphemeralKeySet flag which was added to .NET Core 2.0 (currently in preview). It's not yet available in .NET Framework.
// Keep the private key in memory, never let it touch the hard drive.
var cert = new X509Certificate2(appleCert, p12CerPass, X509KeyStorageFlags.EphemeralKeySet);
_broker.RegisterAppleService(new PushSharp.Apple.ApplePushChannelSettings(cert, false));
But I don't know if PushSharp is available for .NET Core.
I had the same problem and i found the solution
to prevent generate key every time you send new push notification,
the code should be like this. and it work perfect for me
var cert = X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable;
var certificate1 = new X509Certificate2(appleCert, p12CerPass,cert);
_broker.RegisterAppleService(new PushSharp.Apple.ApplePushChannelSettings(certificate1, false));
I am moving an existing (and working) ASP.NET web site to Azure web site. One of the bits of functionality on the site is signing an XML document. The code to get the key is:
// retrieve a key from the key safe - this will create it if it does not exist yet
System.Security.Cryptography.CspParameters csp = new CspParameters();
csp.KeyContainerName = "MyKeyName";
System.Security.Cryptography.RSACryptoServiceProvider key = new RSACryptoServiceProvider(csp);
The last line is throwing a CryptographicException, with the message "The system cannot find the file specified".
I have not put a key or container into Azure - my understanding is that the ServiceProvider would create one.
I have reviewed this article, but did not get any clues.
Clearly I am missing something fundamental.
Thanks Simon - that pointed me in the right direction.
Turns out you need to specify that the key be created in a machine store. Code that worked is:
System.Security.Cryptography.CspParameters csp = new CspParameters();
csp.KeyContainerName = "MyKeyName";
csp.Flags = CspProviderFlags.UseMachineKeyStore;
Note the addition of the line specifying "UseMachineKeyStore"
I have the following code where SIGNED_FILENAME is a constant pointing to an existing pfx file that contains the private key.
X509Certificate2 cert = new X509Certificate2(SIGNED_FILENAME, PASSWORD, X509KeyStorageFlags.MachineKeySet);
RSACryptoServiceProvider certRsa = cert.PrivateKey as RSACryptoServiceProvider;
When I use code to add permissions to the private key I find that they are set on the file specified in certRsa.CspKeyContainerInfo.UniqueKeyContainerName. When I view the certificate permissions in the Certificates mmc snap-in however there are no new permissions set.
When I set the keys manually via the Certificates mmc snap-in I find that the private key it sets the permissions on is different than the one I found in the UniqueContainerName property mentioned above.
TLDR: Each time I run those two lines of code the key container file changes.
Why would this be happening and how can I set the permissions on the same key that the Certificates mmc snap-in does?
Apparently because I was opening it from a file each time the key container was being re-generated (or something). Here is the code that works:
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
X509Certificate2 c = store.Certificates
.Find(X509FindType.FindBySubjectName, SIGNED_SUBJECT, true)
.Cast<X509Certificate2>()
.FirstOrDefault();
store.Close();
RSACryptoServiceProvider rsa = c.PrivateKey as RSACryptoServiceProvider;
Console.WriteLine("Certificate thumbprint:" + c.Thumbprint);
Console.WriteLine("From machine key store?: " + rsa.CspKeyContainerInfo.MachineKeyStore);
Console.WriteLine("Key container name: " + rsa.CspKeyContainerInfo.KeyContainerName);
Console.WriteLine("Key unique container name: " + rsa.CspKeyContainerInfo.UniqueKeyContainerName);
Previously when running the code snippet from my original post (where I open the certificate as a file) the key info that prints to the console would change each time. Running the modified code shows the same info each time.
I'm trying to write to the registry using my C# app.
I'm using the answer given here: Writing values to the registry with C#
However for some reason the key isn't added to the registry.
I'm using the following code:
string Timestamp = DateTime.Now.ToString("dd-MM-yyyy");
string key = "HKEY_LOCAL_MACHINE\\SOFTWARE\\"+Application.ProductName+"\\"+Application.ProductVersion;
string valueName = "Trial Period";
Microsoft.Win32.Registry.SetValue(key, valueName, Timestamp, Microsoft.Win32.RegistryValueKind.String);
The Application.name and Application.version 'folders' don't exists yet.
Do I have to create them first?
Also, I'm testing it on a 64b Win version so I think if I want to check the registry for the key added I have to specifically check the 32bit registry in: C:\Windows\SysWOW64\regedit.exe don't I?
First of all if you want to edit key under LocalMachine you must run your application under admin rights (better use CurrentUser it's safer or create the key in installer). You have to open key in edit mode too (OpenSubKey method) to add new subkeys. I've checked the code and it works. Here is the code.
RegistryKey key = Registry.LocalMachine.OpenSubKey("Software",true);
key.CreateSubKey("AppName");
key = key.OpenSubKey("AppName", true);
key.CreateSubKey("AppVersion");
key = key.OpenSubKey("AppVersion", true);
key.SetValue("yourkey", "yourvalue");
You can use the following code to create and open the required registry keys.
RegistryKey SoftwareKey = Registry.LocalMachine.OpenSubKey("Software",true);
RegistryKey AppNameKey = SoftwareKey.CreateSubKey("AppName");
RegistryKey AppVersionKey = AppNameKey.CreateSubKey("AppVersion");
AppVersionKey.SetValue("yourkey", "yourvalue");
You can basically use CreateSubKey for all your application settings, as it will open the key for write access, if it already exists, and create it otherwise. There is no need to create first, and then open. OpenSubKey comes in handy when you are absolutely certain the key already exists, like in this case, with "HKEY_LOCAL_MACHINE\SOFTWARE\"
Also check if your registry calls are getting virtualised. See here for more information.
It can happen if your application is not UAC aware and occurs for compatibility reasons.
Real path
HKEY_LOCAL_MACHINE\Software\FooKey
Virtual path
HKEY_USERS\<User SID>_Classes\VirtualStore\Machine\Software\FooKey
Try to open HKLM\Software first. Then create key for your program, and then create key for version. Howewer, your key could be placed at HKLM\software\WOW6432Node. Check this.
The problem is you don't have enough privileges. Here is a way that works for my:
RegistryKey myKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
myKey = myKey.OpenSubKey(subkey, RegistryKeyPermissionCheck.ReadWriteSubTree, RegistryRights.FullControl);
if (myKey != null)
{
myKey.SetValue("DefaultPrinterId", ldiPrinters[e.RowIndex].id, RegistryValueKind.String);
myKey.Close();
}
With RegistryKey.OpenBaseKey you open the correct registry, because when you don't have permissions the registry that you write, it does in another location.
By default, your changes will be written to HKLM\SOFTWARE\WOW6432Node\... because of registry redirection. This can be quite confusing.
In order to write to HKLM\SOFTWARE\..., you need to use RegistryKey.OpenBaseKey to open the 64-bit registry:
var path = #"SOFTWARE\...";
var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
var key = baseKey.CreateSubKey(path, RegistryKeyPermissionCheck.ReadWriteSubTree);
key.SetValue(name, value, RegistryValueKind.String);
Also, you need to have permission to write to the specified registry key.
You can get permission either by assigning permissions to specific users or service accounts or by running your app in elevated mode.