Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PDF shows multiple document version for multiple signature #222

Open
dixit-atharva opened this issue Dec 20, 2024 · 3 comments
Open

PDF shows multiple document version for multiple signature #222

dixit-atharva opened this issue Dec 20, 2024 · 3 comments
Labels
Cannot Reproduce https://xkcd.com/583/

Comments

@dixit-atharva
Copy link

I have tried out to implement signature functionality, but when i have tried to sign document for one user which has around 2 pages it goes signed successfully but had two different version for documents, look on below image, it should only revision one for both the signature instead of rev.1 and rev.2

image

@dixit-atharva dixit-atharva changed the title PDF shows multiple signature version PDF shows multiple document version for multiple signature Dec 20, 2024
@TH-Soft TH-Soft added the Cannot Reproduce https://xkcd.com/583/ label Dec 20, 2024
@TH-Soft
Copy link

TH-Soft commented Dec 20, 2024

Is this really an issue?
I have no idea how to change this.

https://docs.pdfsharp.net/General/Issue-Reporting.html

@dixit-atharva
Copy link
Author

// PDFsharp - A .NET library for processing PDF
// See the LICENSE file in the solution root for more information.

using System.Security.Cryptography.X509Certificates;
using DefaultSigner;
using PdfSharp.Drawing;
using PdfSharp.Drawing.Layout;
using PdfSharp.Fonts;
using PdfSharp.Pdf;
using PdfSharp.Pdf.IO;
using PdfSharp.Pdf.Signatures;
using PdfSharp.Quality;

// The minimum assets version required.
// Our test .pfx file is stored in the assets.
const int requiredAssets = 1014;
IOUtility.EnsureAssetsVersion(requiredAssets);

// When we add a timestamp to the digital signature, PDFsharp must access a timestamp server on the Internet.
// This is done async and therefore, we use 'await document.SaveAsync(filename)' here.
// Using the sync version of 'Save' also works, but it blocks the current thread during the web request.

#if CORE
// Core build does not use Windows fonts, so set a FontResolver that handles the fonts our samples need.
GlobalFontSettings.FontResolver = new SamplesFontResolver();
#endif

// Create a signed document without a timestamp (which is very common).
//await CreateSignedDocument();

// PDFsharp 6.2 cannot add a timestamp when using .NET Framework.
#if NET6_0_OR_GREATER
// Create the same signed document, but with a timestamp.
await CreateSignedDocument(true);
#endif
return;

static async Task CreateSignedDocument(bool addTimestamp = false)
{
string filename = PdfFileUtility.GetTempPdfFullFileName("samples-PDFsharp/Signatures/DefaultSignerSample" + (addTimestamp ? "Timestamped" : null));

var font = new XFont("Verdana", 10, XFontStyleEx.Regular);
var fontHeader = new XFont("Verdana", 18, XFontStyleEx.Regular);
using var document = new PdfDocument();
var pdfPage = document.AddPage();
var xGraphics = XGraphics.FromPdfPage(pdfPage);
var layoutRectangle = new XRect(0, 72, pdfPage.Width.Point, pdfPage.Height.Point);
xGraphics.DrawString("License Agreement", fontHeader, XBrushes.Black, layoutRectangle, XStringFormats.TopCenter);
var textFormatter = new XTextFormatter(xGraphics);
layoutRectangle = new XRect(72, 144, pdfPage.Width.Point - 144, pdfPage.Height.Point - 144);

var text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam interdum ultrices purus, et congue nulla aliquam quis. Nullam quis finibus velit. Proin sed tellus eu risus accumsan facilisis eu quis felis. Nulla vel nisl non elit elementum tempus. Fusce et tellus feugiat, tempus elit non, vulputate magna. Etiam id vestibulum enim. Sed euismod auctor orci pellentesque dapibus. Fusce nec commodo erat, a posuere sapien. Phasellus sed lobortis orci. In hac habitasse platea dictumst. Sed lacinia lobortis nunc, sed consequat elit eleifend vitae.\r\n\r\n" +
           "Nulla fringilla nunc ipsum, sed consectetur enim scelerisque nec. Maecenas malesuada libero sit amet nulla mattis finibus. Praesent id ipsum non nulla maximus vestibulum eu ac ipsum. Morbi eget augue at odio mollis tempus in id ante. Praesent efficitur lectus eu velit sagittis, quis egestas lorem pellentesque. Cras placerat aliquet tristique. Donec tincidunt orci ornare odio pretium, ac blandit justo ultrices. Vestibulum a posuere orci. Morbi consectetur, dui ut aliquet dapibus, leo nunc bibendum nibh, eu mattis nulla lectus non mauris. Pellentesque in condimentum lorem, nec aliquet nulla. Etiam massa arcu, blandit a tempor sed, porttitor non ligula. Suspendisse tempor vitae risus ac pretium. Aliquam non dictum massa, vel dictum metus. Pellentesque vitae magna nec dolor gravida ultricies et non nunc. Vivamus at velit at dolor luctus convallis sed consequat risus.\r\n\r\n" +
           "Morbi sit amet eros vitae erat dapibus pulvinar. Cras viverra ex at ullamcorper luctus. Aliquam lobortis quis leo eu hendrerit. Praesent imperdiet, ipsum sed porttitor aliquam, augue felis tempor erat, eu pellentesque est nisl eget risus. Fusce nec facilisis orci, non hendrerit ligula. In vitae enim pellentesque sem lacinia varius sit amet eu lectus. Donec finibus nunc metus, consequat viverra libero finibus ac. Sed ut blandit elit, non ullamcorper ex. Aenean placerat, dui sed gravida fringilla, purus nibh venenatis nibh, id feugiat purus sem in lacus. Nullam sit amet justo augue. In ut pharetra neque. Vestibulum ac efficitur enim, nec tristique nibh. Praesent quis velit interdum ante eleifend convallis. Vestibulum eget lorem at ipsum porttitor aliquet. Maecenas malesuada malesuada leo ut feugiat. Vivamus euismod vitae ligula eget lacinia.";
//textFormatter.DrawString(text, font, new XSolidBrush(XColor.FromKnownColor(XKnownColor.Black)), layoutRectangle, XStringFormats.TopLeft);
textFormatter.DrawString(text, font, XBrushes.Black, layoutRectangle, XStringFormats.TopLeft);

await document.SaveAsync(filename);

byte[] documentBytes = File.ReadAllBytes(filename);
using (var documentStream = new MemoryStream())
{
    documentStream.Write(documentBytes, 0, documentBytes.Length);
    documentStream.Position = 0;
    
    for (global::System.Int32 i = 1; i <= 2; i++)
    {
        var tempDoc = PdfReader.Open(documentStream, PdfDocumentOpenMode.Modify);

        var pdfPosition = xGraphics.Transformer.WorldToDefaultPage(new XPoint(144/i, 600/i));
        var options = new DigitalSignatureOptions
        {
            ContactInfo = "John Doe",
            Location = "Seattle",
            Reason = "License Agreement",
            Rectangle = new XRect(pdfPosition.X, pdfPosition.Y, 200, 50),
            AppearanceHandler = new SignatureAppearanceHandler()
        };

        // Specify the URI of a timestamp server if you want a signature with timestamp.
        var pdfSignatureHandler = DigitalSignatureHandler.ForDocument(tempDoc,
            new PdfSharpDefaultSigner(GetCertificate(), PdfMessageDigestType.SHA256, addTimestamp ? new Uri("http://timestamp.apple.com/ts01") : null),
            options);
        documentStream.SetLength(0);
        pdfSignatureHandler.Document.Save(documentStream,false);
        
        documentStream.Position = 0;
    }

    // Save the MemoryStream as a PDF file on the specified path after the loop
    string outputPath = "c:\\data\\output-signed-document.pdf"; // Change this to your desired output path
    using (var fileStream = new FileStream(outputPath, FileMode.Create, FileAccess.Write))
    {
        documentStream.WriteTo(fileStream);
    }
}

PdfFileUtility.ShowDocument("c:\\data\\output-signed-document.pdf");

}
``

static X509Certificate2 GetCertificate()
{
var certFolder = IOUtility.GetAssetsPath("pdfsharp-6.x/signatures") ??
throw new InvalidOperationException("Call Download-Assets.ps1 before running the tests.");
var pfxFile = Path.Combine(certFolder, "test-cert_rsa_1024.pfx");
var rawData = File.ReadAllBytes(pfxFile);

// This code is for demonstration only. Do not use password literals for real certificates in source code.
var certificatePassword = "Seecrit1243";

var certificate = new X509Certificate2(rawData,
    certificatePassword,
    X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);

return certificate;

}

Above is the code that i have to tried out.

We want functionality that append two signature on page 1 with revision 1 for both.

@packdat
Copy link

packdat commented Jan 4, 2025

Could you elaborate on exactly why this is important for your workflow ?
Do you have example documents where this has been accomplished ?
Do you know of any other tool that allows to do this ?

With the current implementation, this is not possible because saving and signing is done in an atomic operation; you cannot separate the two.
And I don't think, this should be changed, because the PDF-spec states that signatures should cover

"...the entire PDF file, including the signature dictionary but excluding the signature value itself "

What you are asking for is to exclude multiple byte-ranges (i.e. signature-values) from the signature-calculation and by that diverging from what the spec recommends.
While not entirely forbidden for all signatures, multiple byte-ranges shall not be used with signature sub-filters ETSI.CAdES.detached or ETSI.RFC3161. (IIRC, this becomes important when talking about PDF/A)

Additionally, when allowing multiple byte-ranges in the way as described, you could potentially create something that would resemble some kind of "Schrödinger's PDF".
Imagine overwriting the signature-value of one of your signatures with random data after saving the document.
That would clearly invalidate the signature but because the signature-value was not covered by the other signature, that other signature would still be valid, resulting in a PDF which is both valid AND invalid (depending on the signature you look at).
(I would expect Acrobat to report such a document as invalid)

Recommended approach:
Accept the current behavior or switch to different tools that better suit your needs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Cannot Reproduce https://xkcd.com/583/
Projects
None yet
Development

No branches or pull requests

3 participants