3

I have created code with iText 7 that is able to digitally sign a given PDF with a X509 certificate that uses an ECDSA key pair. When I open this signed PDF in Acrobat Reader DC, it correctly reads it, and verifies it to be valid (meaing doc is unmodified since signing, etc etc).

However, when I try to validate this same document with iText 7, the integrity and authenticity check returns false.

Here is a sample code:

// ...
PdfDocument pdfDoc = new(new PdfReader(stream));
SignatureUtil signUtil = new(pdfDoc);
IList<string> names = signUtil.GetSignatureNames();
foreach (string name in names) {
   PdfPKCS7 pkcs7 = signUtil.ReadSignatureData(name);
   bool wholeDocument = signUtil.SignatureCoversWholeDocument(name);
   bool signatureIntegrityAndAuthenticity = pkcs7.VerifySignatureIntegrityAndAuthenticity(); // this returns false, even though Adobe has no problem verifying the signature.
// more code to read values and put them in a json
}
// ...

And a sample output that I extract from the signature:

{
  "$id": "1",
  "signatures": [
    {
      "$id": "2",
      "integrityAndAuthenticity": false, // <---- should be true in my opinion.
      "revisionNumber": 1,
      "coversWholeDocument": true,
      "invisibleSignature": true,
      "filterSubType": "ETSI.CAdES.detached",
      "encryptionAlgorithm": "ECDSA",
      "hashAlgorithm": "SHA512",
      "nameOfSigner": "C=HU, CN=Teszt Elek, GIVENNAME=Elek, L=Budapest, O=Teszt ECC Szervezet, SN=202010260807, SURNAME=Teszt",
      "alternateNameOfSigner": null,
      "signDate": "2021-04-22T12:50:33Z",
      "timestamp": {
        "$id": "3",
        "signDate": "2021-04-22T12:50:33Z",
        "service": "C=HU,L=Budapest,O=Microsec Ltd.,2.5.4.97=VATHU-23584497,CN=Test e-Szigno TSA 2017 01",
        "verified": true,
        "hashAlgorithmOid": "2.16.840.1.101.3.4.2.3"
      },
      "location": " Hungary",
      "reason": "Approval",
      "contactInfo": "",
      "name": "GUID_97e1669d-0fbe-409a-a8fc-8518a1bae460",
      "signatureType": "approval",
      "fillInAllowed": true,
      "annotationsAllowed": true,
      "fieldLocks": []
    }
  ],
  "revisions": 1,
  "valid": false // is an aggregate of all the signatures integrity in the array above
}

I am using the latest iText 7 version as of posting, and my platform is ASP.NET 5 (.Net 5). The example code corresponds to iText's own example codes, that they provide for their learing books (but updated to 7, since the books were written for iText 5).

I am adding a sample pdf, and some combinations of signed versions in this google drive. It contains a sample pdf, that is unsigned and pure. That pdf is then signed separately with ECDSA and an RSA key. Those are then both signed with the opposite type of key. And all of their validation results. Note: in the json files integrityAndAuthenticity is just named as valid for brevity, but the value it holds is the result of pkcs7.VerifySignatureIntegrityAndAuthenticity(). All signing is done by my app (using iText 7).

Edit #1: I am providing the code that does the signing:

using System;
using System.Security.Cryptography;
using iText.Signatures;

public class EcdsaSignature : IExternalSignature
{
    private readonly string _encryptionAlgorithm;
    private readonly string _hashAlgorithm;
    private readonly ECDsa _pk;

    public EcdsaSignature(ECDsa pk, string hashAlgorithm)
    {
        _pk = pk;
        _hashAlgorithm = DigestAlgorithms.GetDigest(DigestAlgorithms.GetAllowedDigest(hashAlgorithm));
        _encryptionAlgorithm = "ECDSA";
    }

    public virtual string GetEncryptionAlgorithm()
    {
        return _encryptionAlgorithm;
    }

    public virtual string GetHashAlgorithm()
    {
        return _hashAlgorithm;
    }

    public virtual byte[] Sign(byte[] message)
    {
        return _pk.SignData(message, new HashAlgorithmName(_hashAlgorithm), DSASignatureFormat.Rfc3279DerSequence); // <---- I have solved the iText 7 issue by providing this enum to the SignData() method.
    }
}

and then:

using (var key = myCertificate.GetECDsaPrivateKey()) {
   /*PdfSigner*/ signer.SignDetached(new EcdsaSignature(key, DigestAlgorithms.SHA512), chainArray, crlList, ocspClient, tsaClient, 0, subfilter);
}

Thanks to @mkl's response it cleared up some confusions about the signature formats, and thankfully Microsoft supports the TLV sequence format in the SignData() method, so I don't have to reverse engineer the signing process to achieve what I want. Although I only assume this enum is the TLV sequence described in the answer, because it uses different RFCs or IEEE specifications to refer to it. Nonetheless it solved my question. (I have also added a new pdf to the drive sample_signed_ecdsa_Rfc3279DerSequence.pdf and a corresponding response JSON.) Probably by default it uses DSASignatureFormat.IeeeP1363FixedFieldConcatenation, because specifying that argument didn't change the signature validity, but specifying the other made it valid in iText 7 as well.

Now as for interoperability, I'm not sure how I could change my code to use IExternalSignatureContainer. I'm new to this digital signature stuff, I have only followed the iText 5 book and the updated iText 7 examples on their site, and unfortunately I have been unable to find examples or documentation about it, other than the API reference.

1 Answers1

4

There is an issue in your ECDSA signatures which is only ignored by Adobe Acrobat but not by iText 7.

There are two major formats to encode an ECDSA signature value:

  • as a TLV SEQUENCE of two INTEGER values

    ECDSA-Sig-Value ::= SEQUENCE {
      r  INTEGER,
      s  INTEGER
    }
    

    (see ANSI X9.62, RFC 5480, and SEC 1: Elliptic Curve Cryptography, in the SECG document extended by two additional, optional values);

  • as the concatenation of two integers with a fixed length (see BSI TR-03111), aka the plain format.

The format to use depends on the signature algorithm applied. For example:

  • SHA512withECDSA (OID 1.2.840.10045.4.3.4) implies the use of the TLV SEQUENCE format.
  • SHA512withPLAIN-ECDSA (OID 0.4.0.127.0.7.1.1.4.1.5) implies the use of the plain format.

Unfortunately, though, your ECDSA CMS signature container SignerInfo objects have an OID 1.2.840.10045.2.1 where the signature algorithm should be; and that OID is merely the OID for an ECDSA public key, not a specific algorithm identifier at all. In different validators this has different effects:

  • Adobe Acrobat ignores that the OID is not valid as signature algorithm OID and accepts signatures either in TLV and in plain format.
  • iText 7 ignores that the OID is not valid as signature algorithm OID and assumes SHAXXXwithECDSA, i.e. expects a TLV encoded signature value.
  • eSig DSS considers the signature cryptographically broken due to the OID not valid as signature algorithm OID.

Thus, if you merely need that your signatures are accepted by Adobe Acrobat and iText 7, it suffices to make sure that the ECDSA signature value is in the TLV format.

On the other hand, if you want your signatures to be more interoperable, change your iText 7 based signing code to use an IExternalSignatureContainer implementation (not an IExternalSignature one) in which you build a correct CMS signature container. Beware, the ECDSA support in iText is limited; in particular, you will have to use a signature algorithm implying a TLV format.

mkl
  • 90,588
  • 15
  • 125
  • 265
  • Much appreciated for the extensive response. The `sign()` method I use accepts this DSASignatureFormat enum. Before I didn't provide it as an argument, so I assume it used the `IeeeP1363FixedFieldConcatenation` value, because I have now tried with both, and without changin anything else in my code, it worked with `Rfc3279DerSequence`. But I am confused because this uses different standards to refer to the things you did. I'll update my question and add my signing code for clarity. https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.dsasignatureformat – Martossy Alex Apr 26 '21 at 05:51
  • 1
    *"But I am confused because this uses different standards to refer to the things you did."* - Well, a lot of standards in this regard copy from one another, or probably more correctly, profile one another. If you look into RFC 3279, the section 2.2.3 _ECDSA Signature Algorithm_ starts with _"The Elliptic Curve Digital Signature Algorithm (ECDSA) is defined in [X9.62]."_ and X9.62 is one of the sources I referenced; the RFC here only profiles the use of ECDSA (and other algorithms) for X.509 PKIs. Furthermore, the RFC 5480 I mentioned actually updates RFC 3279. – mkl Apr 26 '21 at 07:56
  • 1
    Also the BSI TR-03111 refers to IEEE P1363 and P1363a, it doesn't invent the plain format but merely associates it with certain OIDs under 1.2.840.10045.4 – mkl Apr 26 '21 at 08:02
  • @mkl is is sufficient for PdfPKCS7 to work to set digestEncryptionAlgorithmOid to valid ECDSA oid or it takes more? I have set digestEncryptionAlgorithmOid to "1.2.840.10045.4.3.2" and eSig DSS validator seems to treat signature as valid – JK_codes Sep 16 '22 at 11:39
  • @JK_codes *"is is sufficient for PdfPKCS7 to work to set digestEncryptionAlgorithmOid to valid ECDSA oid or it takes more? I have set digestEncryptionAlgorithmOid to "1.2.840.10045.4.3.2" and eSig DSS validator seems to treat signature as valid"* - That OID is for "ecdsa-with-SHA256", so your change only works as long as you use SHA256. – mkl Sep 16 '22 at 18:57