1

I try to sign some pdf's with a Belgian id card. To acheive that, I'm using the belgium eid middleware to sign the data and itext7 to stamp the pdf with the signature.

I use a PdfSigner (itext) and I have implement a IExternalSignature to call the eid middleware to sign the message.

All work well for the Belgian id card 1.7 with encryption RSA and hash SHA256.

But when I try to sign with the new Belgian id card 1.8 with encryption ECDSA and hash SHA384, the signature can't be validated by adobe reader (or other reader). "The document has been altered or corrupted".

It seems to be a mismatch somewhere in the hashing or ...

I search for some days but I have no more idea to fix that ...

Someone have an idea about what is going wrong?

Thanks in advance for your help.

Here some additional informations.

The external signature class:

    internal sealed class BeIDSignature : IExternalSignature
    {
        public string GetEncryptionAlgorithm()
        {
            return eidWrapper.Instance.GetEncryptionAlgorithm().ToString();
        }

        public string GetHashAlgorithm()
        {
            switch (EidWrapper.Instance.GetEncryptionAlgorithm())
            {
                case EidWrapper.EncryptionAmgorithm.RSA:
                    return DigestAlgorithms.SHA256;
                case EidWrapper.EncryptionAmgorithm.ECDSA:
                    return DigestAlgorithms.SHA384;
                default:
                    return null;
            }
        }
    
        public byte[] Sign(byte[] message)
        {
            return EidWrapper.Instance.SignData(message);
        }
    }

GetEncryptionAlgorithm will return RSA or ECDSA depending of the chip. The sign method will use the eid-mw packege to generate the signature.

A little piece of code of the sign method of the EidWrapper:

    if (key.KeyType.KeyType == CKK.EC)
    {
        session.SignInit(new Mechanism(CKM.ECDSA_SHA384), key);
        return session.Sign(data);
    }
    else if (key.KeyType.KeyType == CKK.RSA)
    {
        session.SignInit(new Mechanism(CKM.SHA256_RSA_PKCS), key);
        return session.Sign(data);
    }

You can find here a zip with 3 pdf files:

  • The original file
  • One signed directly with adobe (siganture is ok)
  • One signed with eid-mw and itext (signature is NOT ok). But remember that is working for RSA/SHA256 siganture.

https://easyupload.io/yzscsu

Thanks again for your time.

  • 1
    Could you please share some relevant source code? An example of a PDF signed with ECDSA/SHA384 would also be helpful. – veebee Apr 19 '22 at 09:09
  • Indeed, code and example would be helpful. One remark already now, though: ECDSA support in iText signing with `IExternalSignature` is limited; in particular the signatureAlgorithm entry in the CMS signature container SignerInfo is incorrect. But this problem used to be ignored by Adobe Reader. Thus, either Adobe Reader in some recent update got stricter or your problem is a different one. I would generally propose implementing `IExternalSignatureContainer` instead and using e.g. BouncyCastle to generate the CMS signature container oneself. – mkl Apr 19 '22 at 10:23
  • Hello, firstly thanks for your answers. I have editet my question and added some code and some demo files. – Nicolas Cop Apr 20 '22 at 10:20
  • Which Adobe Reader version did you use? I just opened demo_signed_itext.pdf in the current Reader and saw [this](https://i.stack.imgur.com/Swd9K.png)... – mkl Apr 20 '22 at 12:59
  • I use the last version of acrobat reader. BTW, if i try to validate the pdf with the european DSS, I got an "invalid siganture" (https://ec.europa.eu/digital-building-blocks/DSS/webapp-demo). I think it the last version of acrobat reader, the LTV is mandatory. What do you think about this? – Nicolas Cop Apr 20 '22 at 13:07
  • *"I use the last version of acrobat reader."* - That's weird, so did I. *'if i try to validate the pdf with the european DSS, I got an "invalid siganture"'* - that's to be expected: As mentioned above, iText ECDSA signing stores a wrong value for the signature algorithm in the signature container. eSig DSS uses that wrong algorithm and, therefore, validation fails. For that reason you definitively should switch from `IExternalSignature` to `IExternalSignatureContainer` usage. – mkl Apr 20 '22 at 13:19
  • 1
    For some more backgrounds, read see [this iText knowledge base article](https://kb.itextpdf.com/home/it7kb/examples/digital-signing-with-itext-7/part-i-overview-and-simple-cases#PartIOverviewandSimpleCases-WhichInterfacetoUse). – mkl Apr 20 '22 at 13:20
  • OK thanks, I will have a look on this right now. Honestly, I have ever seen some answers from you to others questions that must have been resolved using some IExternalSigntureContainer. But, I'm still a bit lost on how implement this container. I hope will firstly read the documentation behind your link and i hope to be able to implement it :) – Nicolas Cop Apr 20 '22 at 13:25
  • I just looked through there. Apparently I had focused on Java, there is no .NET PKCS#11 `IExternalSignatureContainer` implementation there, and the BeID example is based on the old cards.... :( – mkl Apr 20 '22 at 14:02
  • @mkl I can imagine the answer but I try my luck :) I would like to implement an IExternalSignatureConatiner. Have you got an idea on how I can generate the CMS signature using the signed data generated by the eid middleware? So in other words, how can i generate the CMS signature including the certificates and using the eid mw to sign the whole. – Nicolas Cop Apr 21 '22 at 09:30
  • Hello @mkl, as suggested I have implemented my own IExternalSignatureContainer. It works fine for RSA/SHA256 but always not for the ECDSA/SHA384. What do you use to "extract/analyze" the signature content of a PDF? openssl? Thx – Nicolas Cop Apr 28 '22 at 06:43
  • Can you share an example PDF? Other than that I do have some utility code for signature extraction etc. but they're using Java. – mkl Apr 28 '22 at 07:03
  • Thanks for your time, here some samples: https://easyupload.io/qhxyh4 From my IExternalSignatureContainer, I have firstly generate a PKCS7 container. Results: RSA/SHA256 signature is OK and ECDSA/SHA384 signature is KO. I have changed to generate PADES container. Results: RSA/SHA256 signature is OK (excepted LTV (I don't know why for now...), ECDSA/SHA384 signature is KO. Another hint, on the european SD-DSS tool I see that pdf with ECDSA/SHA384 (failing) siganture has a signature scope to full pdf instead partial pdf scope. I don't why :( – Nicolas Cop Apr 28 '22 at 10:39
  • How exactly do those ECDSA signatures fail? As a test I opened them in Adobe Acrobat, and Acrobat was happy. – mkl Apr 29 '22 at 13:26
  • Ah, I see, the signature card returns the ECDSA signature in _plain_ format but the signature algorithm OID you use requires the _TLV_ (tag-length-value) format, see e.g. [here](https://stackoverflow.com/a/67255440/1729265). Thus, either use OIDs for plain ECDSA (e.g. the BSI OIDs) or transform the plain signature into a TLV signature (e.g. using the `PlainToDer` method [here](https://git.itextsupport.com/projects/I7NS/repos/samples/browse/itext/itext.publications/itext.publications.signing-examples.simple/iText/SigningExamples/Simple/X509Certificate2Signature.cs#73)). – mkl Apr 29 '22 at 13:52
  • Oh man you are my hero :) I transformed the plain siganture to TLV and it works like a charm. I have already seen it in your other post but I don't know how to know if the eid middleware generate plain ecdsa or not and how to change it. So many thanks for your support. You can add you comment as an answer if you want your bounty. Another point, I have seen you are also very active on itext open source (sample), so if I can share my code (IExternalSignatureContainer with belgian card eid middleware), just tell me. – Nicolas Cop Apr 29 '22 at 14:44
  • Ok, I tried to sum up the important details from these comments in an answer. But certainly your actual working code would be interesting , too. You might want to post it as a second answer. iText may want to include that example in their article series. – mkl Apr 29 '22 at 18:20

3 Answers3

2

Here is a sample of an external container for the belgian eid smartcard.

The code is not fully implemented but you have a base to make a siganture in ECDSA/SHA384 correctly.

Hope that will help someone :)


internal sealed class BeIdExternalSignatureContainer : IExternalSignatureContainer
    {
        private IX509Store _crls = null;
        private readonly IHttpClientFactory _httpClientFactory;
        private int _crlsSize = 0;

        public BeIdExternalSignatureContainer(IHttpClientFactory httpClientFactory)
        {
            this._httpClientFactory = httpClientFactory;
        }

        public void ModifySigningDictionary(PdfDictionary signDic)
        {
            signDic.Put(PdfName.Filter, PdfName.Adobe_PPKLite);
            signDic.Put(PdfName.SubFilter, PdfName.ETSI_CAdES_DETACHED);
        }

        public int ComputeEstimateSize()
        {
            // Base on itext estimation
            if (this._crlsSize == 0)
            {
                this.InitializeCrls();
            }

            return (8192 + this._crlsSize + 4192) * 2 + 2;
        }

        public byte[] Sign(Stream data)
        {
            IReadOnlyDictionary<string, byte[]> certificatesBinaries = EidWrapper.Instance.GetCertificateFiles(new string[] {
                        Constants.SIGNATURE_CERTIFICATE_NAME,
                        Constants.CA_CERTIFICATE_NAME,
                        Constants.ROOT_CERTIFICATE_NAME
                    });

            X509Certificate signCertificate = new(X509CertificateStructure.GetInstance(certificatesBinaries[Constants.SIGNATURE_CERTIFICATE_NAME]));
            EidWrapper.EncryptionAlgorithms encryptionAlgorithm = EidWrapper.Instance.GetEncryptionAlgorithm();

            SignerInfoGenerator signerGenerator = new SignerInfoGeneratorBuilder()
                .WithSignedAttributeGenerator(new PadesSignedAttributeGenerator { SigningCertificate = signCertificate })
                .WithUnsignedAttributeGenerator(new PadesUnsignedAttributeGenerator(this._httpClientFactory))
                .Build(new BeIdSignatureFactory(encryptionAlgorithm), signCertificate);

            CmsSignedDataGenerator gen = new();
            gen.AddSignerInfoGenerator(signerGenerator);

            IX509Store x509CertStore = X509StoreFactory.Create(
                "Certificate/Collection",
                new X509CollectionStoreParameters(certificatesBinaries.Values.Select(b => new X509Certificate(X509CertificateStructure.GetInstance(b))).ToList()));

            gen.AddCertificates(x509CertStore);
            gen.AddCrls(this.Crls);

            CmsProcessableInputStream cmsProcessableInputStream = new(data);
            CmsSignedData signedData = gen.Generate(cmsProcessableInputStream, false);
            return signedData.GetEncoded();
        }

        private IX509Store Crls
        {
            get
            {
                if (this._crls == null)
                {
                    this.InitializeCrls();
                }

                return this._crls;
            }
        }

        private void InitializeCrls()
        {
            this._crlsSize = 0;
            List<X509Crl> crls = new();
            X509CrlParser crlParser = new();
            HttpClient httpClient = this._httpClientFactory.CreateClient();
            foreach (var crlUrl in BeIdSignatureConstants.CRL_URL_COLLECTION)
            {
                using HttpResponseMessage response = httpClient.Send(new HttpRequestMessage { Method = HttpMethod.Get, RequestUri = new Uri(crlUrl) });
                if (response.IsSuccessStatusCode)
                {
                    using MemoryStream ms = new MemoryStream();
                    response.Content.ReadAsStream().CopyTo(ms);
                    byte[] crlBytes = ms.ToArray();
                    this._crlsSize += crlBytes.Length;
                    crls.Add(crlParser.ReadCrl(crlBytes));
                }
            }

            this._crls = X509StoreFactory.Create(
                "CRL/Collection",
                new X509CollectionStoreParameters(crls));
        }
    }

internal class BeIdSignatureFactory : ISignatureFactory
    {
        private readonly EidWrapper.EncryptionAlgorithms _encryptionAlgorithm;
        public BeIdSignatureFactory(EidWrapper.EncryptionAlgorithms encryptionAlgorithm)
        {
            this._encryptionAlgorithm = encryptionAlgorithm;
        }

        public IStreamCalculator CreateCalculator()
        {
            BeIdSigner signer = new(this._encryptionAlgorithm);
            signer.Init(true, null);
            return new DefaultSignatureCalculator(signer);
        }

        public object AlgorithmDetails
        {
            get
            {
                return MapAlgorithm(this._encryptionAlgorithm);
            }
        }

        public static AlgorithmIdentifier MapAlgorithm(EidWrapper.EncryptionAlgorithms encryptionAlgorithm)
        {
            switch (encryptionAlgorithm)
            {
                case EidWrapper.EncryptionAlgorithms.RSA:
                    return new AlgorithmIdentifier(PkcsObjectIdentifiers.Sha256WithRsaEncryption, DerNull.Instance);
                case EidWrapper.EncryptionAlgorithms.ECDSA:
                    return new AlgorithmIdentifier(X9ObjectIdentifiers.ECDsaWithSha384, DerNull.Instance);
                default:
                    throw new ArgumentException($"Unsupported encryption algorithm: {encryptionAlgorithm}");
            }
        }
    }

internal class BeIdSigner : ISigner
    {
        private byte[] _input;
        private readonly EidWrapper.EncryptionAlgorithms _encryptionAlgorithm;

        public BeIdSigner(EidWrapper.EncryptionAlgorithms encryptionAlgorithm)
        {
            this._encryptionAlgorithm = encryptionAlgorithm;
        }

        public void Init(bool forSigning, ICipherParameters parameters)
        {
            this.Reset();
        }

        public void Update(byte input)
        {
            throw new NotImplementedException("The \"ISigner.Update\" method is not implemented for the \"BeIdSigner\" class.");
        }

        public void BlockUpdate(byte[] input, int inOff, int length)
        {
            this._input = input.Skip(inOff).Take(length).ToArray();
        }

        public byte[] GenerateSignature()
        {
            return this._encryptionAlgorithm == EidWrapper.EncryptionAlgorithms.ECDSA
                ? PlainToDer(EidWrapper.Instance.SignData(this._input))
                : EidWrapper.Instance.SignData(this._input);
        }

        public bool VerifySignature(byte[] signature)
        {
            throw new NotImplementedException("The \"ISigner.VerifySignature\" method is not implemented for the \"BeIdSigner\" class.");
        }

        public void Reset()
        {
            this._input = null;
        }

        public string AlgorithmName => throw new NotImplementedException("The \"ISigner.AlgorithmName\" property is not implemented for the \"BeIdSigner\" class.");

        private static byte[] PlainToDer(byte[] plain)
        {
            int valueLength = plain.Length / 2;
            BigInteger r = new(1, plain, 0, valueLength);
            BigInteger s = new(1, plain, valueLength, valueLength);
            return new DerSequence(new DerInteger(r), new DerInteger(s)).GetEncoded(Asn1Encodable.Der);
        }
    }
  • Where did you find the PadesSignedAttributeGenerator and PadesUnsignedAttributeGenerator classes? – brz Jun 29 '23 at 08:37
0

Some thoughts:

  • Are you sure that there is an ECDSA key available on the chip ? I had a short look at the documentation (not sure it's up to date - cf. eid-mw github), which only mentions RSA. Additionally if you can sign using RSA/SHA256, having ECDSA support as well would mean that there's a second key pair on the card - I have some doubts about this ;
  • Try to sign with ECDSA / SHA384 in Adobe Reader using your eID - check whether you can validate the signature ;
  • Validate the signature online, using the SD-DSS tool: the diagnostic data may help you in pin-pointing what is wrong (e.g. a sha384 digest was generated, but the signature structure mentions sha256 as digest algo).
veebee
  • 391
  • 2
  • 12
  • Hello, Thanks for your answer. Yes I'm sure that this chip is using ECDSA / SHA384. it seems to be the case for the new chips from 2021. When I sign directly in adobe, it's working well. The failing signature is of course not validated by the SD-DSS tool: SIG_CRYPTO_FAILURE : The signature is not intact. Seems to be a mismatch in the hash or the encryption, no? – Nicolas Cop Apr 20 '22 at 10:21
  • I also confirm that there isn'at any other private key on the chip to allow you to sign in RSA/SHA256. – Nicolas Cop Apr 20 '22 at 10:56
  • The signature byte range is completely different between the two (iText and Adobe). Additionally, the signature generated with Adobe is in PKCS7, while the iText one is an embedded CAdES. – veebee Apr 20 '22 at 13:17
0

This answer sums up the comments to the question.

iText and ECDSA signatures

First of all one has to realize that currently (i.e. for iText 7.2.2) ECDSA is not supported by all parts of the iText signing API.

This limitation is mostly due to the iText PdfPKCS7 class used to create and validate CMS signature containers embedded in PDFs. When it is used to create a signature container, the identifier it stores in the signatureAlgorithm field does not take the used hash algorithm into account. E.g. it uses the same value for RSA (with PKCS1 v1.5 padding) signatures, no matter if it's actually SHA1withRSA or SHA256withRSA or whichever combination.

For RSA this is ok because there indeed is an identifier that can be used for all these cases. For DSA it is somewhat ok because in many contexts DSA is limited to use with SHA1 only.

For ECDSA this is not ok, there only are identifiers taking the hash algorithm into account. iText uses the EC public key identifier in all these cases which is simply wrong. The reason why hardly anyone noticed this bug is that Adobe Acrobat validation apparently ignores the contents of this signatureAlgorithm field: You can even write the RSA identifier into this field of an ECDSA signature and the validation succeeds without an indication of a problem.

To create proper ECDSA signatures, therefore, one currently should not use the PdfPKCS7 class. As all the PdfSigner.signDetached methods internally use the PdfPKCS7 class, this in turn means that one must not use them but instead PdfSigner.signExternalContainer. As a consequence, one must not use an IExternalSignature implementation to retrieve one's signature value but instead an IExternalSignatureContainer implementation in which one builds the CMS signature container differently, for example using BouncyCastle classes.

In the case at hand the BeIDSignature implementation of IExternalSignature, therefore, must be replaced accordingly.

For further details please read the section Which Interface to Use of the iText knowledge base article Digital Signing with iText 7.

ECDSA signature formats

There are two major formats in which an ECDSA signature value can be stored, either as a TLV (DER) encoded sequence of two integers or (plain encoding) as the concatenation of fixed length representations of those two integers.

Depending on the used format one has to use specific algorithm identifiers for ECDSA and PLAIN-ECDSA respectively. If one needs a specific identifier, one can convert the signature value from one format to the other.

In the case at hand the Belgian ID card returns the ECDSA signature value in plain format. To use the more common non-PLAIN ECDSA identifiers, one has to convert that value to the DER format. This can be done using this method:

byte[] PlainToDer(byte[] plain)
{
    int valueLength = plain.Length / 2;
    BigInteger r = new BigInteger(1, plain, 0, valueLength);
    BigInteger s = new BigInteger(1, plain, valueLength, valueLength);
    return new DerSequence(new DerInteger(r), new DerInteger(s)).GetEncoded(Asn1Encodable.Der);
}

(X509Certificate2Signature helper method from the code examples of Digital Signing with iText 7)

mkl
  • 90,588
  • 15
  • 125
  • 265
  • As a follow-up to this excellent answer: the issues with mistaken OIDs for (EC)DSA have been fixed "en passant" as part of this work: https://github.com/itext/itext7/pull/106. This will make ECDSA work as advertised in iText 8 onwards. (Note: there's still no first-class support for PLAIN-ECDSA as I write this, but that's a technically relatively uncomplicated thing to add, and it shouldn't matter all that much for your use case.) – mval May 07 '23 at 11:14