I have X509 certificates that are stored on the network. I can read the chain from remote windows certificate store. I need to sign some data and include chain to the signature to make it possible to validate it later.
The problem is that I can't find a way to put certificate chain to the CsmSigner. I have read that it takes certificate from constructor parameter and tries to build a chain with X509Chain.Build. It ignores Certificates list values and fails (obviously) because no certificate can be found in the local Windows cert store.
Please find below my test code (that works only if certificates were saved locally to the windows cert 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 any way to make it work without uploading certificates to the local store? Should I use any alternative signer?
I can try to upload certificates to the store but the problems are that
I have to add and remove certificates (permissions have to be granted)
There are several processes that applies signature so cross-process synchronization have to be added.
This is not that I'd like to do.
To be clear, I am no security or cryptography expert.. but per my knowledge, for receiver to be able to validate the signature, the root certificate in the certificate chain you used for signing, must already be a trusted root for the receiver.
If the receiver does not have the root certificate already in their store, and marked as a trusted root... then doesn't matter how you sign the data.. it will fail validation on receiver end. And this is by design.
See more at Chain of trust
Hence the only real solution to your problem I see is to ensure that the root certificate is provisioned as trusted root on both ends... Typically done by a Certificate Authority.
Enterprise application scenario - Typically in an enterprise some group in IT department (who have access to all machines in the domain - like domain admins) would enable this scenario by ensuring that every computer in the domain has root certificate owned by this group, present on every machine as trusted root, and an application developer in the enterprise typically requests a new certificate for use with their application, which has the chain of trust going back to the root certificate already distributed to all machines in the domain.
Found out contact person for this group in your company, and have them issue a certificate you can use for signature.
Internet application scenario - There are established Certificate Authorities, who own their root certificates, and work with OS vendors to ensure that their root certificates are in trusted store, as the OS vendor ships the OS to it's customers. (One reason why using pirated OS can be harmful. It's not just about viruses / malware..). And that is why when you use a certificate issued by VeriSign to sign the data, the signature can be validated by most other machines in the world.
Example CMS Signing with BouncyCastle for .NET
You could use the BouncyCastle crypto library for .NET, which contains its own X509 certificate and CMS signing machinery. A lot of the examples and documentation on the web are for Java, as BouncyCastle was a Java library first. I've used the answer to this Stackoverflow question as a starting point for the certificate and key loading, and added the CMS signing. You may have to tweak parameters to produce the results you want for your use case.
I've made the signing function look approximately like yours, but note the private key is a separate parameter now.
.NET CMS (Quick-fix with rest of chain omitted from signature)
I can reproduce your problem with a certificate whose root is not in the trusted certificate store, and confirm that adding the certificate chain to the
cmsSigner
/signedCms
Certificates
collection does not avoid theA certificate chain could not be built to a trusted root authority
error.You can sign successfully by setting
cmsSigner.IncludeOption = X509IncludeOption.EndCertOnly;
However, if you do this, you will not get the rest of the chain in the signature. This probably isn't what you want.
As an aside, in your example you are using
X509Certificate
for the array of certificates in the chain, but passing them to anX509Certificate2Collection
(note the "2" in there).X509Certificate2
derives fromX509Certificate
, but if its not actually anX509Certificate2
that you put in one of those collections, you'll get a cast error if something iterates over the collection (you don't get an error when adding a certificate of the wrong type unfortunately, becauseX509Certificate2Collection
also derives fromX509CertificateCollection
and inherits its add methods).Adding sample code that creates detached
PKCS7
signature usingBouncyCastle
(thanks to softwariness) without Certificate store.It uses .net
X509Certificate2
instances as input parameter. First certificate in collection have to be linked with private key to sign data.Also I'd like to note that it is not possible to read private key associated with certificate from remote Windows cert store using .net
X509Certificate2.PrivateKey
property. By default private key is not loaded with certificate usingX509Store(@"\\remotemachine\MY", StoreLocation.LocalMachine)
and whenX509Certificate2.PrivateKey
property is accessed on local machine it fails with error "Keyset does not exist".