runtime: SignedCms considers signature invalid on .NET Core but valid on .NET Framework

While @onovotny and I were experimenting with NuGet package signing on netcoreapp2.1 on Windows, we seem to have run in to a behavioral difference of SignedCms between .NET Core and the .NET Framework. The .NET Framework considers a PKCS#7 correctly signed, while .NET Core does not.

When NuGet timestamps a package, it validates the received timestamp after receiving the timestamp

We noticed that when running on .NET Core 2.1, on Windows, the validation step fails.

I distilled this down to what appears to be a behavioral difference between .NET Core and Desktop .NET Framework from SignedCms.

I produced a small repro here that basically:

  1. Has a base-64 encoded PKCS#7 object in tst.txt. This came from CryptRetrieveTimeStamp which is the same Win32 API that NuGet is using.
  2. Loads it in to a SignedCms.
  3. Calls CheckSignature on the first SignerInfo.
# Does not throw an exception
dotnet run --framework net471

and

# Throw an invalid signature exception
dotnet run --framework netcoreapp2.1

Both of these were run on Window 10 x64 1803.

Further more, it appears that SignedCms prevents the PKCS#7 object from round-tripping correctly. The PKCS#7 in tst.txt is valid according to the .NET Framework. If I load the signature into a SignedCms in .NET Core, then export again, the .NET Framework will not consider the re-exported PKCS#7 CMS valid anymore.

About this issue

  • Original URL
  • State: closed
  • Created 6 years ago
  • Reactions: 1
  • Comments: 27 (24 by maintainers)

Most upvoted comments

To close the loop on a thread here, I got word from DigiCert (@clintwilson) that their TSA is now properly sorting DER sets.

I tried several timestamps I produced a few minutes ago, and validating the timestamp did not use the compatMode: true path for ValidateSignature.

Thanks Clint!

/cc @bartonjs @onovotny

Approved for 2.1.5

[Fact]
public static void VerifyUnsortedAttributeSignature_ImportExportImport()
{
    SignedCms cms = new SignedCms();
    cms.Decode(SignedDocuments.DigiCertTimeStampToken);

    // Assert.NoThrows
    cms.CheckSignature(true);

    byte[] exported = cms.Encode();
    cms = new SignedCms();
    cms.Decode(exported);

    // Assert.NoThrows
    cms.CheckSignature(true);
}

Fails on the last CheckSignature. I’ll also add one for countersigning and additional-signing.

I’m sad at the amount of code required to go from “spec compliant” to “real-world compatible”.

Shiproom template

Description

If the creator of a CMS SignedData document does not sort a signer’s signed attributes before computing their signature then .NET Core will report the signature as invalid, but .NET Framework reports it as valid.

Customer Impact

Customers who receive signed documents which were built over unsorted attributes will be told they are invalid on .NET Core, even if they were told they were valid on .NET Framework. Since at least one free cryptographic time-stamping service is known to have this behavior, it can lead to situations where .NET Framework says a Signed NuGet Package is valid, but .NET Core says it is invalid.

Regression?

Behavioral regression from .NET Framework (the .NET Core implementation was new for netcoreapp2.1).

Risk

Low. The solution is just to try processing attributes in the file-specified order if sorted order fails, which matches the Crypt32 behavior.

Loading your test document in and stepping through the process I see

  • I compute the tstInfo digest as FD1766E9C1772950D946AB845EC105961236914A2BD4273980C7851E24E8FFAA, which matches the value of the messageDigest attribute.
    • This is the part that is different between CMS and old PKCS#7
  • I then compute the digest for the SignerInfo (“the result is the message digest of the complete DER encoding of the SignedAttrs value … A separate encoding of the signedAttrs field is performed for message digest calculation. The IMPLICIT [0] tag in the signedAttrs is not used for the DER encoding, rather an EXPLICIT SET OF tag is used.”) as 57E6A51620AB549890DFEC1494753BE1C2C8A8D6230FF15AD7B722E1A542F59A.
  • The signature is for the hash A46F7471F64F2B6DB38A9A540AF64525FB448A047C3B17106D75CDBE2ED18D0E

I see that the attributes are not listed in DER order (the last two are sorted incorrectly), making me wonder if Windows isn’t actually doing the required-by-DER sort on hash computation/verification. (I’ll recompute the hash with sorting off after some meetings). If that is the problem, then I’ll have to compare it with other implementations to see if it’s “do what the writer says” or “do what the spec says” or “try both ways” in practice.

Partially confirmed, if I disable the sorting required for SET-OF under DER, the SignerInfo digest is A46F7471F64F2B6DB38A9A540AF64525FB448A047C3B17106D75CDBE2ED18D0E