A javascript tool to sign a pdf in web browser, google apps script and nodejs.
 
 
 
 
Go to file
zboris12 9210c41068 Supported to enable LTV, and made to publish on npm. 2022-11-19 19:52:06 +09:00
closure Working on LTV, Completeness is 90%. 2022-11-14 22:20:19 +09:00
lib Working on LTV, Completeness is 90%. 2022-11-14 22:20:19 +09:00
.env.sample Working on LTV, Completeness is 90%. 2022-11-14 22:20:19 +09:00
.gitignore The first commit. 2022-09-17 21:55:09 +09:00
LICENSE Initial commit 2022-09-17 21:48:13 +09:00
README.md Supported to enable LTV, and made to publish on npm. 2022-11-19 19:52:06 +09:00
closure.js Working on LTV, Completeness is 90%. 2022-11-14 22:20:19 +09:00
logo.png Working on LTV, Completeness is 90%. 2022-11-14 22:20:19 +09:00
package-lock.json Added support run in node js. 2022-10-22 18:42:13 +09:00
package.json Supported to enable LTV, and made to publish on npm. 2022-11-19 19:52:06 +09:00
test.html Added support to DocMDP and improved the way of appending TSA timestamp. 2022-10-14 21:12:43 +09:00
test4node.js Working on LTV, Completeness is 90%. 2022-11-14 22:20:19 +09:00

README.md

ZgaPdfSigner

A javascript tool to sign a pdf or set protection of a pdf in web browser.
And it is more powerful when used in Google Apps Script or nodejs.

PS: ZGA is the abbreviation of my father's name.
And I use this name to hope the merits from this application will be dedicated to my parents.

Main features

  • Sign a pdf with an invisible pkcs#7 signature.
  • Sign a pdf with a visible pkcs#7 signature by drawing an image.
  • Sign a pdf and set DocMDP.
  • Add a new signature to a pdf if it has been signed already. (An incremental update)
  • Add a document timestamp from TSA. (Not available in web browser)
  • Sign a pdf with a timestamp from TSA. ((Not available in web browser)
  • Enable signature's LTV. (Not available in web browser)
  • Set password protection to a pdf. Supported algorithms:
    • 40bit RC4 Encryption
    • 128bit RC4 Encryption
    • 128bit AES Encryption
    • 256bit AES Encryption
  • Set public-key certificate protection to a pdf. Supported algorithms are as same as the password protection.

About signing with TSA and LTV

Because of the CORS security restrictions in web browser, signing with a timestamp from TSA or enabling LTV can only be used in Google Apps Script or nodejs.

The Dependencies

How to use this tool

Web Browser

Just import the dependencies and this tool.

<script src="https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js" type="text/javascript"></script>
<script src="https://unpkg.com/node-forge@1.3.1/dist/forge.min.js" type="text/javascript"></script>
<script src="https://github.com/zboris12/zgapdfsigner/releases/download/2.5.0/zgapdfsigner.min.js" type="text/javascript"></script>

Google Apps Script

Load the dependencies and this tool.

// Simulate setTimeout function for pdf-lib
function setTimeout(func, sleep){
  Utilities.sleep(sleep);
  func();
}
// Simulate window for node-forge
var window = globalThis;
// Load pdf-lib
eval(UrlFetchApp.fetch("https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js").getContentText());
// Load node-forge
eval(UrlFetchApp.fetch("https://unpkg.com/node-forge@1.3.1/dist/forge.min.js").getContentText());
// Load ZgaPdfSigner
eval(UrlFetchApp.fetch("https://github.com/zboris12/zgapdfsigner/releases/download/2.5.0/zgapdfsigner.min.js").getContentText());

Or simply import the library of ZgaPdfToolkit

  1. Add the library of ZgaPdfToolkit to your project, and suppose the id of library you defined is "pdfkit".
    Script id: 1T0UPf50gGp2fJ4dR1rZfEFgKYC5VpCwUVooCRNySiL7klvIUVsFBCZ9m
  2. Load the library.
pdfkit.loadZga(globalThis);

nodejs

  1. Install
npm install zgapdfsigner
  1. Import
const Zga = require("zgapdfsigner");

Let's sign

Sign with an invisible signature.

/**
 * @param {ArrayBuffer} pdf
 * @param {ArrayBuffer} cert
 * @param {string} pwd
 * @return {Promise<Blob>}
 */
async function sign1(pdf, cert, pwd){
  /** @type {SignOption} */
  var sopt = {
    p12cert: cert,
    pwd: pwd,
    permission: 1,
  };
  var signer = new Zga.PdfSigner(sopt);
  var u8arr = await signer.sign(pdf);
  return new Blob([u8arr], {"type" : "application/pdf"});
}

Sign with a visible signature of an image.

/**
 * @param {ArrayBuffer} pdf
 * @param {ArrayBuffer} cert
 * @param {string} pwd
 * @param {ArrayBuffer} imgdat
 * @param {string} imgtyp
 * @return {Promise<Blob>}
 */
async function sign2(pdf, cert, pwd, imgdat, imgtyp){
  /** @type {SignOption} */
  var sopt = {
    p12cert: cert,
    pwd: pwd,
    drawinf: {
      area: {
        x: 25,  // left
        y: 150, // top
        w: 60,  // width
        h: 60,  // height
      },
      imgData: imgdat,
      imgType: imgtyp,
    },
  };
  var signer = new Zga.PdfSigner(sopt);
  var u8arr = await signer.sign(pdf);
  return new Blob([u8arr], {"type" : "application/pdf"});
}

Sign with a visible signature of drawing a text.

//TODO

Use it in Google Apps Script

/**
 * @param {string} pwd Passphrase of certificate
 * @return {Promise}
 */
async function createPdf(pwd){
  // Load pdf, certificate
  var pdfBlob = DriveApp.getFilesByName("_test.pdf").next().getBlob();
  var certBlob = DriveApp.getFilesByName("_test.pfx").next().getBlob();
  // Sign the pdf
  /** @type {SignOption} */
  var sopt = {
    p12cert: certBlob.getBytes(),
    pwd,
    signdate: "1",
    ltv: 1,
  };
  var signer = new Zga.PdfSigner(sopt);
  var u8arr = await signer.sign(pdfBlob.getBytes());
  // Save the result pdf to some folder
  var fld = DriveApp.getFolderById("a folder's id");
  fld.createFile(Utilities.newBlob(u8arr, "application/pdf").setName("signed_test.pdf"));
}

Use queryPassword function in ZgaPdfToolkit.

function myfunction(){
  var spd = SpreadsheetApp.getActiveSpreadsheet();
  pdfkit.queryPassword("createPdf", "Please input the passphrase", spd.getName());
}

Use it in nodejs

const m_fs = require("fs");
const m_path = require("path");
async function main(){
  /** @type {string} */
  var pdfPath = m_path.join(__dirname, "_test.pdf");
  /** @type {string} */
  var pfxPath = m_path.join(__dirname, "_test.pfx");
  /** @type {string} */
  var ps = "";
  /** @type {string} */
  var imgPath = m_path.join(__dirname, "_test.png");

  if(process.argv.length > 3){
    pfxPath = process.argv[2];
    ps = process.argv[3];
  }else if(process.argv[2]){
    ps = process.argv[2];
  }

  if(!ps){
    // throw new Error("The passphrase is not specified.");
    pfxPath = "";
  }

  /** @type {Buffer} */
  var pdf = m_fs.readFileSync(pdfPath);
  /** @type {Buffer} */
  var pfx = null;
  if(pfxPath){
    pfx = m_fs.readFileSync(pfxPath);
  }
  /** @type {Buffer} */
  var img = null;
  /** @type {string} */
  var imgType = "";
  if(imgPath){
    img = m_fs.readFileSync(imgPath);
    imgType = m_path.extname(imgPath).slice(1);
  }

  /** @type {SignOption} */
  var sopt = {
    p12cert: pfx,
    pwd: ps,
    permission: pfx ? 2 : 0,
    signdate: "1",
    reason: "I have a test reason.",
    location: "I am on the earth.",
    contact: "zga@zga.com",
    ltv: 1,
    debug: true,
  };
  if(img){
    sopt.drawinf = {
      area: {
        x: 25, // left
        y: 150, // top
        w: 60,
        h: 60,
      },
      imgData: img,
      imgType: imgType,
    };
  }

  /** @type {Zga.PdfSigner} */
  var ser = new Zga.PdfSigner(sopt);
  /** @type {Uint8Array} */
  var u8dat = await ser.sign(pdf);

  if(u8dat){
    /** @type {string} */
    var outPath = m_path.join(__dirname, "test_signed.pdf");
    m_fs.writeFileSync(outPath, u8dat);
    console.log("Output file: " + outPath);
  }

  console.log("Done");
}

Detail of SignOption

  • p12cert: Array|Uint8Array|ArrayBuffer|string 👉 (Optional) Certificate's data. In the case of adding a document timestamp, it must be omitted.
  • pwd: string 👉 (Optional) The passphrase of the certificate. In the case of adding a document timestamp, it must be omitted.
  • permission: number 👉 (Optional) The modification permissions granted for this document. This is a setting of DocMDP. Valid values are:
    • 1: No changes to the document are permitted; any change to the document invalidates the signature.
    • 2: Permitted changes are filling in forms, instantiating page templates, and signing; other changes invalidate the signature.
    • 3: Permitted changes are the same as for 2, as well as annotation creation, deletion, and modification; other changes invalidate the signature.
  • reason: string 👉 (Optional) The reason for signing
  • location: string 👉 (Optional) Your location
  • contact: string 👉 (Optional) Your contact information
  • signdate: Date|string|TsaServiceInfo 👉 (Optional) In the case of adding a document timestamp, it can't be omitted and can't be a Date.
  • signame: string 👉 (Optional) The name of the signature
  • ltv: number 👉 (Optional) Type of LTV. Valid values are:
    • 1: auto; Try using OCSP only to enable the LTV first; If can't, try using CRL to enable the LTV.
    • 2: crl only; Only try using CRL to enable the LTV.
  • drawinf: SignDrawInfo 👉 (Optional) Visible signature's information
    • area: SignAreaInfo 👉 The signature's drawing area, these numbers are dots on 72dpi.
      • x: number 👉 Distance from left
      • y: number 👉 Distance from top
      • w: number 👉 Width
      • h: number 👉 Height
    • pageidx: number 👉 (Optional) The index of a page where the signature will be drawn.
    • imgData: Array|Uint8Array|ArrayBuffer|string 👉 (Optional) The image's data
    • imgType: string 👉 (Optional) The image's type, only support jpg and png
    • text: string 👉 (Optional) A text drawing for the signature, not implemented yet
    • fontData: PDFLib.StandardFonts|Array|Uint8Array|ArrayBuffer|string 👉 (Optional) The font's data for drawing text, not implemented yet

Let's protect the pdf

Set password protection to the pdf.

/**
 * @param {ArrayBuffer} pdf
 * @param {string} upwd
 * @param {string} opwd
 * @return {Promise<Blob>}
 */
async function protect1(pdf, upwd, opwd){
  /** @type {EncryptOption} */
  var eopt = {
    mode: Zga.Crypto.Mode.RC4_40,
    permissions: ["modify", "annot-forms", "fill-forms", "extract", "assemble"],
    userpwd: upwd,
    ownerpwd: opwd,
  };
  var cyptor = new Zga.PdfCryptor(eopt);
  var pdfdoc = await cyptor.encryptPdf(pdf);
  u8arr = await pdfdoc.save({"useObjectStreams": false});
  return new Blob([u8arr], {"type" : "application/pdf"});
}

Set public-key certificate protection to the pdf.

/**
 * @param {ArrayBuffer} pdf
 * @param {ArrayBuffer} cert
 * @return {Promise<Blob>}
 */
async function protect2(pdf, cert){
  /** @type {EncryptOption} */
  var eopt = {
    mode: Zga.Crypto.Mode.AES_128,
    pubkeys: [{
      c: cert,
      p: ["copy", "modify", "copy-extract", "annot-forms", "fill-forms", "extract", "assemble"],
    }],
  };
  var cyptor = new Zga.PdfCryptor(eopt);
  var pdfdoc = await cyptor.encryptPdf(pdf);
  u8arr = await pdfdoc.save({"useObjectStreams": false});
  return new Blob([u8arr], {"type" : "application/pdf"});
}

Sign and set protection.

/**
 * @param {ArrayBuffer} pdf
 * @param {ArrayBuffer} cert
 * @param {string} pwd
 * @param {string} opwd
 * @return {Promise<Blob>}
 */
async function signAndProtect1(pdf, cert, pwd, opwd){
  /** @type {SignOption} */
  var sopt = {
    p12cert: cert,
    pwd: pwd,
  };
  /** @type {EncryptOption} */
  var eopt = {
    mode: Zga.Crypto.Mode.RC4_128,
    permissions: ["modify", "annot-forms", "fill-forms", "extract", "assemble"],
    ownerpwd: opwd,
  };
  var signer = new Zga.PdfSigner(sopt);
  var u8arr = await signer.sign(pdf, eopt);
  return new Blob([u8arr], {"type" : "application/pdf"});
}

Sign and set protection by the same certificate.

/**
 * @param {ArrayBuffer} pdf
 * @param {ArrayBuffer} cert
 * @param {string} pwd
 * @return {Promise<Blob>}
 */
async function signAndProtect2(pdf, cert, pwd){
  /** @type {SignOption} */
  var sopt = {
    p12cert: cert,
    pwd: pwd,
  };
  /** @type {EncryptOption} */
  var eopt = {
    mode: Zga.Crypto.Mode.AES_256,
    permissions: ["modify", "annot-forms", "fill-forms", "extract", "assemble"],
    pubkeys: [],
  };
  var signer = new Zga.PdfSigner(sopt);
  var u8arr = await signer.sign(pdf, eopt);
  return new Blob([u8arr], {"type" : "application/pdf"});
}

Detail of EncryptOption

  • mode: Zga.Crypto.Mode 👉 The values of Zga.Crypto.Mode
    • RC4_40: 40bit RC4 Encryption
    • RC4_128: 128bit RC4 Encryption
    • AES_128: 128bit AES Encryption
    • AES_256: 256bit AES Encryption
  • permissions: Array 👉 (Optional) The set of permissions to be blocked
    • "copy": (Only valid on public-key mode) Copy text and graphics from the document;
    • "print": Print the document;
    • "modify": Modify the contents of the document by operations other than those controlled by 'fill-forms', 'extract' and 'assemble';
    • "copy-extract": Copy or otherwise extract text and graphics from the document;
    • "annot-forms": Add or modify text annotations, fill in interactive form fields, and, if 'modify' is also set, create or modify interactive form fields (including signature fields);
    • "fill-forms": Fill in existing interactive form fields (including signature fields), even if 'annot-forms' is not specified;
    • "extract": Extract text and graphics (in support of accessibility to users with disabilities or for other purposes);
    • "assemble": Assemble the document (insert, rotate, or delete pages and create bookmarks or thumbnail images), even if 'modify' is not set;
    • "print-high": Print the document to a representation from which a faithful digital copy of the PDF content could be generated. When this is not set, printing is limited to a low-level representation of the appearance, possibly of degraded quality.
  • userpwd: string 👉 (Optional) User password. Used when opening the pdf.
  • ownerpwd: string 👉 (Optional) Owner password. If not specified, a random value is used.
  • pubkeys: Array<PubKeyInfo> 👉 (Optional) Array of recipients containing public-key certificates ('c') and permissions ('p').
    • c: Array|Uint8Array|ArrayBuffer|string|forge_cert 👉 (Optional) A public-key certificate. Only when you want to encrypt the pdf by the certificate used in signing, the c can be omitted.
    • p: Array 👉 (Optional) Permissions

Thanks

  • The module of setting protection was almost migrated from TCPDF.

License

This tool is available under the MIT license.

Note

  • CORS: Cross-Origin Resource Sharing
  • CRL: Certificate Revocation Lists
  • DocMDP: Document Modification Detection and Prevention
  • LTV: Long-Term Validation
  • OCSP: Online Certificate Status Protocol
  • TSA: Time Stamp Authority