3
votes

I am using iText 7 to sign pdf documents. This works without problems, and the signature is shown as valid.

In addition to the digital signature, i want to show a visual representation on the pdf. This is described in the digital signature book chapter 2.4 Creating different signature appearances.

The produced pdf shows this appearance if i open it using adobe reader. enter image description here enter image description here

The first image is a pdf created using word and the save as pdf functionality. The second image is a demo pdf i just downloaded random.

If i open the first pdf in chrome, the signature appearance text is not shown, but if i open the pdf which was initially created using word, the signature apperance is missing. enter image description here enter image description here

Any ideas on whats wrong with the pdf which doesn't show the signature appearance in chrome?

edit: Links to the documents

edit 2: Code sample

The following code sample will sign a pdf document using a local certificate and place some text into the SignatureAppearance which is not shown in chrome.

using iText.Kernel.Geom;
using iText.Kernel.Pdf;
using iText.Signatures;
using System.IO;
using System.Security.Cryptography.X509Certificates;

namespace PdfSigning.Lib.Helpers
{
    public class SignPdfTest
    {
        public static byte[] SingPdfUsingCertificate(X509Certificate2 cert2, byte[] pdfToSign)
        {
            var apk = Org.BouncyCastle.Security.DotNetUtilities.GetKeyPair(cert2.PrivateKey).Private;

            IExternalSignature pks = new PrivateKeySignature(apk, DigestAlgorithms.SHA512);

            var cp = new Org.BouncyCastle.X509.X509CertificateParser();
            var chain = new[] { cp.ReadCertificate(cert2.RawData) };

            using (PdfReader reader = new PdfReader(new MemoryStream(pdfToSign)))
            {
                using (MemoryStream fout = new MemoryStream())
                {
                    StampingProperties sp = new StampingProperties();
                    sp.UseAppendMode();

                    PdfSigner signer = new PdfSigner(reader, fout, sp);
                    PdfSignatureAppearance appearance = signer.GetSignatureAppearance();

                    appearance.SetPageNumber(1);
                    appearance.SetLayer2Text("Hello world");
                    appearance.SetLayer2FontSize(8);

                    Rectangle pr = new Rectangle(10, 10, 200, 100);
                    appearance.SetPageRect(pr);

                    appearance.SetRenderingMode(PdfSignatureAppearance.RenderingMode.DESCRIPTION);
                    appearance.SetPageRect(pr);

                    signer.SignDetached(pks, chain, null, null, null, 0, PdfSigner.CryptoStandard.CMS);
                    return fout.ToArray();
                }
            }
        }
    }
}



    private static void SignDocumentUsingCertificateConfiguration()
    {
        try
        {
            var certificateSignatureConfiguration = new CertificateSignatureConfiguration();
            var cert2 = new X509Certificate2(@"C:\temp\MyCertificate.pfx", "mypassword", X509KeyStorageFlags.Exportable);
            CertificatePdfSigner certPdfSigner = new CertificatePdfSigner(certificateSignatureConfiguration);

            byte[] signedPdf = PdfSigning.Lib.Helpers.SignPdfTest.SingPdfUsingCertificate(cert2, File.ReadAllBytes(@"C:\temp\WordSaveAsPdf.pdf"));

            File.WriteAllBytes(@"C:\temp\WordSaveAsPdf_Signed.pdf", signedPdf);
            Console.WriteLine("Done");
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
3

3 Answers

4
votes

In short

Chrome appears to not read object streams of hybrid reference PDFs, in particular not in the incremental update added during signature creation.

iText, on the other hand, puts nearly all its changes during signing into an object stream.

Thus, Chrome is not aware of the added signature and its appearance.

One can resolve the situation by forcing iText not to create an object stream here.

What is special about the Word generated source PDF?

PDF files contain object cross reference information which map object numbers to offsets of the respective starts of these objects in the file. These information can be stored in two ways, as cross reference table and (since PDF 1.5) also as cross reference stream. Also since PDF 1.5 the format allows to put non-stream objects into so called object streams which allows superior compression as only stream contents can be compressed.

As most PDF viewers at the time PDF 1.5 has been introduced did not support cross reference and object streams, a mixed, hybrid reference style was also introduced then. In this style the basic objects in a PDF which are strictly necessary to display it, are added normally (not in object streams) and are referenced from cross reference tables. Extra information which is not strictly necessary is then added in object streams and referenced from cross reference streams.

MS Word creates PDFs in this hybrid style and is virtually the only software to do so.

What is special about the iText signed result PDF?

iText put nearly all the changes into an object stream in a new incremental update.

Apparently, though, Chrome does not fully support object and cross reference streams, in particular not if combined with further incremental updates.

Thus, Chrome is not aware of the added signature and its visualization.

How to resolve the problem?

What we need to do, therefore, is convince iText that it shall not add important data in an object stream during signing. Due to member variable visibilities this is not as easy as one would like; I used reflection here for that.

In your code simply use the following PdfSignerNoObjectStream instead of PdfSigner:

public class PdfSignerNoObjectStream : PdfSigner
{
    public PdfSignerNoObjectStream(PdfReader reader, Stream outputStream, StampingProperties properties) : base(reader, outputStream, properties)
    {
    }

    protected override PdfDocument InitDocument(PdfReader reader, PdfWriter writer, StampingProperties properties)
    {
        try
        {
            return base.InitDocument(reader, writer, properties);
        }
        finally
        {
            FieldInfo propertiesField = typeof(PdfWriter).GetField("properties", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
            WriterProperties writerProperties = (WriterProperties) propertiesField.GetValue(writer);
            writerProperties.SetFullCompressionMode(false);
        }
    }
}

Beware, though, tweaking iText functionality like this is not guaranteed to work across versions. I tested it for a recent iText-7.1.7-SNAPSHOT development state; I expect it to also work for the previous 7.1.x versions.

Is this a Chrome bug? Or an iText bug? Or what?

Most likely it's kind of both.

On one hand the Chrome PDF viewer appears to have issues with hybrid reference PDFs. Considering how long they have been part of the PDF format, that is somewhat disappointing.

And on the other hand the PDF specification requires in the context of hybrid reference documents:

In general, the objects that may be hidden are optional objects specified by indirect references. [...]

Items that shall be visible include the entire page tree, fonts, font descriptors, and width tables. Objects that may be hidden in a hybrid-reference file include the structure tree, the outline tree, article threads, annotations, destinations, Web Capture information, and page labels.

(ISO 32000-1, section 7.5.8.4 Compatibility with Applications That Do Not Support Compressed Reference Streams)

In the case at hand an (updated) page object is in the object stream, i.e. hidden from viewers not supporting cross reference and object streams.

Currently iText 7 PdfDocument attempts to enforce FullCompression on PdfWriters if the underlying PdfReader has any cross reference stream (HasXrefStm):

writer.properties.isFullCompression = reader.HasXrefStm();

(PdfDocument method Open)

Probably it shouldn't enforce that if the PdfReader also is identified as hybrid reference stream (HasHybridXref).

0
votes

This might be simply caused by the chrome build-in PDF reader. As far as I understood his case, the person who requested help from Chrome devs in this question has received some answers and was redirected to another part of the forum where he could get help. I can try to recreate the problem with itext-sharp 5 (I used that in a previous project) and see if that signature is not shown in Chrome but the odds won't be good.

0
votes

This sounds an awful lot like a case of the "Needs Appearances" flag not being set. Back in my day (wheeze) iText form fields were generated with as little graphical data as possible, and would set the \NeedsAppearances flag to true, letting the PDF viewer in question (Acrobat Reader was about it back then) that it needed to generate the form fields' appearances before trying to draw them to screen.

And visible PDF Signatures are held in form fields.

So its at least theoretically possible that you can fix this programmatically by telling iText to (re?)generate the form field appearances.