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.