Подписание существующего документа PDF иногда приводит к повреждению файла

Я пишу демонстрационное доказательство концепции добавления электронных подписей в существующие PDF-файлы. Я столкнулся с одной странной проблемой, которую я не понимаю. Кажется, что добавление подписи к некоторым документам работает нормально, а добавление подписи к другим не работает, и создается поврежденный файл, который Adobe Reader не может открыть.

Вот мой код:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using iText;
using iText.Kernel.Pdf;
using System.IO;
using iText.Layout;
using iText.Layout.Element;
using iText.Kernel.Geom;
using Org.BouncyCastle.Crypto.Tls;
using iText.Signatures;
using System.Collections.ObjectModel;
using Org.BouncyCastle.Pkcs;
using System.Security.Cryptography.X509Certificates;
using Org.BouncyCastle.Crypto;
using System.Security.Cryptography;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using iText.IO.Image;

namespace LTVSkilrikjaDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            string welcomeText = "Welcome to LTVSkilríkjaDemotolid!";
            string pressEnterToTry = "Commands: 's' - sign, 'stop' - stops the programme";
            Console.WriteLine(welcomeText);
            Console.WriteLine(pressEnterToTry);

            // Base directory prepared
            string basedir = AppDomain.CurrentDomain.BaseDirectory;
            int index = basedir.IndexOf(@"bin\");
            basedir = basedir.Remove(index);

            string readString = Console.ReadLine().ToLower();

            while(!readString.Equals("stop"))
            {
                if(readString.Equals("c"))
                {
                    string inFile = "Infile2.pdf";
                    string outFile = "Outfile2.pdf";

                    // Open PDF document and decide where to write the new document
                    PdfWorker worker = new PdfWorker();
                    worker.ReadPdf(basedir + "App_Data\\InFiles\\" + inFile, basedir + "App_Data\\OutFiles\\" + outFile);

                    // Start working on certificate
                    X509Store store = new X509Store("My");

                    store.Open(OpenFlags.ReadOnly);

                    Collection<Org.BouncyCastle.X509.X509Certificate> xcertificates = new Collection<Org.BouncyCastle.X509.X509Certificate>();

                    foreach (X509Certificate2 mCert in store.Certificates)
                    {
                        if (mCert.Subject.IndexOf("CN=Róbert") > -1)
                        {
                             xcertificates.Add(Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(mCert));
                        }
                    }

                    Org.BouncyCastle.X509.X509Certificate[] certificatesProcessed = new Org.BouncyCastle.X509.X509Certificate[xcertificates.Count];
                    for(int i = 0; i < xcertificates.Count; i++)
                    {
                        certificatesProcessed[i] = xcertificates[i];
                    }

                    var pk = Org.BouncyCastle.Security.DotNetUtilities.GetKeyPair(store.Certificates[5].PrivateKey).Private;

                    try
                    {
                        worker.Sign(certificatesProcessed, pk, DigestAlgorithms.SHA1, PdfSigner.CryptoStandard.CADES, "No apparent raisin!", "Lost in Iceland", null, null, null, 0, true, basedir);
                    }
                    catch(Exception ex)
                    {
                        Console.ForegroundColor = ConsoleColor.Red;
                        Console.WriteLine("Error! " + ex.Message + "\n\r" + ex.StackTrace);
                        if(ex.InnerException != null)
                        {
                            Console.WriteLine("Inner exception: " + ex.InnerException.Message);
                        }
                        Console.ForegroundColor = ConsoleColor.Gray;                      
                    }
                }
                else if(!readString.Equals("stop"))
                {
                    Console.WriteLine("Command not understood. Understand only 's' and 'stop'.");
                }

                readString = Console.ReadLine();
            }

            Console.WriteLine("Goodbye!");
            System.Threading.Thread.Sleep(500);

        }
    }

    public class PdfWorker
    {
        private PdfDocument _document;
        private string _source;
        private string _dest;

        public void ReadPdf(string source, string dest)
        {
            _source = source;
            _dest = dest;
        }

        public void Sign(Org.BouncyCastle.X509.X509Certificate[] chain, Org.BouncyCastle.Crypto.ICipherParameters pk,
            string digestAlgorithm, PdfSigner.CryptoStandard subfilter, string reason, 
            string location, Collection<ICrlClient> crlList, IOcspClient ocspClient, ITSAClient tsaClient,
            int estimatedSize, bool initial, string baseDir)
        {
            File.Copy(_source, _dest, true);
            FileStream f = new FileStream(_dest, FileMode.Append);
            try
            {

                PdfSigner signer = new PdfSigner(new PdfReader(_source), f, true);
                _document = signer.GetDocument();
                _document.AddNewPage();

                // Work the last page
                Rectangle pageSize = _document.GetLastPage().GetPageSizeWithRotation();
                //PdfWriter w = _document.GetWriter();
                //long currentPos = w.GetCurrentPos();
                float llx = pageSize.GetWidth() - 350 - 20; //pageSize.GetWidth() / 2 - 350 / 2;
                float lly = pageSize.GetHeight() - 50 - 20; // pageSize.GetHeight() / 2 - 150 / 2;
                float urx = 350; //llx + 350;
                float ury = 50; //lly + 150;
                PdfSignatureAppearance appearance = signer.GetSignatureAppearance();
                appearance.SetPageRect(new Rectangle(llx, lly, urx, ury));
                appearance.SetReason(reason);
                appearance.SetLocation(location);

                byte[] imagebytes = File.ReadAllBytes(baseDir + "App_Data\\UndirskriftDemo.png");
                // It is not possible to use the path as it contains Icelandic characters 
                // which itext chokes on. We use byte array instead
                ImageData imgData = ImageDataFactory.Create(imagebytes);
                Image img = new Image(imgData);
                img = img.ScaleToFit(350.0f, 50.0f);
                appearance.SetImage(imgData);

                int pageCount = _document.GetNumberOfPages();

                // Creating the appearance
                if(initial == true)
                {
                    signer.SetCertificationLevel(PdfSigner.CERTIFIED_FORM_FILLING_AND_ANNOTATIONS);
                }

                appearance.SetPageNumber(pageCount);
                Rectangle rect = new Rectangle(10, 50, 350, 50);
                appearance.SetPageRect(rect).SetPageNumber(pageCount);
                appearance.SetRenderingMode(PdfSignatureAppearance.RenderingMode.NAME_AND_DESCRIPTION);
                signer.SetFieldName(signer.GetNewSigFieldName());

                // Creating the signature
            IExternalSignature pks = new PrivateKeySignature(pk, digestAlgorithm);

                signer.SignDetached(pks, chain, crlList, ocspClient, tsaClient, estimatedSize, subfilter);

                Console.WriteLine("Signing successful!");
            }
            catch(Exception ex)
            {
                throw ex;
            }
            finally
            {
                _document.Close();
                f.Close();
            }
        }
    }
}

Тогда смотрите документы здесь. Infile1.pdf я не могу подписать, но Infile2.pdf подписывается нормально. Outfile1.pdf — поврежденный файл. https://app.box.com/s/52jqe8qirl80km6hunxucs00dntx70o5

Что вызывает это? Есть ли что-то о входном файле PDF или вышеуказанной программе?


person rbadi76    schedule 21.11.2016    source источник


Ответы (1)


Проблема в вашей программе, точнее в вашем методе PdfWorker.Sign:

File.Copy(_source, _dest, true);
FileStream f = new FileStream(_dest, FileMode.Append);
try
{
    PdfSigner signer = new PdfSigner(new PdfReader(_source), f, true);
    ...

Здесь вы сначала копируете файл для подписи в место назначения, а затем добавляете к нему вывод PdfSigner.

Но этот PdfSigner вывод представляет собой полный подписанный PDF-файл, т. е. исходный текст плюс дополнительная редакция, в которой добавлена ​​подпись. Таким образом, в целевом файле у вас есть две копии исходного кода, а затем несколько подписей, созданных в предположении, что предшествует только одна копия исходного кода.

Чтобы решить эту проблему, просто удалите операцию File.Copy(_source, _dest, true) и не открывайте FileStream с помощью FileMode.Append.


Даже после этого исправления подписание файла примера OP «Infile1.PDF» по-прежнему создает поврежденный PDF-файл, в то время как подпись файла образца «Infile2.pdf» теперь выполняется успешно. Причиной является ошибка iText 7.0.0, описанная в этом ответе, который (насколько я вижу) исправлен в 7.0. .1. Поскольку «Infile1.PDF» использует потоки объектов, а «Infile2.pdf» — нет, затрагивается только предыдущий файл.

person mkl    schedule 23.11.2016
comment
Я попытался закомментировать строку с помощью File.Copy(...), но все равно получаю ту же ошибку, когда пытаюсь открыть выходной файл. Причина, по которой я скопировал файл, заключается в том, что я думал, что исходный файл будет изменен, чего я не хотел. Также я не хотел создавать временный файл, а затем, наконец, окончательный документ, как это делается в одном примере, с которым я столкнулся. - person rbadi76; 23.11.2016
comment
но я по-прежнему получаю ту же ошибку, когда пытаюсь открыть выходной файл. - Пожалуйста, поделитесь и этим файлом. Он может отображать ту же ошибку, но по другой причине. - person mkl; 23.11.2016
comment
@ rbadi76 а, еще одна вещь, не открывайте FileStream с FileMode.Append: вы не хотите добавлять вывод PdfSigner к чему-либо, а также к какому-то старому тестовому выводу, случайно все еще там ... - person mkl; 24.11.2016
comment
Я изменил FileMode на FileMode.Create. Тот же результат. Посмотрите два новых файла в уже общей папке с именами Outfile1_nocopy.pdf и Outfile1_nocopy_create.pdf. Папка: ссылка - person rbadi76; 24.11.2016
comment
@ rbadi76 Хорошо, в новых примерах больше нет дублирования частей файла. Оставшаяся проблема - это ошибка iText 7.0.0, описанная в этом ответе, и это проясняет, почему подписывание вашего Infile 1.PDF фатально терпит неудачу, в то время как подпись Infile2.pdf завершается успешно. - person mkl; 24.11.2016
comment
Вот и все! Я загрузил исходный код и добавил строку, которую вы упомянули в этом посте, создал ее и обновил dll в своем проекте, и вуаля, я получил неповрежденный файл. Большое вам спасибо за вашу помощь! - person rbadi76; 24.11.2016