node-signpdf icon indicating copy to clipboard operation
node-signpdf copied to clipboard

How can fields of placeholder be visible.

Open pranay18997 opened this issue 1 year ago • 20 comments

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).

pranay18997 avatar Apr 17 '24 11:04 pranay18997

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

vbuch avatar Apr 17 '24 12:04 vbuch

@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: '[email protected]',
    name: 'Sign PDF',
    location: 'The digital world.',
    signatureLength: 1612,
    widgetRect: widgetRect, // <== !!! This is where we tell the widget to be visible
});

pranay18997 avatar Apr 17 '24 12:04 pranay18997

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

image

vbuch avatar Apr 17 '24 12:04 vbuch

@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.

ngvhob avatar Apr 22 '24 11:04 ngvhob

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

pranay18997 avatar Apr 22 '24 21:04 pranay18997

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

vbuch avatar Apr 22 '24 22:04 vbuch

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

pranay18997 avatar Apr 24 '24 10:04 pranay18997

@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)

ngvhob avatar Apr 24 '24 10:04 ngvhob

@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: '[email protected]',
            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.

pranay18997 avatar Apr 25 '24 10:04 pranay18997

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: '[email protected]', 
      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)

YO-SC avatar May 09 '24 23:05 YO-SC

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!

vbuch avatar May 10 '24 14:05 vbuch

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

YO-SC avatar May 10 '24 14:05 YO-SC

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.

jamesjhonatan123 avatar May 27 '24 13:05 jamesjhonatan123

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

vbuch avatar May 27 '24 15:05 vbuch

@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.

jamesjhonatan123 avatar May 27 '24 15:05 jamesjhonatan123

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

vbuch avatar May 27 '24 15:05 vbuch

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: '[email protected]',
        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: '[email protected]',
        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();

jamesjhonatan123 avatar May 27 '24 20:05 jamesjhonatan123

@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

vbuch avatar May 28 '24 18:05 vbuch

Sure, I'll do it.

jamesjhonatan123 avatar May 28 '24 23:05 jamesjhonatan123

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: '[email protected]',
        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: '[email protected]',
        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

vinhnhq avatar Jun 19 '24 09:06 vinhnhq