CMS signing in .NET with a certificate chain not in a local trusted certificate store

I have X509 certificates that are stored on the network. I can read the chain from the remote windows certificate store. I need to sign some data and include the chain in the signature in order to subsequently confirm it.

The problem is that I cannot find a way to put the certificate chain in CsmSigner. I read that it accepts the certificate from the constructor parameter and tries to chain with X509Chain.Build. It ignores the values ​​of the certificate list and does not work (obviously) because the certificate cannot be found in the local Windows certificate store.

Below is my test code (which only works if the certificates were saved locally in the Windows certificate store).

protected byte[] SignWithSystem(byte[] data, X509Certificate2 cert, X509Certificate[] chain) { ContentInfo contentInfo = new ContentInfo(data); SignedCms signedCms = new SignedCms(contentInfo, true); CmsSigner cmsSigner = new CmsSigner(cert); cmsSigner.DigestAlgorithm = new Oid("2.16.840.1.101.3.4.2.1"); //sha256 cmsSigner.IncludeOption = X509IncludeOption.WholeChain; if (chain != null) { //adding cert chain to signer cmsSigner.Certificates.AddRange(chain); signedCms.Certificates.AddRange(chain); } signedCms.ComputeSignature(cmsSigner); //fails here with System.Security.Cryptography.CryptographicException : A certificate chain could not be built to a trusted root authority. byte[] signedPkcs = signedCms.Encode(); return signedPkcs; } 

Is there a way to make it work without uploading certificates to the local store? Should I use an alternate subscriber?

I can try to upload certificates to the store, but the problem is that

  • I need to add and remove certificates (permissions must be granted)

  • There are several processes that apply a signature, so interprocess synchronization must be added.

  • This is not what I would like to do.

+8
c # cryptography digital-signature
source share
3 answers

CMS Signature Example with BouncyCastle for .NET

You can use the BouncyCastle crypto library for .NET, which contains its own X509 certificate and CMS signing mechanisms. Many examples and documentation on the Internet are for Java, since BouncyCastle was originally a Java library. I used https://stackoverflow.com/a/3609777/ ... as a starting point for downloading the certificate and key and added the CMS signature. You may need to adjust the settings to get the results that you want to use to use.

I made the signature function about the same as yours, but note that the private key is now a separate parameter.

 using System; using System.Collections.Generic; using System.IO; using System.Linq; using Org.BouncyCastle.Cms; using Org.BouncyCastle.Pkcs; using Org.BouncyCastle.X509; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.X509.Store; class Program { protected static byte[] SignWithSystem(byte[] data, AsymmetricKeyParameter key, X509Certificate cert, X509Certificate[] chain) { var generator = new CmsSignedDataGenerator(); // Add signing key generator.AddSigner( key, cert, "2.16.840.1.101.3.4.2.1"); // SHA256 digest ID var storeCerts = new List<X509Certificate>(); storeCerts.Add(cert); // NOTE: Adding end certificate too storeCerts.AddRange(chain); // I'm assuming the chain collection doesn't contain the end certificate already // Construct a store from the collection of certificates and add to generator var storeParams = new X509CollectionStoreParameters(storeCerts); var certStore = X509StoreFactory.Create("CERTIFICATE/COLLECTION", storeParams); generator.AddCertificates(certStore); // Generate the signature var signedData = generator.Generate( new CmsProcessableByteArray(data), false); // encapsulate = false for detached signature return signedData.GetEncoded(); } static void Main(string[] args) { try { // Load end certificate and signing key AsymmetricKeyParameter key; var signerCert = ReadCertFromFile(@"C:\Temp\David.p12", "pin", out key); // Read CA cert var caCert = ReadCertFromFile(@"C:\Temp\CA.cer"); var certChain = new X509Certificate[] { caCert }; var result = SignWithSystem( Guid.NewGuid().ToByteArray(), // Any old data for sake of example key, signerCert, certChain); File.WriteAllBytes(@"C:\Temp\Signature.data", result); } catch (Exception ex) { Console.WriteLine("Failed : " + ex.ToString()); Console.ReadKey(); } } public static X509Certificate ReadCertFromFile(string strCertificatePath) { // Create file stream object to read certificate using (var keyStream = new FileStream(strCertificatePath, FileMode.Open, FileAccess.Read)) { var parser = new X509CertificateParser(); return parser.ReadCertificate(keyStream); } } // This reads a certificate from a file. // Thanks to: http://blog.softwarecodehelp.com/2009/06/23/CodeForRetrievePublicKeyFromCertificateAndEncryptUsingCertificatePublicKeyForBothJavaC.aspx public static X509Certificate ReadCertFromFile(string strCertificatePath, string strCertificatePassword, out AsymmetricKeyParameter key) { key = null; // Create file stream object to read certificate using (var keyStream = new FileStream(strCertificatePath, FileMode.Open, FileAccess.Read)) { // Read certificate using BouncyCastle component var inputKeyStore = new Pkcs12Store(); inputKeyStore.Load(keyStream, strCertificatePassword.ToCharArray()); var keyAlias = inputKeyStore.Aliases.Cast<string>().FirstOrDefault(n => inputKeyStore.IsKeyEntry(n)); // Read Key from Aliases if (keyAlias == null) throw new NotImplementedException("Alias"); key = inputKeyStore.GetKey(keyAlias).Key; //Read certificate into 509 format return (X509Certificate)inputKeyStore.GetCertificate(keyAlias).Certificate; } } } 

.NET CMS (Quick-fix with the rest of the chain skipped from the signature)

I can reproduce your problem with a certificate whose root is not in the trust store, and make sure that adding a certificate chain to the cmsSigner / signedCms Certificates collection does not avoid the error A certificate chain could not be built to a trusted root authority .

You can successfully complete the registration by setting cmsSigner.IncludeOption = X509IncludeOption.EndCertOnly;

However, if you do this, you will not get the rest of the chain in the signature. This is probably not what you want.

As an aside, in your example, you use X509Certificate for the array of certificates in the chain, but pass them to X509Certificate2Collection (note the β€œ2” there). X509Certificate2 comes from X509Certificate , but if it really is not X509Certificate2 , which you put in one of these collections, you will receive a cast error if something iterates over the collection (you do not get an error when adding, unfortunately, the certificate is of the wrong type, because that X509Certificate2Collection also comes from X509CertificateCollection and inherits its add methods).

+4
source share

Adding sample code that creates a separate PKCS7 signature using BouncyCastle (due to its softness) without certificate storage.

As an input parameter, it uses .net X509Certificate2 instances. The first certificate in the collection must be associated with a private key for signing data.

I would also like to note that it is not possible to read the private key associated with a certificate from a remote Windows certificate store using the .net property of X509Certificate2.Private key. By default, the private key is not loaded with the certificate using the X509Store (@ "\ remotemachine \ MY", StoreLocation.LocalMachine), and when access to the local computer is opened on the X509Certificate2.PrivateKey property, it fails with the "Keyset does not exist" error.

 public void SignWithBouncyCastle(Collection<X509Certificate2> netCertificates) { // first cert have to be linked with private key var signCert = netCertificates[0]; var Cert = Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(signCert); var data = Encoding.ASCII.GetBytes(Cert.SubjectDN.ToString()); var bcCertificates = netCertificates.Select(_ => Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(_)).ToList(); var x509Certs = X509StoreFactory.Create("Certificate/Collection", new X509CollectionStoreParameters(bcCertificates)); var msg = new CmsProcessableByteArray(data); var gen = new CmsSignedDataGenerator(); var privateKey = Org.BouncyCastle.Security.DotNetUtilities.GetKeyPair(signCert.PrivateKey).Private; gen.AddSigner(privateKey, Cert, CmsSignedDataGenerator.DigestSha256); gen.AddCertificates(x509Certs); var signature = gen.Generate(msg, false).GetEncoded(); Trace.TraceInformation("signed"); CheckSignature(data, signature); Trace.TraceInformation("checked"); try { CheckSignature(new byte[100], signature); } catch (CryptographicException cex) { Trace.TraceInformation("signature was checked for modified data '{0}'", cex.Message); } } void CheckSignature(byte[] data, byte[] signature) { var ci = new ContentInfo(data); SignedCms signedCms = new SignedCms(ci, true); signedCms.Decode(signature); foreach (X509Certificate cert in signedCms.Certificates) Trace.TraceInformation("certificate found {0}", cert.Subject); signedCms.CheckSignature(true); } 
+3
source share

To be clear, I am not a security or cryptography expert, but as far as I know, in order for the recipient to verify the signature, the root certificate in the certificate chain that you used for signing must already be a trusted root for the recipient.

If the recipient does not already have a root certificate in his repository and is marked as a trusted root ... then it doesn’t matter how you sign the data .. he will not check at the end of the receiver. And it is by design.

More in the chain of trust

Therefore, the only real solution to your problem that I see is to ensure that the root certificate is provided as a trusted root at both ends ... A Certificate Authority that is usually run.

Enterprise application script Typically, in an enterprise, some groups in the IT department (which have access to all the machines in the domain’s domain admins) will allow this scenario, ensuring that each computer in the domain has a root certificate belonging to this group, present on each machine as a trusted root, and an enterprise application developer typically requests a new certificate for use with their application, which has a trust chain that returns to the root certificate already distributed across all machines in the domain.

Find out the contact person for this group in your company and ask them to issue a certificate that you can use to sign.

script of internet applications . Certificate authorities have been created that own their root certificates and work with OS vendors to ensure that their root certificates are in a safe store, since the OS provider sends the OS to clients. (One of the reasons why using a pirated OS can be harmful is not only viruses and malware ..). And that is why when you use a certificate issued by VeriSign to sign data, the signature can be verified by most other computers in the world.

+2
source share

All Articles