How to verify certificate in SignedXml against machine store - c#

I would like to verify the signature in a SignedXml against the certificates in the machine store. This code is used to verify the signature:
internal bool VerifySignature(XmlDocument xml)
{
var signedXml = new SignedXml(xml);
var nsMgr = new XmlNamespaceManager(xml.NameTable);
nsMgr.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#");
signedXml.LoadXml((XmlElement)xml.SelectSingleNode("//ds:Signature", nsMgr));
return signedXml.CheckSignature();
}
The signature verifies fine, but only against itself and not against the certificates installed on the machine. Is there a way to check it against the root certificates in the local certificate store as well?

If anyone is interested, I used the CheckSignature(X509Certificate2, Boolean) method. I got the certificate from the Signature object and checked it like this:
var x509data = signedXml.Signature.KeyInfo.OfType<KeyInfoX509Data>().First();
var verified = false;
if(x509data != null)
{
var cert = x509data.Certificates[0] as X509Certificate2;
verified = cert != null && signedXml.CheckSignature(cert, false);
}
return verified;

You can use the overload of the CheckSignature method which takes an AsymmetricAlgorithm.
Pass along the public key of your certificate. You can fetch this via X509Store.

Related

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 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).

Locating Certificates by Application Policy OID

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.

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.

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.

Categories