I have a .p7b file with a few certificates and i want to install them in the "Enterprise Trust" Store. A program i want to use expects it there.
I have writte code in c# which extract all the certificates from the file and installs them into a X509Store ("Storename.My") which works.
If i try to use the same code to write in a different store it (which already exists) it creates a new empty store and writes in there.
The StoreName.My is taken from system.Security.Cryptography.X509Certificates public enum StoreName, but there is no option for the "Enterprise Trust" store.
So i tried to use the constructor where i can give the StoreName as a string.
I use the certmgr from windows to check what certificats are stored in which stores.
// open file
var certificateStore = new CmsSignedData(new
FileStream(_tempFileName.ToString(), FileMode.Open));
// get all certificats
IX509Store x509Certs = certificateStore.GetCertificates("Collection");
var a = new ArrayList(x509Certs.GetMatches(null));
// this works
var store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
// this doesnt work
// var store = new X509Store("Enterprise Trust", StoreLocation.CurrentUser);
// open store
store.Open(OpenFlags.ReadWrite);
// write in store
foreach (object o in a) {
var cert = (Org.BouncyCastle.X509.X509Certificate)o;
var cert509 = new X509Certificate2();
cert509.Import(cert.GetEncoded());
store.Add(cert509);
}
store.Close();
How do i write correctly in the a store which isnt the StoreName enum?
If you want to be sure you aren't creating a new store, you can use the OpenFlags value OpenExistingOnly. Asserting that flag and checking for, e.g. "Trust2" yields System.Security.Cryptography.CryptographicException: The system cannot find the file specified. Therefore we get the best level of assurance that "Trust" is the right answer by specifying it as:
using (X509Store store = new X509Store("Trust", StoreLocation.CurrentUser))
{
store.Open(OpenFlags.ReadWrite | OpenFlags.OpenExistingOnly);
...
}
(Note that this is less good to use on Linux (.NET Core), because only Windows pre-initializes the stores)
We can get confirmation on the programmatic name to the display name via the certutil.exe command-line utility:
>certutil -store trust
trust "Enterprise Trust"
CertUtil: -store command completed successfully.
(I just don't have anything in that store)
Related
So, I having trouble to get the private key from the certificate. This is my first time working with certificates and I have tried to find a solution to this but I cant make i work. It's a console application and a need the key to sign a SOAP message/request.
Here is a code sample; (Let me know if you need anything more)
public static X509Certificate2 FindCertificate(string issuedBy)
{
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
//We gets the certificate from store
var certificate = store.Certificates.Cast<X509Certificate2>().FirstOrDefault(c => c.Issuer.Contains($"CN={issuedBy}"));
if (certificate.HasPrivateKey) // Result: True
{
//PrivateKey throws error: Keyset dosent exist
var key = certificate.PrivateKey;
}
if (certificate == null)
{
throw new ArgumentException($"Could not find certificate issued by: '{issuedBy}'", nameof(issuedBy));
}
var expireDate = DateTime.Parse(certificate.GetExpirationDateString());
if (expireDate < DateTime.Now)
{
throw new Exception($"Certificate has already expired: '{expireDate}'");
}
return certificate;
}
An alternative to your own solution (so you don't have to run VS in administrator mode is to give your own user privileges to read the private key.
Open the certificate manager for your machine certificates (type in "certificates" in Windows home menu search and choose "Manage computer certificates"
Find the certificate and right click -> "All Tasks" -> "Manage Private Keys..."
Click "Add..."
Type in your username (and click "Check Names" to see if you typed it in correctly)
Click "OK" and "OK"
Now you should be able to run your application (and VS) normally with access to the private key.
I was able to get it to work now just by start Visual Studio in Admin mode.
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\.
I have an exe file signed using signtool.exe. If I view the signature using Windows (Right click -> Properties -> Digital Signatures -> Details -> View Certificate -> Path), the chain looks like this, as expected:
Verisign
|
---Symantec Class 3 SHA256 Code Signing CA
|
---Me
However, if I load the certificate using the .NET API X509Certificate.CreateFromSignedFile(path) and look at the certificate using X509Certificate2UI.DisplayCertificate(cert), I only see the leaf certificate. Of course, because the chain is missing, trying to build the chain using X509Chain results in a failure.
Is this the expected behavior, and is there any way to build the entire chain using managed .NET code (read, without using WinVerifyTrust p/invoke)?
Yes, and no.
In your UI workflow you when you push "View Certificate" you switch from the file properties dialog to the CertUI dialog. CertUI (probably) only looks at the leaf/end-entity certificate and then builds the cert chain itself. So at that point it's slightly moot as to what else was in the signed file.
You can get slightly further in one call by reading all of the certificate information which was embedded in the file. My local testing shows that it wrote the EE cert (because it had to) and also the intermediate CA (with no signature line) but not the root certificate (because you normally omit root certificates in transfer... the other party either already has it or won't trust it, so it's wasted bytes).
var coll = new X509Certificate2Collection();
coll.Import("signedfile.exe");
// My coll had 2 certs at this point.
So you could pass all of those certificates into X509Chain.ChainPolicy.ExtraStore in case it needs help resolving intermediates, but to determine the root you still need to build the chain.
using (X509Certificate2 cert = new X509Certificate2("signedfile.exe"))
{
X509Chain chain = new X509Chain();
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.IgnoreNotTimeValid;
bool validChain = chain.Build(cert);
if (!validChain)
{
// Whatever you want to do about that.
foreach (var status in chain.ChainStatus)
{
// In reality you can == this, since X509Chain.ChainStatus builds
// an object per flag, but since it's [Flags] let's play it safe.
if ((status.Status & X509ChainStatusFlags.PartialChain) != 0)
{
// Incomplete chain.
}
}
}
X509Certificate2Collection chainCerts = new X509Certificate2Collection();
foreach (var element in chain.ChainElements)
{
chainCerts.Add(element.Certificate);
}
// now chainCerts has the whole chain in order.
}
I'm trying to add certificates but the Add function doesn't seem to do anything.
I have two certificates. Both I can add manually by right clicking and saving to the personal "testStore" store but they don't get saved when I try to add them programmatically. I even added just one of them, and the X509Store object contains it just as expected, but when I call .Add(cert), nothing gets saved there.
//I've already added 1 cert manually
X509Certificate2 cert2 = new X509Certificate2(#"C:\temp\Cert2.cer");
X509Store store = new X509Store("testStore", StoreLocation.CurrentUser);
store.Open(OpenFlags.MaxAllowed);
//here store.Certificates has the one Certificate I added manually as expected.
store.Certificates.Add(cert2);
//here store.Certificates still only has the first certificate, cert2 still isn't there..
store.Close();
Am I missing something?
Edit
I've also tried using StorePermission (as below) and also tried impersonating the administrator account and those didn't help either
StorePermission sp = new StorePermission( PermissionState.Unrestricted);
sp.Flags = StorePermissionFlags.AllFlags;
sp.Assert();
I got it to work... It turns out you should use store.Add() instead of store.Certificates.Insert();
//When LocalMachine is used, .Add() requires that you run the app as an administrator in order to work.
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
X509Certificate2 cert = new X509Certificate2("C:\\test\\test.cer");
store.Open(OpenFlags.MaxAllowed);
store.Add(cert);
store.Close();
Try with this flag:
store.Open (OpenFlags.ReadWrite);
http://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.openflags(v=vs.110).aspx
i want to obtain the CN of the certificates stored in the MY store as i want to verify if the certificate exists or not in that store.
I don't know the which method should be used to perform this task.
I tried using below code but it doesn't works
X509Certificate2Collection cers = store.Certificates.Find(X509FindType.FindBySubjectName,"Root_Certificate",false);
if(cers.Count>0)
{
//certificate present
}
else
{
//certificate not present
}
Does the subjectName gives CN?
is there any other method?
Please suggest me how to check whether a particular certificate is present or not and i want to do it using CN.
You could use the store.Certificates.Find(X509FindType.FindBySubjectName, "SubjectName", false)
function to search for a certificate by its subject name. Do NOT include "CN=" in the subject name.
To search more specific you could use the thumbprint to search for your certificate.
The following code sample demonstrates this:
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly | OpenFlags.IncludeArchived);
foreach (var c in store.Certificates)
{
Console.Out.WriteLine(c.Thumbprint);
Console.Out.WriteLine(c.Subject);
}
// Find by thumbprint
X509Certificate2Collection col =
store.Certificates.Find(X509FindType.FindByThumbprint, "669502F7273C447A62550D41CD856665FBF23E48", false);
store.Close();
I've added a foreach loop to the code sample to iterate over all certificates in the selected store.
Your certificate must be listed there. If not, you probably use the wrong store.
Note, there is a My store for the Machine and the Current User. So, be sure to open the right store.
To get the thumbprint of your certificate follow these steps:
Open certmgr.msc.
Double click on your certificate.
Go to the details tab.
Under thumbprint you will find the thumbprint of your certificate.
Hope, this helps.