2

I have to digitally sign some data in C# using a certificate and private key. The certificate that I'm using has a custom root CA as well as a custom intermediate CA.

I can use code like this to do the signing without any problem, if the root and intermediate CAs are installed into the Windows certificate stores:

var content = new ContentInfo(manifest);
var cms = new SignedCms(content, true);
var signer = new CmsSigner(SubjectIdentifierType.SubjectKeyIdentifier, myCertificate);

signer.SignedAttributes.Add(new Pkcs9SigningTime(DateTime.Now));
cms.ComputeSignature(signer);

return cms.Encode();

Unfortunately, howeber, I am not able to install the root and intermediate CAs into the server's certificate store (I'm using Azure Web Apps). I'm trying to work out a way to sign by using the root and intermediate CA certificates if they are stored on disk instead. I thought I might be able to do something like this before calling cms.Encode():

// add CAs from disk
var intermediateCACertificate = new X509Certificate2(@"pathToIntermediateCertificate.cer");
signer.Certificates.Add(intermediateCACertificate);

var rootCACertificate = new X509Certificate2(@"pathToRootCertificate.cer");
signer.Certificates.Add(rootCACertificate);

But when I execute this I get a CryptographicException thrown "A certificate chain could not be built to a trusted root authority.".

Is it possible to digitally sign using non-standard CAs without installing them into the Windows certificate store?

John
  • 419
  • 1
  • 4
  • 12
  • Wouldn't that defeat the purpose of having a CA? You cannot (shouldn't) be able to add a trusted Certificate Authority with out admin privileges. Signing is very different than adding the certs as a trusted CA - but you probably are not going to be allowed to sign with a CA chain that's not trusted – TheFiddlerWins Nov 24 '15 at 21:32
  • Possibly, yes - I can't add a CA though, and I don't want necessarily want to trust it, just digitally sign something using a certificate it has generated. It may be the case that this just isn't possible due to the architecture of CAs, but I'd like to know for sure. – John Nov 24 '15 at 21:35
  • I guess essentially what I want to do is tell the system "for the purposes of this operation, treat this chain as trusted", if that makes sense. – John Nov 24 '15 at 21:39
  • I don't think Windows will let you but I don't work with it much, have you tried using a self-signed certificate instead of a CA chain? – TheFiddlerWins Nov 24 '15 at 21:51
  • I don't have any choice in the certificates I'm using. – John Nov 24 '15 at 22:23
  • See if Bouncy Castle library gives you the flexibility. The BCL classes are bind to Windows API which restricts too much. – Lex Li Nov 25 '15 at 00:10
  • Here's a related answer may be helpful for How to Store and Retrieve Public Keys via the Azure Key Vault: https://stackoverflow.com/a/48106470/764307 – Jonathan B. Jan 05 '18 at 16:38

1 Answers1

3

It is possible.

Basically, the BLOB that comes back from the SignedCms class has the capability to include any number of arbitrary certificates in addition to the signature itself. This is typically done to include at least the certificate that the message was signed with and possibly any other intermediate certificates so that the receiving entity can verify the signature up to a root certificate that it trusts.

When you are making calls to signer.Certificates.Add() call above, you're adding certificates to be encoded in the output signature BLOB. However, adding certificates to this collection does nothing to imply any sort of "trust" of them.

The problem in your case is the fact that by default the .NET SignedCms class tries to help you by automatically including the certificates that would be included for a typical scenario. By default, it includes every certificate in the chain except for the root. It does this by attempting to build a chain with certificates it finds in the server's certificate store, which, as you've noticed, fails when the root/intermediate CA's certificates aren't installed.

The certificate chain building is controlled by the CmsSigner.IncludeOption property. The default value for this is X509IncludeOption.ExcludeRoot. Both this value and X509IncludeOption.WholeChain will fail on your system since the class will be unable to build a chain based on what's in the certificate store.

Likely, what you'll want to do is as follows:

signer.IncludeOption = X509IncludeOption.EndCertOnly;
signer.Certificates.Add(intermediateCACertificate);
signer.Certificates.Add(rootCACertificate);

This will include the certificate you are signing with (which is included when you specify EndCertOnly) and the intermediate and root certificates which are explicitly added. SignedCms won't attempt to build a certificate chain since it only needs to include a certificate it has already been provided, so it won't throw any exceptions.

Incidentally, you could get the same result as above like this:

signer.IncludeOption = X509IncludeOption.None;
signer.Certificates.Add(myCertificate);
signer.Certificates.Add(intermediateCACertificate);
signer.Certificates.Add(rootCACertificate);

In this case, the signature certificate isn't automatically included, but the next line explicitly adds it to the collection.

Depending upon which CAs your receiver trusts, it's possible that you don't need to include all the certificates in the chain as above and you may be able to get away with including just the signature certificate. However, I'd suggest manually including at least up to but not including the root, as this is the .NET default behavior. Indeed, there is no real harm in including the entire chain either, but for all intents and purposes the receiver should already trust the root CA or else the whole concept of PKI breaks down.

Ben
  • 370
  • 1
  • 2
  • 6
  • This approach definitely worked for me to resolve the `certificate chain could not be built to a trusted root authority` exception as mentioned by the OP within an Azure serverless environment. – Jonathan B. Jan 05 '18 at 02:26
  • I am using Azure Functions and ended up using [Key Vault Explorer](https://github.com/elize1979/AzureKeyVaultExplorer) to load the public keys (.cer files) of the root and intermediate certificates. I then loaded those certs from the keys stored in the Key Vault using the semi-non-standard format used by that application (since .cer files aren't natively supported by Azure Key Vault). Once these certs were added as described by @ben, the issue was resolved! – Jonathan B. Jan 05 '18 at 02:31