I have an exe file signed using signtool.exe. If I view the signature using Windows (Right click -> Properties -> Digital Signatures -> Details -> View Certificate -> Path), the chain looks like this, as expected:
Verisign
|
---Symantec Class 3 SHA256 Code Signing CA
|
---Me
However, if I load the certificate using the .NET API X509Certificate.CreateFromSignedFile(path)
and look at the certificate using X509Certificate2UI.DisplayCertificate(cert)
, I only see the leaf certificate. Of course, because the chain is missing, trying to build the chain using X509Chain
results in a failure.
Is this the expected behavior, and is there any way to build the entire chain using managed .NET code (read, without using WinVerifyTrust p/invoke)?
Yes, and no.
In your UI workflow you when you push "View Certificate" you switch from the file properties dialog to the CertUI dialog. CertUI (probably) only looks at the leaf/end-entity certificate and then builds the cert chain itself. So at that point it's slightly moot as to what else was in the signed file.
You can get slightly further in one call by reading all of the certificate information which was embedded in the file. My local testing shows that it wrote the EE cert (because it had to) and also the intermediate CA (with no signature line) but not the root certificate (because you normally omit root certificates in transfer... the other party either already has it or won't trust it, so it's wasted bytes).
var coll = new X509Certificate2Collection();
coll.Import("signedfile.exe");
// My coll had 2 certs at this point.
So you could pass all of those certificates into X509Chain.ChainPolicy.ExtraStore
in case it needs help resolving intermediates, but to determine the root you still need to build the chain.
using (X509Certificate2 cert = new X509Certificate2("signedfile.exe"))
{
X509Chain chain = new X509Chain();
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.IgnoreNotTimeValid;
bool validChain = chain.Build(cert);
if (!validChain)
{
// Whatever you want to do about that.
foreach (var status in chain.ChainStatus)
{
// In reality you can == this, since X509Chain.ChainStatus builds
// an object per flag, but since it's [Flags] let's play it safe.
if ((status.Status & X509ChainStatusFlags.PartialChain) != 0)
{
// Incomplete chain.
}
}
}
X509Certificate2Collection chainCerts = new X509Certificate2Collection();
foreach (var element in chain.ChainElements)
{
chainCerts.Add(element.Certificate);
}
// now chainCerts has the whole chain in order.
}