Skip to content

Cms signature created with .Net Framework cannot be verified with .Net Core for an elliptic curve certificate #77377

Closed

Description

@jgustavs-tibco

Description

Link to a project to reproduce: https://github.com/jgustavs-tibco/cmssigner-incompatibility

If you use CmsSigner to create a signature in .Net Framework 4.8 with an ECC certificate then the signature cannot be verified using CmsSigner in .Net Core 6.0.

The signature is created as follows:

            using (var certificate = new X509Certificate2(CertificatePath, Password))
            {
                var cmsSigner = new CmsSigner(certificate);
                cmsSigner.IncludeOption = X509IncludeOption.EndCertOnly;
                var contentInfo = new ContentInfo(new Oid(PkcsObjectIdentifiersData), InputBytes);
                var signedCms = new SignedCms(SubjectIdentifierType.Unknown, contentInfo, true);
                signedCms.ComputeSignature(cmsSigner);
                File.WriteAllBytes(SignaturePath, signedCms.Encode());
            }

The signature is verified as follows.

                var contentInfo = new ContentInfo(InputBytes);
                var signedData = new SignedCms(contentInfo, true);
                signedData.Decode(signatureBytes);
                var signerInfos = signedData.SignerInfos;
                signedData.CheckSignature(true);

Note that it is possible to verify the signature in .Net Framework.

We have tried to generate the ECC certificate using .Net and using Java and it the verification fails in both cases. We have tried to reproduce the issue using RSA certificates. Then it works fine.

We noticed that the signature algorithm is SignedCms.SignerInfos is not the same if we try to generate the signature using .Net Framework and .Net Core which is not what we expected.

Reproduction Steps

Build the project in https://github.com/jgustavs-tibco/cmssigner-incompatibility and run test.bat.

This runs different combinations of .Net Framework and .Net Core and certificate key algorithms.

It gives the following output

Test create signature in .Net Framework. Verify in .Net Framework using elliptic curve.
 Verification ok
Test create signature in .Net Core. Verify in .Net Core using elliptic curve.
 Verification ok
Test create signature in .Net Framework. Verify in .Net Core using RSA.
 Verification ok
Test create signature in .Net Framework. Verify in .Net Core using elliptic curve.
 Verification failed
 Exception: SignerInfo digest algorithm '2.16.840.1.101.3.4.2.1' is not valid for signature algorithm ''.

Expected behavior

All combinations of .Net Framework and .Net Core and certificate key algorithms should work.

Actual behavior

The case where the signature is created in .Net Framework but verified in .Net Core does not work for ECC certificates.

Regression?

No response

Known Workarounds

No response

Configuration

We have tried .Net Framework 4.8 and .Net Core 6.0 on Windows.

Other information

No response

Activity

  1. ghost added
    untriagedNew issue has not been triaged by the area owner
    on Oct 24, 2022
  2. vcsjones commented on Oct 24, 2022

    @vcsjones
    Member

    Okay, I think I see what is going on here. In .NET Framework, the signature algorithm is 1.2.840.10045.2.1 (EcPublicKey).

    When we lookup the signature info here:

    lookup.Add(Oids.EcPublicKey, new ECDsaCmsSignature(null, default));

    We use a default hash algorithm name. When we compare the digest with the default digest of what we are expecting the comparison fails here:

    I think the right fix here for maintaining compat with .NET Framework is to skip the hash algorithm check if we are using .NET Framework's signature algorithm identifier. Basically change the check to:

    if (_expectedDigest != default(HashAlgorithmName) && _expectedDigest != digestAlgorithmName)

    (or make _expectedDigest nullable, etc).

    @bartonjs does this sound reasonable?

  3. bartonjs commented on Oct 24, 2022

    @bartonjs
    Member
    if (_expectedDigest != default(HashAlgorithmName) && _expectedDigest != digestAlgorithmName)

    seems like the right fix to me.

  4. self-assigned this
    on Oct 24, 2022
  5. vcsjones commented on Nov 14, 2022

    @vcsjones
    Member

    As a matter of an update to this, even once we get past the hash algorithm check, the digest that gets signed and verified differs between .NET Framework and .NET 6+. So this will take a little more time for me to understand exactly what .NET Framework is doing differently.

  6. removed their assignment
    on Jun 6, 2023
  7. jeffhandley commented on Jun 28, 2023

    @jeffhandley
    Member

    I'm moving this to Future as we won't be able to further investigate it during .NET 8. I'm also going to label this as help wanted [up-for-grabs] Good issue for external contributors since more investigation is needed to get to a root cause and proposed fix for this issue.

  8. added this to the Future milestone on Jun 28, 2023
  9. removed
    untriagedNew issue has not been triaged by the area owner
    on Jun 28, 2023
  10. ghost added
    in-prThere is an active PR which will close this issue when it is merged
    on Aug 27, 2023
  11. jborean93 commented on Aug 27, 2023

    @jborean93
    Contributor

    @vcsjones I tested out skipping the check if the EcPublicKey OID was in the signature and I found it worked just fine #91183.

    As a matter of an update to this, even once we get past the hash algorithm check, the digest that gets signed and verified differs between .NET Framework and .NET 6+

    Do you remember how you verified this? From testing I can generate a signature from both .NET and the Win32 API directly with an ECDSA key and the code is able to check the digest just fine. Even the test the PR adds is fine with the new changes.

    I do have a user report with a problematic signature signed by signtool.exe where these changes do fail with the below stacktrace:

    System.Security.Cryptography.CryptographicException: Invalid signature.
       at System.Security.Cryptography.Pkcs.SignerInfo.Verify(X509Certificate2Collection extraStore, X509Certificate2 certificate, Boolean verifySignatureOnly)
       at System.Security.Cryptography.Pkcs.SignerInfo.CheckSignature(X509Certificate2Collection extraStore, Boolean verifySignatureOnly)
       at System.Security.Cryptography.Pkcs.SignerInfo.CheckSignature(Boolean verifySignatureOnly)
    ...
    

    I don't think they are related as even Windows fails to verify the signature but I've yet to figure out why

    Untitled

  12. ghost removed
    in-prThere is an active PR which will close this issue when it is merged
    on Aug 28, 2023
  13. removed
    help wanted[up-for-grabs] Good issue for external contributors
    on Aug 29, 2023
  14. modified the milestones: Future, 9.0.0 on Aug 29, 2023
  15. modified the milestones: 9.0.0, 8.0.0 on Aug 31, 2023
  16. ghost locked as resolved and limited conversation to collaborators on Oct 1, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions