vbuch / node-signpdf

Simple signing of PDFs in node.
MIT License
676 stars 174 forks source link

How can fields of placeholder be visible. #250

Open pranay18997 opened 2 months ago

pranay18997 commented 2 months ago

Describe the bug and the expected behaviour I am working on the digital signature, and I want it should look like this. But I am not getting any option in pdflib. Please help here. image

Is it a bug in signing or in the helpers? Its a doubt

To Reproduce Help us reproduce. Include any relevant code. Point to where you are having issues with links. Give us a failing test. In all cases make sure to include at least a source PDF that you are trying and failing to sign. To let us easily take a look, it would help if you also include a resulting document (thou probably corrupted one).

vbuch commented 2 months ago

Hi, Have a look at the "visual" tag if you want. And we have this example that adds a visual: https://github.com/vbuch/node-signpdf/blob/develop/packages/examples/src/pdfkit010-with-visual.js

pranay18997 commented 2 months ago

@vbuch thanks for the quick response, but what is the significance of this parameter reason,location?

var refs = pdfkitAddPlaceholder({
    pdf: pdf,
    pdfBuffer: Buffer.from([pdf]), // FIXME: This shouldn't be needed.
    reason: 'Showing off.',
    contactInfo: 'signpdf@example.com',
    name: 'Sign PDF',
    location: 'The digital world.',
    signatureLength: 1612,
    widgetRect: widgetRect, // <== !!! This is where we tell the widget to be visible
});
vbuch commented 2 months ago

@pranay18997 https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.7old.pdf

image

ngvhob commented 2 months ago

@pranay18997 Were you able to get the green tick in Adobe Acrobat after signing the PDF ? Cause I tried using visual tag implementation but I still didn't get the green tick even though the certificate is showing as valid in Acrobat.

pranay18997 commented 2 months ago

@ngvhob no, @vbuch could you please help with https://github.com/vbuch/node-signpdf/issues/250#issuecomment-2069151089 query?

vbuch commented 2 months ago

@pranay18997, @ngvhob https://stackoverflow.com/a/40391641/2528232

pranay18997 commented 2 months ago

@vbuch is there any way we can read signature values from the certificate?

ngvhob commented 2 months ago

@vbuch got it, but is there a way to have every page of the PDF digitally signed? In my task, I need the ability to sign all pages, and I have a demo document too. I will attach a screenshot to provide my problem statement. Cause atm I get error if I try to place multiple place holders in the pdf doc. Is this possible ??

Screenshot (1)

pranay18997 commented 2 months ago

@vbuch I was trying to add digital signature in last page of pdf doc. Please find the code below.

        pdflibAddPlaceholder({
            pdfDoc: pdfDoc,
            pdfPage:pages[2],
            reason: 'Digitally Signed by xxx',
            contactInfo: 'xxx@gmail.com',
            name: 'xxxxxx',
            signatureLength: 13786,
            location: 'Signature Valid',
            widgetRect: widgetRect,
            signingTime: new Date(),
        });

Issue:- Widget is not visible in last page. But if I am not passing 'pdfPage' parameter then widget is visible in first page. Please help here on prioirty.

YO-SC commented 1 month ago

Hello y'all, if I understand correctly @pranay18997 and @ngvhob sign widgets seem to be displaying the correct information of their respective pdflibAddPlaceholder() usage. Its kinda hard to tell but I think so (if I am wrong feel free to correct me). That said, I am having some issues with similar implementations.

I don't know if this is the normal behavior of the pdflibAddPlaceholder() , but the widgets on my end don't seem to be displaying any information: Screenshot 2024-05-09 190532 Screenshot taken from Adobe Acrobat Reader

For context, here is the relevant code:

import { pdflibAddPlaceholder } from '@signpdf/placeholder-pdf-lib'
import { P12Signer } from '@signpdf/signer-p12'
import signpdf from '@signpdf/signpdf'
import { PDFDocument, PDFPage } from 'pdf-lib'

//...

  const p12Signer = new P12Signer(p12Buffer, {
      passphrase: process?.env?.CERT_PASSWORD,
    })
  const widgetRect = [50, 70, 200, 200]

// Add a placeholder for a signature.
    pdflibAddPlaceholder({
      appName: 'Some App',
      pdfDoc: pdfDoc,
      reason: 'The user is declaring consent through JavaScript.',
      contactInfo: 'signpdf@example.com', 
      name: 'John Doe',
      location: 'Some place on Earth',
      signingTime: new Date(), 
      widgetRect: widgetRect, // ! <== This is where we tell the widget to be visible
    })

// Get the modified PDFDocument bytes
  const pdfSaveBytes = await pdfDoc.save({ updateFieldAppearances: true })
  const pdfWithPlaceholderBuffer = Buffer.from(pdfSaveBytes)

  // And finally sign the document.
  const signedPdfBuffer = await signpdf.sign(
    pdfWithPlaceholderBuffer,
    p12Signer
  )

  const pdfBlob = new Blob([signedPdfBuffer])

// Save to disk
  await Bun.write(`${process.cwd()}/public/pdf/some-pdf-signed.pdf`, pdfBlob)

As you can see here, I am using @signpdf + pdf-lib. The thing is, I have followed the most up to date methods and read through all the relevant issues and documentation; but I can't seem to be able to display the placeholder information on the "sign box".

I really appreciate any leads to a solution and I am thankful for your time (anybody reading this through). Thanks.

(Also, very nice library/package. Any ways to support/donate? @vbuch)

vbuch commented 1 month ago

hi @YO-SC , The visual part of the widget is up to you. You need to find a way to draw it on the page. The signature widget is just a rectangle that should be interactive if the viewer has support for that. The signature information is supposed to be shown by the Reader itself and not embedded visually in the document.

1st thing would to be to look around the "visual" tag. I think all of these ask the same question - "How do I make the signature visually appealing?". 2nd: I believe you are looking for something like this https://github.com/vbuch/node-signpdf/blob/develop/packages/examples/src/pdfkit010-with-visual.js This is a pdfkit 0.10.0 implementation of the same and you can see at https://github.com/vbuch/node-signpdf/blob/develop/packages/examples/src/pdfkit010-with-visual.js#L82 that it actually draws the content before placing a signature widget on top of it. Not sure if someone ever created an example with pdf-lib so this above the closest I can get you to one. On the support/donate question: There must be a "Sponsor" link on the repo that takes you to buymeacoffee where you can do that. Thanks if you do!

YO-SC commented 1 month ago

Thanks for your timely response @vbuch, I will check out the examples provided. Thanks 👍🏼

jamesjhonatan123 commented 1 month ago

Hello, how are you? First of all, congratulations on the initiative. I am facing issues with multiple signatures because when I add a visible placeholder and then sign, everything works perfectly. However, any other signature with a new visual placeholder invalidates the previous ones (in terms of integrity).

I used pdf-lib to add the visible placeholder.

vbuch commented 1 month ago

@jamesjhonatan123 pdf-lib does not support incremental updates (at least to my knowledge) and those are needed for multiple signatures.

jamesjhonatan123 commented 1 month ago

@vbuch Is there any library or possibility to do this without using pdfKit or pdf-lib? Unfortunately, I can't load an already signed PDF with pdfKit, as it does not allow editing.

vbuch commented 1 month ago

@jamesjhonatan123 the signing part you can do with placeholder-plain. The visual part - no clue

jamesjhonatan123 commented 1 month ago

I did it. I used a fork of HummusJS - Muhammara (maintained) - for incremental writing. Now I can sign as many times as necessary by adding the visible placeholder. Thank you.

Here's the example for those who had the same problem:

import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { SignPdf } from '@signpdf/signpdf';
import { P12Signer } from '@signpdf/signer-p12';
import { plainAddPlaceholder } from '@signpdf/placeholder-plain';
import { Recipe } from 'muhammara';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

async function addPlaceholder(pdfBuffer, options) {
    return plainAddPlaceholder({
        pdfBuffer,
        reason: options.reason,
        contactInfo: options.contactInfo,
        name: options.name,
        location: options.location,
        widgetRect: options.widgetRect,
    });
}

async function signPdf(pdfBuffer, certificatePath, passphrase, targetPath) {
    const certificateBuffer = fs.readFileSync(path.join(__dirname, certificatePath));
    const signer = new P12Signer(certificateBuffer, { passphrase });
    const signPdf = new SignPdf();

    const signedPdf = await signPdf.sign(pdfBuffer, signer);
    fs.writeFileSync(targetPath, signedPdf);
    return signedPdf;
}

function drawTextOnWidget(pdfPath, outputPath, text, widgetRect) {
    const pdfDoc = new Recipe(pdfPath, outputPath);

    pdfDoc
        .editPage(1)
        .text(text, widgetRect.x, widgetRect.y)
        .endPage()
        .endPDF();
}

async function work() {
    const pdfPath = path.join(__dirname, 'multiple-signatures-buyer-seller-3.pdf');
    const pdfBuffer = fs.readFileSync(pdfPath);

    const pdfWithBuyerPlaceholder = await addPlaceholder(pdfBuffer, {
        reason: 'Agrees to buy the truck trailer.',
        contactInfo: 'john@example.com',
        name: 'John Doe',
        location: 'Free Text Str., Free World',
        widgetRect: [200, 200, 300, 300],
    });

    const buyerSignedPdfPath = path.join(__dirname, './multiple-signatures-buyer-seller-1.pdf');
    const buyerSignedPdf = await signPdf(
        pdfWithBuyerPlaceholder,
        '1/1.p12',
        '123456',
        buyerSignedPdfPath
    );

    drawTextOnWidget(buyerSignedPdfPath, buyerSignedPdfPath, 'Buyer Signature', { x: 250, y: 250 });

    const buyerSignedPdfBuffer = fs.readFileSync(buyerSignedPdfPath);
    const pdfWithSellerPlaceholder = await addPlaceholder(buyerSignedPdfBuffer, {
        reason: 'Agrees to sell a truck trailer to John Doe.',
        contactInfo: 'dealer@example.com',
        name: 'Thug Dealer',
        location: 'Automotive Str., Free World',
        widgetRect: [400, 400, 500, 500],
    });

    const sellerSignedPdfPath = path.join(__dirname, './multiple-signatures-buyer-seller-2.pdf');
    const finalSignedPdf = await signPdf(
        pdfWithSellerPlaceholder,
        '2/2.p12',
        '654321',
        sellerSignedPdfPath
    );

    drawTextOnWidget(sellerSignedPdfPath, path.join(__dirname, './multiple-signatures-buyer-seller-3.pdf'), 'Seller Signature', { x: 450, y: 450 });

    console.log('PDF signed by both buyer and seller with text drawn on widgetRect.');
}

work();
vbuch commented 1 month ago

@jamesjhonatan123 want to make it a package under packages/examples? Or at least start a PR that I can when I have the time help with/wrap up for you

jamesjhonatan123 commented 1 month ago

Sure, I'll do it.

vinhnhq commented 2 weeks ago

I did it. I used a fork of HummusJS - Muhammara (maintained) - for incremental writing. Now I can sign as many times as necessary by adding the visible placeholder. Thank you.

Here's the example for those who had the same problem:

import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { SignPdf } from '@signpdf/signpdf';
import { P12Signer } from '@signpdf/signer-p12';
import { plainAddPlaceholder } from '@signpdf/placeholder-plain';
import { Recipe } from 'muhammara';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

async function addPlaceholder(pdfBuffer, options) {
    return plainAddPlaceholder({
        pdfBuffer,
        reason: options.reason,
        contactInfo: options.contactInfo,
        name: options.name,
        location: options.location,
        widgetRect: options.widgetRect,
    });
}

async function signPdf(pdfBuffer, certificatePath, passphrase, targetPath) {
    const certificateBuffer = fs.readFileSync(path.join(__dirname, certificatePath));
    const signer = new P12Signer(certificateBuffer, { passphrase });
    const signPdf = new SignPdf();

    const signedPdf = await signPdf.sign(pdfBuffer, signer);
    fs.writeFileSync(targetPath, signedPdf);
    return signedPdf;
}

function drawTextOnWidget(pdfPath, outputPath, text, widgetRect) {
    const pdfDoc = new Recipe(pdfPath, outputPath);

    pdfDoc
        .editPage(1)
        .text(text, widgetRect.x, widgetRect.y)
        .endPage()
        .endPDF();
}

async function work() {
    const pdfPath = path.join(__dirname, 'multiple-signatures-buyer-seller-3.pdf');
    const pdfBuffer = fs.readFileSync(pdfPath);

    const pdfWithBuyerPlaceholder = await addPlaceholder(pdfBuffer, {
        reason: 'Agrees to buy the truck trailer.',
        contactInfo: 'john@example.com',
        name: 'John Doe',
        location: 'Free Text Str., Free World',
        widgetRect: [200, 200, 300, 300],
    });

    const buyerSignedPdfPath = path.join(__dirname, './multiple-signatures-buyer-seller-1.pdf');
    const buyerSignedPdf = await signPdf(
        pdfWithBuyerPlaceholder,
        '1/1.p12',
        '123456',
        buyerSignedPdfPath
    );

    drawTextOnWidget(buyerSignedPdfPath, buyerSignedPdfPath, 'Buyer Signature', { x: 250, y: 250 });

    const buyerSignedPdfBuffer = fs.readFileSync(buyerSignedPdfPath);
    const pdfWithSellerPlaceholder = await addPlaceholder(buyerSignedPdfBuffer, {
        reason: 'Agrees to sell a truck trailer to John Doe.',
        contactInfo: 'dealer@example.com',
        name: 'Thug Dealer',
        location: 'Automotive Str., Free World',
        widgetRect: [400, 400, 500, 500],
    });

    const sellerSignedPdfPath = path.join(__dirname, './multiple-signatures-buyer-seller-2.pdf');
    const finalSignedPdf = await signPdf(
        pdfWithSellerPlaceholder,
        '2/2.p12',
        '654321',
        sellerSignedPdfPath
    );

    drawTextOnWidget(sellerSignedPdfPath, path.join(__dirname, './multiple-signatures-buyer-seller-3.pdf'), 'Seller Signature', { x: 450, y: 450 });

    console.log('PDF signed by both buyer and seller with text drawn on widgetRect.');
}

work();

hey @jamesjhonatan123 thanks for your sample, but when executing that script on mac m1 it throws me this error, did you get it before? thanks for any comments

image