EPPlus - How to Remove Digital Signature - c#

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();
}

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;

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.

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