zgapdfsigner/README.md

9.0 KiB

ZgaPdfSigner

A javascript tool to sign a pdf or set protection of a pdf in web browser.
And it also can be used in Google Apps Script.

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.

About signing with TSA

This tool supports signing with a timestamp from TSA(Time Stamp Authority), but because of the CORS security restrictions in web browser, this function can only be used in Google Apps Script.

And because node-forge hasn't supported unauthenticated attributes in pkcs7 yet, so when use this function, the edited version needs to be imported.

The Dependencies

How to use this tool

Just import the dependencies and this tool.

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

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,
  };
  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 a picture.

/**
 * @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

// 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://github.com/zboris12/zgapdfsigner/releases/download/1.2.0/forge.min.edited.js").getContentText());
// Load ZgaPdfSigner
eval(UrlFetchApp.fetch("https://github.com/zboris12/zgapdfsigner/releases/download/2.0.0/zgapdfsigner.min.js").getContentText());

// Load pdf, certificate
var pdfBlob = DriveApp.getFilesByName("_test.pdf").next().getBlob();
var certBlob = DriveApp.getFilesByName("_test.pfx").next().getBlob();
// Sign the pdf
var sopt = {
  p12cert: certBlob.getBytes(),
  pwd: "some passphrase",
  signdate: "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"));

Detail of SignOption

  • p12cert: Array|Uint8Array|ArrayBuffer|string 👉 Certificate's data
  • pwd: string 👉 The passphrase of the certificate
  • reason: string 👉 (Optional) The reason for signing
  • location: string 👉 (Optional) Your location
  • contact: string 👉 (Optional) Your contact information
  • signdate: Date|string|TsaServiceInfo 👉 (Optional)
  • signame: string 👉 (Optional) The name of the signature
  • drawinf: SignDrawInfo 👉 (Optional) Visible signature's information
    • area: SignAreaInfo 👉 The signature's drawing area
      • x: number 👉 Distance from left
      • y: number 👉 Distance from top
      • w: number 👉 Width
      • h: number 👉 Height
    • pageidx: number 👉 (Optional) The page index for drawing the signature
    • 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 on 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 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.AES_256,
    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"});
}

Sign and set protection.

/**
 * @param {ArrayBuffer} pdf
 * @param {ArrayBuffer} cert
 * @param {string} pwd
 * @param {string} opwd
 * @return {Promise<Blob>}
 */
async function sign1(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"});
}

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<Zga.Crypto.Permission> 👉 (Optional) The set of permissions you want to block
    • "print": Print the document;
    • "modify": Modify the contents of the document by operations other than those controlled by 'fill-forms', 'extract' and 'assemble';
    • "copy": 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.
    • "owner": (inverted logic - only for public-key) when set permits change of encryption and enables all other permissions.
  • 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'). Not supported yet.
    • c: string 👉 A public-key certificate
    • p: Array<Zga.Crypto.Permission> 👉 (Optional) Permissions

Thanks

  • The module of setting protection was migrated from TCPDF.

License

This tool is available under the MIT license.