Locating Certificates by Application Policy OID - c#

I have two x509Certificates installed in my Personal certificate store and wish to retrieve the certificates by Application Policy.
I use the following code to achieve this:
public X509Certificate2 LocateCertificate(Oid oid)
{
var store = new X509Store(Store.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
try
{
var certificates = store.Certificates.Find(X509FindType.FindByApplicationPolicy, oid.Value, true);
if(certificates.Count != 1)
{
throw new CryptographicException(string.Format("Expected one certificate, found {0}", certificates.Count);
}
return certificates[0];
}
finally
{
store.Close();
}
}
When both installed X509Certificates have different Extended key Usage values the above method successfully retrieve the correct certificate when provided with a valid OID. However, if one certificate does not have its Extended Key Usage property set it is also returned by the query along with the correct certificate. I want to guard against returning certificates which have:
An incorrect Extended Key Usage value set.
No Extended Key Usage value set.
Any help would be appriciated.

Related

Error Reading Private Key from Certificate

I wrote a simple c# console app that just tries to read the private key of a x509 cert,
The code is:
var store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
X509Certificate2Collection cers = store.Certificates.Find(X509FindType.FindBySubjectName, "cert", false);
if (cers.Count > 0)
{
X509Certificate2 cer = cers[0];
try
{
if (cer.HasPrivateKey)
{
Console.WriteLine("Private key:");
Console.WriteLine(cer.GetRSAPrivateKey().ToXmlString(false));
}
}
catch (CryptographicException)
{
string currentUser = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
Console.WriteLine("This cert has a private key and this user, " + currentUser + ", was unable to access it.");
}
};
store.Close();
If I run as admin, it work fine.
Otherwise, I get a read permission error.
I used the mmc to give Full Access to my account and even tried giving full access to Everybody (for testing) but same result. The cert does not seem to honor the permissions set.
Am I missing a step?
Any help would be appreciated.

EPPlus - How to Remove Digital Signature

I would like to remove a digital signature from a VBA signed excel macro file. However when I look at EPPlus's library I see that the "Signature" property is read-only, and setting the Certificate as null doesn't seem to remove it, only invalidates the signature in the file:
using (ExcelPackage xlPackage = new ExcelPackage(fiNew))
{
xlPackage.Workbook.VbaProject.Signature.Certificate = null;
xlPackage.Save();
}
Calling the dispose method doesn't work either, errors out on the save. Does anybody know how to do this in EPPlus?
Looking at the source just provide a certificate without a private key - see line 137.
internal void Save(ExcelVbaProject proj)
{
if (Certificate == null)
{
return;
}
if (Certificate.HasPrivateKey==false) //No signature. Remove any Signature part
You could for example just use the first certificate in the Trusted Root Certificate Authorities, which have no keys, as long as you are not running on a root certificate authority or somebody improted a PFX by accident... so we filter for that too:
Here is some code to read from the Trusted Root Certificate Authorities store:
using (var store = new X509Store(StoreName.Root, StoreLocation.CurrentUser)) {
store.Open(OpenFlags.ReadOnly);
var someCertWithoutPrivateKey =
store.Certificates
.Cast<X509Certificate2>()
.Where(c => !c.HasPrivateKey)
.FirstOrDefault();
}

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;

How to ensure X509Certificate2 class does not return duplicate signing certificates?

I have a C# windows form application. The user types in message, subject, to, and selects a signing certificate from a drop down to sign the email as well using X509Certificate2 class.
Here is how the snippet for how the dropdown (ComboBox SigningCertList) is populated:
try
{
X509Certificate2[] certs;
certs = CryptoHelper.GetSigningCertificateList();
SigningCertList.Items.AddRange(certs);
SigningCertList.ValueMember = "SerialNumber";
SigningCertList.DisplayMember = "FriendlyName";
SigningCertList.SelectedIndexChanged += new System.EventHandler(SigningCertList_SelectedIndexChanged);
SigningCertList.SelectedItem = 0;
}
Symptoms are odd. The combobox will show my signing certificate (installed from a p12 file). However, if I load the Windows Certificates MMC snapin, I cannot find it when doing a search. Upon reinstalling the certificate, I see it in the Windows Certificates MMC snapin, and now duplicated in the dropdown. Only the second (or last / recent) signing cert in the list actually signs it.
So how can I ensure X509Certificate2 class does not return duplicate signing certificates?
Here is the GetSigningCertificateList() method below:
`public static X509Certificate2[] GetSigningCertificateList()
{
var list = new List();
int matches = 0;
X509Store localStore = new X509Store(StoreLocation.LocalMachine);
localStore.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
try
{
foreach (X509Certificate2 cert in localStore.Certificates)
{
foreach (X509Extension extension in cert.Extensions)
{
X509KeyUsageExtension usageExtension = extension as X509KeyUsageExtension;
if (usageExtension != null)
{
bool matchesUsageRequirements = ((X509KeyUsageFlags.DigitalSignature & usageExtension.KeyUsages) == X509KeyUsageFlags.DigitalSignature);
if (matchesUsageRequirements)
{
list.Add(cert);
matches += 1;
}
}
}
}
}
finally
{
localStore.Close();
}
X509Store userStore = new X509Store(StoreLocation.CurrentUser);
userStore.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
try
{
foreach (X509Certificate2 cert in userStore.Certificates)
{
foreach (X509Extension extension in cert.Extensions)
{
X509KeyUsageExtension usageExtension = extension as X509KeyUsageExtension;
if (usageExtension != null)
{
bool matchesUsageRequirements = ((X509KeyUsageFlags.DigitalSignature & usageExtension.KeyUsages) == X509KeyUsageFlags.DigitalSignature);
if ((matchesUsageRequirements) && cert.FriendlyName.IndexOf("MYcompanyname.",0) >= 0)
{
list.Add(cert);
matches += 1;
}
}
}
}
}
finally
{
userStore.Close();
}
return list.ToArray();
}
}`
You mention that you don't see a cert in MMC, but do in your app; and that when you install the cert via MMC it shows up twice. This suggests that you're using MMC to view the user My store (or the computer My store) but the certificate in question is normally present in the other location.
Once the certificate has been registered in two different stores (same store name, different location => different store) then Windows no longer considers it to be a duplicate (for one, the two instances can have different private key permissions). So while there's a duplicate to your application, there's not (intrinsically) to Windows or .NET.
You can prevent duplicates by standard dedup tactics, such as using a HashSet<X509Certificate2> instead of a List<X509Certificate2>. The default .Equals check (which is performed by the default comparator) will match if the issuer and serial number are the same. That should be unique as long as your certificates come from a public CA; but private PKI could recycle serial numbers or not guarantee uniqueness. If you're concerned you could use a custom comparator which uses whatever match logic you like.
So the easy dedup is to replace list = new List<X509Certificate2>() with list = new HashSet<X509Certificate2>() (though you should probably change the variable name).
A HashSet keeps only the first of the collisions; so if you want LocalMachine to be preferred you've already achieved that. If CurrentUser should win, you may want to switch your blocks around.
Two other things of note:
If a certificate has no key usage extension at all it's considered valid for all usages. Your code doesn't do that. (If you know that a "correct" cert in your application always will then there's no problem)
X509Store.Certificates returns new objects every call; you could reduce finalizations by calling Dispose on the certificates you don't return (or Reset for .NET 4.5.2 and below).

Using certificate file to connect to webservice over SSL

I am developing windows service in C# which invokes webservice methods. I must use SSL to connect to webservice. I have recieved from publisher p12 file with certificate. The file is password protected. To use Import method to use this certificate. Everything is working fine, but I do not like this method - I have password harcoded in my app. When publisher changes certificate I must rewrite code(changing the password to new one). Is there any way not to harcode password to .p12 file or use other option(.cer file)?
What you could do is something like this:
Install the SSL certificate into your local machine certificate store (using the Microsoft Management Console "MMC")
Extract the certificates thumbprint (e.g. "748681ca3646ccc7c4facb7360a0e3baa0894cb5")
Use a function which fetches you the certificate from the local certificate store for the given thumbprint.
Provide the SSL certificate when calling your web service.
private static X509Certificate2 GetCertificateByThumbprint(string certificateThumbPrint, StoreLocation certificateStoreLocation) {
X509Certificate2 certificate = null;
X509Store certificateStore = new X509Store(certificateStoreLocation);
certificateStore.Open(OpenFlags.ReadOnly);
X509Certificate2Collection certCollection = certificateStore.Certificates;
foreach (X509Certificate2 cert in certCollection)
{
if (cert.Thumbprint != null && cert.Thumbprint.Equals(certificateThumbPrint, StringComparison.OrdinalIgnoreCase))
{
certificate = cert;
break;
}
}
if (certificate == null)
{
Log.ErrorFormat(CultureInfo.InvariantCulture, "Certificate with thumbprint {0} not found", certificateThumbPrint);
}
return certificate;
}
public string GetServiceResponse() {
string WebSvcEndpointConfigurationName = "WebServiceEndpoint";
Uri webSvcEndpointAddress = new Uri("http://www.example.com/YourWebService.svc");
string webSvcCertificateThumbPrint = "748681ca3646ccc7c4facb7360a0e3baa0894cb5";
string webSvcResponse = null;
SomeWebServiceClient webServiceClient = null;
try
{
webServiceClient = new SomeWebServiceClient(WebSvcEndpointConfigurationName, new EndpointAddress(webSvcEndpointAddress));
webServiceClient.ClientCredentials.ClientCertificate.Certificate = GetCertificateByThumbprint(webSvcCertificateThumbPrint, StoreLocation.LocalMachine);
webSvcResponse = webServiceClient.GetServiceResponse();
}
catch (Exception ex)
{
}
finally
{
if (webServiceClient != null)
{
webServiceClient.Close();
}
}
return webSvcResponse;
}
PKCS#12 file is provided to you as it is a natural way to transport certificates together with private keys. You can use one of the following:
convert it to format you like and store the way you like
convert it to passwordless PFX
import it to computer's certificate storage and use it this way
But all those methods (together with keeping a hardcoded password) provide no real protection to the private key and thus are not usable if you distribute the application to outside of your organization.

Categories