Hopding / pdf-lib

Create and modify PDF documents in any JavaScript environment
https://pdf-lib.js.org
MIT License
6.6k stars 635 forks source link

Embed digital signature(pkcs7) to pdf #1643

Open ParitoshKarnatak opened 1 month ago

ParitoshKarnatak commented 1 month ago

What were you trying to do?

I am implementing signing of a PDF. I have pkcs7 value and I want to embed it in the PDF.

I am using pdf-lib and node-signpdf

The PDF file is getting signed, but when verifying using pdfsign, I am getting this error:

pdfsig signed.pdf Digital Signature Info of: signed.pdf Syntax Error (0): Illegal values in ByteRange array Signature #1:

How did you attempt to do it?

const fs = require('fs'); const { PDFDocument, PDFName, PDFHexString, PDFArray, PDFDict, PDFNumber } = require('pdf-lib'); const { plainAddPlaceholder } = require('node-signpdf/dist/helpers');

async function embedSignatureToPdf(pdfPath, outputPdfPath, base64Signature) { try { // Read the PDF const pdfBuffer = fs.readFileSync(pdfPath);

    // Add a placeholder for the signature
    const pdfWithPlaceholder = plainAddPlaceholder({
        pdfBuffer,
        reason: 'I am the author',
        contactInfo: 'contact@example.com',
        name: 'Author',
        location: 'Location',
    });

    // Debugging: Log the length of the PDF with placeholder
    console.log('pdfWithPlaceholder length:', pdfWithPlaceholder.length);

    // Convert Base64 signature to Buffer
    const pkcs7Buffer = Buffer.from(base64Signature, 'base64');

    // Find the ByteRange placeholder position
    const byteRangePlaceholder = '**********';
    const byteRangePos = pdfWithPlaceholder.indexOf(byteRangePlaceholder);
    if (byteRangePos === -1) {
        throw new Error('Could not find ByteRange placeholder in the PDF');
    }

    // Calculate the lengths of the first and second ranges
    const length1 = byteRangePos;
    const length2 = pdfWithPlaceholder.length - (length1 + byteRangePlaceholder.length);

    // Calculate ByteRange values
    const byteRange = [
        0, // start1
        length1, // length1
        length1 + byteRangePlaceholder.length, // start2
        length2, // length2
    ];

    // Ensure the ByteRange values are correct
    console.log('ByteRange:', byteRange);
    const totalSignedLength = length1 + length2;
    console.log('totalSignedLength:', totalSignedLength);
    if (totalSignedLength !== pdfWithPlaceholder.length - byteRangePlaceholder.length) {
        throw new Error('ByteRange values do not sum up to the total document length minus the placeholder length');
    }

    // Format ByteRange values as a string
    const byteRangeStr = byteRange.map(val => `0000000000${val}`.slice(-10)).join(' ');

    // Debugging: Log ByteRange string
    console.log('ByteRangeStr:', byteRangeStr);

    // Replace ByteRange placeholder with actual values
    let pdfWithByteRange = Buffer.concat([
        pdfWithPlaceholder.slice(0, byteRangePos),
        Buffer.from(byteRangeStr),
        pdfWithPlaceholder.slice(byteRangePos + byteRangePlaceholder.length),
    ]);

    // Load the PDF document with ByteRange replaced
    const signedPdfDoc = await PDFDocument.load(pdfWithByteRange);

    // Get the AcroForm dictionary
    const acroForm = signedPdfDoc.context.lookup(signedPdfDoc.catalog.get(PDFName.of('AcroForm')));
    if (!acroForm) {
        throw new Error('Could not find AcroForm in the PDF');
    }

    // Get the signature field
    const fields = acroForm.lookup(PDFName.of('Fields'), PDFArray);
    const signatureField = fields.lookup(0, PDFDict);
    const signatureDict = signatureField.lookup(PDFName.of('V'), PDFDict);

    // Set the /ByteRange entry with the calculated ByteRange
    const byteRangeArray = PDFArray.withContext(signedPdfDoc.context);
    byteRange.forEach(val => byteRangeArray.push(PDFNumber.of(val)));
    signatureDict.set(PDFName.of('ByteRange'), byteRangeArray);

    // Set the /Contents entry with the PKCS#7 signature
    const hexSignature = PDFHexString.of(pkcs7Buffer.toString('hex'));

    // Ensure the contents fit in the allocated space
    const placeholderLengthBytes = byteRangePlaceholder.length * 2; // Each byte is represented by 2 hex digits
    const paddedSignature = hexSignature.value.padEnd(placeholderLengthBytes, '0');

    signatureDict.set(PDFName.of('Contents'), PDFHexString.of(paddedSignature));

    // Save the signed PDF
    const signedPdfBytes = await signedPdfDoc.save();
    fs.writeFileSync(outputPdfPath, signedPdfBytes);

    console.log('PDF signed successfully!');
} catch (error) {
    console.error('Error signing PDF:', error);
}

}

What actually happened?

The PDF file is getting signed, but when verifying using pdfsign, I am getting this error:

pdfsig signed.pdf Digital Signature Info of: signed.pdf Syntax Error (0): Illegal values in ByteRange array Signature #1:

What did you expect to happen?

The PDF should be signed with the appropriate digital signature

How can we reproduce the issue?

By running the code along with appropriate pkcs7 certificate value

Version

1.17.1

What environment are you running pdf-lib in?

Node

Checklist

Additional Notes

No response