1
votes

im trying to sign a document several times simulating a signature by different users using itext 5.5.13.1, PdfStamper is on AppendMode. If document has not signatures, the certification level is CERTIFIED_NO_CHANGES_ALLOWED or CERTIFIED_FORM_FILLING_AND_ANNOTATIONS, else i dont set this param for PdfSignatureAppearence. After the second signing the first signature is invalid, because the document was changed. Any ideas how to fix this? Here's my code:

        public void Sign(string Thumbprint, string document, string logoPath) {

        X509Certificate2 certificate = FindCertificate(Thumbprint);

        PdfReader reader = new PdfReader(document);

        //Append mode
        PdfStamper st = PdfStamper.CreateSignature(reader, new FileStream(SignedDocumentPath(document), FileMode.Create, FileAccess.Write), '\0', null, true);


        int signatureWidth = 250;
        int signatureHeight = 100;       
        int NewXPos = 0;
        int NewYPos = 0;

        SetStampCoordinates(reader, st, ref NewXPos, ref NewYPos, signatureWidth, signatureHeight);            

        PdfSignatureAppearance sap = st.SignatureAppearance;

        if (reader.AcroFields.GetSignatureNames().Count == 0)
        {
            SetSignatureFieldOptions(certificate, sap, reader, "1", 1, NewXPos, NewYPos, signatureWidth, signatureHeight);
        }
        else {
            SetSignatureFieldOptions(certificate, sap, reader, "2", NewXPos, NewYPos, signatureWidth, signatureHeight);
        }

        Image image = Image.GetInstance(logoPath);
        image.ScaleAbsolute(50, 50);

        Font font1 = SetFont("TIMES.TTF", BaseColor.BLUE, 10, 0);
        Font font2 = SetFont("TIMES.TTF", BaseColor.BLUE, 8, 0);

        PdfTemplate layer = sap.GetLayer(2);
        Chunk chunk1 = new Chunk($"\r\nДОКУМЕНТ ПОДПИСАН\r\nЭЛЕКТРОННОЙ ПОДПИСЬЮ\r\n", font1);
        Chunk chunk2 = new Chunk($"Сертификат {certificate.Thumbprint}\r\n" +
                             $"Владелец {certificate.GetNameInfo(X509NameType.SimpleName, false)}\r\n" +
                             $"Действителен с {Convert.ToDateTime(certificate.GetEffectiveDateString()).Date.ToShortDateString()} " +
                             $"по {Convert.ToDateTime(certificate.GetExpirationDateString()).Date.ToShortDateString()}\r\n", font2);

        PdfTemplate layer0 = sap.GetLayer(0);
        image.SetAbsolutePosition(5, 50);
        layer0.AddImage(image);

        Paragraph para1 = SetParagraphOptions(chunk1, 1, 50, 0, 2, 1.1f);
        Paragraph para2 = SetParagraphOptions(chunk2, 0, 5, 15, 0.5f, 1.1f);

        ColumnText ct = new ColumnText(layer);
        ct.SetSimpleColumn(3f, 3f, layer.BoundingBox.Width - 3f, layer.BoundingBox.Height);
        ct.AddElement(para1);
        ct.AddElement(para2);
        ct.Go();

        layer.SetLineWidth(3);
        layer.SetRGBColorStroke(0, 0, 255);
        layer.Rectangle(0, 0, layer.BoundingBox.Right, layer.BoundingBox.Top);
        layer.Stroke();

        EncryptDocument(certificate, sap);
    }

    public X509Certificate2 FindCertificate(string Thumbprint) {
        X509Store store = new X509Store("My", StoreLocation.CurrentUser);
        store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
        X509Certificate2Collection found = store.Certificates.Find(
            X509FindType.FindByThumbprint, Thumbprint, true);

        X509Certificate2 certificate = found[0];

        if (certificate.PrivateKey is Gost3410_2012_256CryptoServiceProvider cert_key)
        {
            var cspParameters = new CspParameters
            {
                KeyContainerName = cert_key.CspKeyContainerInfo.KeyContainerName,
                ProviderType = cert_key.CspKeyContainerInfo.ProviderType,
                ProviderName = cert_key.CspKeyContainerInfo.ProviderName,
                Flags = cert_key.CspKeyContainerInfo.MachineKeyStore
               ? (CspProviderFlags.UseExistingKey | CspProviderFlags.UseMachineKeyStore)
               : (CspProviderFlags.UseExistingKey),
                KeyPassword = new SecureString()
            };

            certificate = new X509Certificate2(certificate.RawData)
            {
                PrivateKey = new Gost3410_2012_256CryptoServiceProvider(cspParameters)
            };
        }

        return certificate;
    }

    public Font SetFont(string TTFFontName, BaseColor color, float size, int style) {
        string ttf = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Fonts), TTFFontName);
        BaseFont baseFont = BaseFont.CreateFont(ttf, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);

        Font font = new Font(baseFont, size, style);
        font.Color = color;

        return font;
    }

    public Paragraph SetParagraphOptions(Chunk chunk, int ParagraphAligment, float marginLeft, float marginTop, float fixedLeading, float multipledLeading) {
        Paragraph paragraph = new Paragraph();
        paragraph.Alignment = ParagraphAligment;
        paragraph.IndentationLeft = marginLeft;
        paragraph.SpacingBefore = marginTop;
        paragraph.SetLeading(fixedLeading, multipledLeading);
        paragraph.Add(chunk);

        return paragraph;
    }

    public string SignedDocumentPath(string document) {
        string filename = String.Concat(Path.GetFileNameWithoutExtension(document), "_signed.pdf");
        string path = Path.Combine(Path.GetDirectoryName(document), filename);

        return path;
    }

    public void SetSignatureFieldOptions(X509Certificate2 certificate, PdfSignatureAppearance sap, PdfReader reader, string field, int level, int XPos, int YPos, int width, int height)
    {
        X509CertificateParser parser = new X509CertificateParser();

        try
        {
            Rectangle rectangle = new Rectangle(XPos, YPos, XPos + width, YPos + height);
            sap.SetVisibleSignature(rectangle, reader.NumberOfPages, field);
            sap.Certificate = parser.ReadCertificate(certificate.RawData);
            sap.Reason = "I agree";
            sap.Location = "Location";
            sap.Acro6Layers = true;
            sap.SignDate = DateTime.Now;
            sap.CertificationLevel = level;
        }

        catch (Exception ex)
        {
            if (ex.Message == $"The field {certificate.Thumbprint} already exists.")
                throw new Exception("Вы уже подписали данный документ");
        }
    }

    public void SetSignatureFieldOptions(X509Certificate2 certificate, PdfSignatureAppearance sap, PdfReader reader,string field, int XPos, int YPos, int width, int height) {
        X509CertificateParser parser = new X509CertificateParser();

        try
        {
            Rectangle rectangle = new Rectangle(XPos, YPos, XPos + width, YPos + height);
            sap.SetVisibleSignature(rectangle, reader.NumberOfPages, field);
            sap.Certificate = parser.ReadCertificate(certificate.RawData);
            sap.Reason = "I agree";
            sap.Location = "Location";
            sap.Acro6Layers = true;
            sap.SignDate = DateTime.Now;
        }

        catch (Exception ex) {
            if (ex.Message == $"The field {certificate.Thumbprint} already exists.")
                throw new Exception("Вы уже подписали данный документ");
        }
    }

    public void EncryptDocument(X509Certificate2 certificate, PdfSignatureAppearance sap) {

        PdfName filterName;
        if (certificate.PrivateKey is Gost3410CryptoServiceProvider)
            filterName = new PdfName("CryptoPro#20PDF");
        else
            filterName = PdfName.ADOBE_PPKLITE;

        PdfSignature dic = new PdfSignature(filterName, PdfName.ADBE_PKCS7_DETACHED);
        dic.Date = new PdfDate(sap.SignDate);
        dic.Name = certificate.GetNameInfo(X509NameType.SimpleName, false);
        if (sap.Reason != null)
            dic.Reason = sap.Reason;
        if (sap.Location != null)
            dic.Location = sap.Location;
        sap.CryptoDictionary = dic;

        int intCSize = 4000;
        Dictionary<PdfName, int> hashtable = new Dictionary<PdfName, int>();
        hashtable[PdfName.CONTENTS] = intCSize * 2 + 2;
        sap.PreClose(hashtable);
        Stream s = sap.GetRangeStream();
        MemoryStream ss = new MemoryStream();
        int read = 0;
        byte[] buff = new byte[8192];
        while ((read = s.Read(buff, 0, 8192)) > 0)
        {
            ss.Write(buff, 0, read);
        }

        ContentInfo contentInfo = new ContentInfo(ss.ToArray());
        SignedCms signedCms = new SignedCms(contentInfo, true);
        CmsSigner cmsSigner = new CmsSigner(certificate);
        signedCms.ComputeSignature(cmsSigner, false);
        byte[] pk = signedCms.Encode();

        byte[] outc = new byte[intCSize];
        PdfDictionary dic2 = new PdfDictionary();
        Array.Copy(pk, 0, outc, 0, pk.Length);
        dic2.Put(PdfName.CONTENTS, new PdfString(outc).SetHexWriting(true));
        sap.Close(dic2);
    }
1
First of all, if you set "CERTIFIED_NO_CHANGES_ALLOWED", you shouldn't be surprised that a second signature breaks the first one: If no changes are allowed, then in particular no second signature is allowed. And even "CERTIFIED_FORM_FILLING_AND_ANNOTATIONS" does not allow creating new signature fields, merely signing existing empty ones. For details on allowed and disallowed changes to signed PDFs, read this answer.mkl
I checked your link and choose NOT CERTIFIED. When im Signed document with it 2 times, first signature is still broken. Shoud i create 2 signature fields before sign, or some other options?.Alexander Goritskov
Please share a pdf you signed twice illustrating the issue. Usually analyzing the pdf will quicker allow to pin point the problem.mkl

1 Answers

1
votes

The Change

The most important part of your screenshot

screen shot

is the text "1 Page(s) Modified" between the signatures on the signature panel. This tells us that you do other changes than merely adding and filling signature fields. Inspecting the file itself one quickly recognizes the change:

  • In the original sample.pdf

    there is just a single content stream.

  • In sample_signed.pdf with one signature

    there are three content streams, the original one enveloped by the new ones.

  • In sample_signed_signed.pdf with two signatures

    there are five content streams, the former three enveloped by two new ones.

So in each signing pass you change the page content. As you can read in this answer, changes to the page content of signed documents are always disallowed. It doesn't even help that the contents of the added streams are trivial, each stream added in front contains:

q

and each stream added at the end contains

Q
q
Q

i.e. only some saving and restoring the graphics state happens.

The Cause

The changes described above are typical preparation steps done by the PdfStamper method GetOverContent, wrapping the original content in a q ... Q (save & restore graphics state) envelope to prevent changes there to influence additions in the OverContent and starting a new block also enveloped in such an envelope. That the latter block remained empty, indicates that the OverContent has not been edited.

I don't find such a call in the code you posted, but in your code the method SetStampCoordinates is missing. Do you probably call GetOverContent for the PdfStamper argument in that method?