vbuch / node-signpdf

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

Issue with PFX #232

Closed troyam closed 3 months ago

troyam commented 4 months ago

Describe the bug and the expected behaviour

(node:209944) UnhandledPromiseRejectionWarning: TypeError: sign is not a function
    at assinarPdfComPfx (/root/cert/app.js:66:24)
    at /root/cert/app.js:78:15
    at Object.<anonymous> (/root/cert/app.js:78:79)
    at Module._compile (internal/modules/cjs/loader.js:1114:14)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1143:10)
    at Module.load (internal/modules/cjs/loader.js:979:32)
    at Function.Module._load (internal/modules/cjs/loader.js:819:12)
    at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:75:12)
    at internal/main/run_main_module.js:17:47
(Use `node --trace-warnings ...` to show where the warning was created)
(node:209944) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)
(node:209944) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Is it a bug in signing or in the helpers? Why yhe bug with sign is not function?

To Reproduce

const forge = require('node-forge');
const fs = require('fs');
const { sign } = require('node-signpdf');
const { plainAddPlaceholder } = require('node-signpdf/dist/helpers');
function readPfxFile(filePath, pfxPassword) {
    const pfxFile = fs.readFileSync(filePath);

    const asn1 = forge.asn1.fromDer(pfxFile.toString('binary'));

    const pfx = forge.pkcs12.pkcs12FromAsn1(asn1, pfxPassword);

    const bags = pfx.getBags({bagType: forge.pki.oids.certBag});
    const keyBags = pfx.getBags({bagType: forge.pki.oids.pkcs8ShroudedKeyBag});

    const certificate = bags[forge.pki.oids.certBag][0].cert;
    const privateKey = keyBags[forge.pki.oids.pkcs8ShroudedKeyBag][0].key;

    const certPem = forge.pki.certificateToPem(certificate);
    const privateKeyPem = forge.pki.privateKeyToPem(privateKey);

    console.log('Certificado:');
    console.log('Emitido para: ', certificate.subject.attributes.map(attr => `${attr.name}=${attr.value}`).join(', '));
    console.log('Emitido por: ', certificate.issuer.attributes.map(attr => `${attr.name}=${attr.value}`).join(', '));
    console.log('Válido de: ', certificate.validity.notBefore);
    console.log('Válido até: ', certificate.validity.notAfter);

    return { certPem, privateKeyPem };
  }
function extrairDePfx(caminhoDoPfx, senhaPfx) {
    const pfx = fs.readFileSync(caminhoDoPfx, {encoding: 'binary'});
    const pfxAsn1 = forge.asn1.fromDer(pfx);
    const pfxObj = forge.pkcs12.pkcs12FromAsn1(pfxAsn1, senhaPfx);

    const keyBags = pfxObj.getBags({bagType: forge.pki.oids.pkcs8ShroudedKeyBag});
    const privateKey = forge.pki.privateKeyToPem(keyBags[forge.pki.oids.pkcs8ShroudedKeyBag][0].key);

    const certBags = pfxObj.getBags({bagType: forge.pki.oids.certBag});
    const cert = forge.pki.certificateToPem(certBags[forge.pki.oids.certBag][0].cert);

    return { privateKey, cert };
}
async function assinarPdfComPfx(caminhoDoPdf, caminhoDoPfx, senhaPfx) {
    const { privateKey, cert } = extrairDePfx(caminhoDoPfx, senhaPfx);

    let pdfBuffer = fs.readFileSync(caminhoDoPdf);

    // Adicionar um placeholder de assinatura ao PDF, se necessário
    pdfBuffer = plainAddPlaceholder({
      pdfBuffer,
      reason: 'Eu concordo com o conteúdo deste documento.',
      signatureLength: 8192,
    });

    // Assinar o PDF
    const assinatura = sign(pdfBuffer, privateKey, [cert], {
      passphrase: senhaPfx,
    });

    fs.writeFileSync('./arquivo-assinado.pdf', assinatura);
}
const { certPem, privateKeyPem } = readPfxFile('./certificadoFinal.pfx', '123456');

console.log('Certificado:', certPem);
console.log('Chave Privada:', privateKeyPem);

(async () =>  assinarPdfComPfx('./1.pdf', './certificadoFinal.pfx', '123456'))();
troyam commented 4 months ago

Working with

var fs = require('fs');
var path = require('path');
var PDFDocument = require('pdf-lib').PDFDocument;
var pdflibAddPlaceholder = require('@signpdf/placeholder-pdf-lib').pdflibAddPlaceholder;
var signpdf = require('@signpdf/signpdf').default;
var P12Signer = require('@signpdf/signer-p12').P12Signer;

function work() {
    // contributing.pdf is the file that is going to be signed
    var sourcePath = path.join(__dirname, '1.pdf');
    var pdfBuffer = fs.readFileSync(sourcePath);

    // certificate.p12 is the certificate that is going to be used to sign
    var certificatePath = path.join(__dirname, 'certificadoFinal.pfx');
    var certificateBuffer = fs.readFileSync(certificatePath);
    var signer = new P12Signer(certificateBuffer, { passphrase: "123456" });

    // Load the document into PDF-LIB
    PDFDocument.load(pdfBuffer).then(function (pdfDoc) {
        // Add a placeholder for a signature.
        pdflibAddPlaceholder({
            pdfDoc: pdfDoc,
            reason: 'The user is declaring consent through JavaScript.',
            contactInfo: 'signpdf@example.com',
            name: 'John Doe',
            location: 'Free Text Str., Free World',
        });

        // Get the modified PDFDocument bytes
        pdfDoc.save().then(function (pdfWithPlaceholderBytes) {
            // And finally sign the document.
            signpdf
                .sign(pdfWithPlaceholderBytes, signer)
                .then(function (signedPdf) {
                    // signedPdf is a Buffer of an electronically signed PDF. Store it.
                    var targetPath = path.join(__dirname, 'pdf-lib.pdf');
                    fs.writeFileSync(targetPath, signedPdf);
                })
        })
    })
}

work();`
troyam commented 4 months ago

but with can't install : @signpdf/placeholder-pdfkit010 using v21 node.js

var fs = require('fs');
var path = require('path');
var PDFDocument = require('pdfkit');
var signpdf = require('@signpdf/signpdf').default;
var P12Signer = require('@signpdf/signer-p12').P12Signer;
var pdfkitAddPlaceholder = require('@signpdf/placeholder-pdfkit010').pdfkitAddPlaceholder;

/**
 * Transform coordinates from top/left to bottom/left coordinate system
 */
function topLeftToBottomLeft(coords, page) {
    return [
        coords[0], // x1
        page.height - coords[1], // y1
        coords[2], // x2
        page.height - coords[3], // y2
    ];
}

function addVisual(pdf) {
    // Go to first page
    pdf.switchToPage(0);

    var margin = 30;
    var padding = 10;
    var label = 'Signed with @signpdf';
    pdf
        .fillColor('#008B93')
        .fontSize(10);
    var text = {
        width: pdf.widthOfString(label),
        height: pdf.heightOfString(label)
    };
    text.x = pdf.page.width - text.width - margin;
    text.y = pdf.page.height - text.height - margin;

    pdf.text(label, text.x, text.y, {width: text.width, height: text.height});

    return [
        text.x - padding,
        text.y - padding,
        text.x + text.width + padding,
        text.y + text.height + padding,
    ];
}

function work() {
    // Start a PDFKit document
    var pdf = new PDFDocument({
        autoFirstPage: false,
        size: 'A4',
        layout: 'portrait',
        bufferPages: true,
    });
    pdf.info.CreationDate = '';

    // At the end we want to convert the PDFKit to a string/Buffer and store it in a file.
    // Here is how this is going to happen:
    var pdfReady = new Promise(function (resolve) {
        // Collect the ouput PDF
        // and, when done, resolve with it stored in a Buffer
        var pdfChunks = [];
        pdf.on('data', function (data) {
            pdfChunks.push(data);
        });
        pdf.on('end', function () {
            resolve(Buffer.concat(pdfChunks));
        });
    });

    // Add some content to the page(s)
    pdf
        .addPage()
        .fillColor('#333')
        .fontSize(25)
        .moveDown()
        .text('@signpdf');

    // !!! ADDING VISUALS AND APPLYING TO SIGNATURE WIDGET ==>

    // Add a some visuals and make sure to get their dimensions.
    var visualRect = addVisual(pdf);
    // Convert these dimension as Widgets' (0,0) is bottom-left based while the
    // rest of the coordinates on the page are top-left.
    var widgetRect = topLeftToBottomLeft(visualRect, pdf.page);

    // Here comes the signing. We need to add the placeholder so that we can later sign.
    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
    });

    // <== !!! ADDING VISUALS AND APPLYING TO SIGNATURE WIDGET

    // `refs` here contains PDFReference objects to signature, form and widget.
    // PDFKit doesn't know much about them, so it won't .end() them. We need to do that for it.
    Object.keys(refs).forEach(function (key) {
        refs[key].end()
    });

    // Once we .end the PDFDocument, the `pdfReady` Promise will resolve with
    // the Buffer of a PDF that has a placeholder for signature that we need.
    // Other that we will also need a certificate
    // certificate.p12 is the certificate that is going to be used to sign

    var certificatePath = path.join(__dirname, 'certificadoFinal.pfx');
    var certificateBuffer = fs.readFileSync(certificatePath);
    var signer = new P12Signer(certificateBuffer, { passphrase: "123456" });
    // Once the PDF is ready we need to sign it and eventually store it on disc.
    pdfReady
        .then(function (pdfWithPlaceholder) {
            return signpdf.sign(pdfWithPlaceholder, signer);
        })
        .then(function (signedPdf) {
            var targetPath = path.join(__dirname, 'pdfkit010-with-visual.pdf');
            fs.writeFileSync(targetPath, signedPdf);
        });

    // Finally end the PDFDocument stream.
    pdf.end();
    // This has just triggered the `pdfReady` Promise to be resolved.
}

work();