Zgapdfsigner
A javascript tool to sign a pdf in web browser, google apps script and nodejs.
Install / Use
/learn @zboris12/ZgapdfsignerREADME
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 or a text or both.
- A visible signature can be placed on multiple pages. (In the same position)
- 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. ( :no_entry_sign:Not available in web browser :sunflower:)
- Sign a pdf with a timestamp from TSA. ( :no_entry_sign:Not available in web browser :sunflower:)
- Enable signature's LTV. ( :no_entry_sign:Not available in web browser :sunflower:)
- 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.
:sunflower: However, if you can avoid the CORS security restrictions
by creating your own service or providing a reverse proxy server, these features are also available in web browser.
The Dependencies
- pdf-lib
- node-forge
- follow-redirects
- pako - For drawing text
- pdf-fontkit - For drawing text
How to use this tool
:question: For more details please see the wiki.
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://cdn.jsdelivr.net/npm/zgapdfsigner/dist/zgapdfsigner.min.js" type="text/javascript"></script>
When drawing text for signature, importing fontkit and pako library is necessary.
<script src="https://unpkg.com/pdf-fontkit@1.8.9/dist/fontkit.umd.min.js" type="text/javascript"></script>
<script src="https://unpkg.com/pako@1.0.11/dist/pako_inflate.min.js" type="text/javascript"></script>
Thanks to znacloud for fixing the font subsetting issue in @pdf-lib/fontkit.
Google Apps Script
Load the dependencies and this tool.
// Simulate setTimeout function for pdf-lib
function setTimeout(func, sleep){
Utilities.sleep(sleep);
func();
}
// Simulate clearTimeout function for pdf-fontkit
function clearTimeout(timeoutID){
// Do nothing
}
// 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());
// It is necessary for drawing text for signature.
eval(UrlFetchApp.fetch("https://unpkg.com/pdf-fontkit@1.8.9/dist/fontkit.umd.min.js").getContentText());
// Load pako, It is necessary for drawing text for signature.
eval(UrlFetchApp.fetch("https://unpkg.com/pako@1.0.11/dist/pako_inflate.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://cdn.jsdelivr.net/npm/zgapdfsigner/dist/zgapdfsigner.min.js").getContentText());
Or simply import the library of ZgaPdfToolkit
- Add the library of ZgaPdfToolkit to your project, and suppose the id of library you defined is "pdfkit".
Script id:1T0UPf50gGp2fJ4dR1rZfEFgKYC5VpCwUVooCRNySiL7klvIUVsFBCZ9m - Load the library.
pdfkit.loadZga(globalThis);
nodejs
- Install
npm install zgapdfsigner
If using typescript for development, installation of definitely typed for node-forge is necessary.
npm install --save-dev @types/node-forge
- Import
// CommonJS Mode
const Zga = require("zgapdfsigner");
// ES Module Mode
import { default as Zga } from "zgapdfsigner";
// Typescript
import * as Zga from "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
},
imgInfo: {
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 by drawing a text.
/**
* @param {ArrayBuffer} pdf
* @param {ArrayBuffer} cert
* @param {string} pwd
* @param {string} txt
* @param {ArrayBuffer} fontdat
* @return {Promise<Blob>}
*/
async function sign3(pdf, cert, pwd, txt, fontdat){
/** @type {SignOption} */
var sopt = {
p12cert: cert,
pwd: pwd,
drawinf: {
area: {
x: 25, // left
y: 150, // top
w: 60, // width
h: 60, // height
},
textInfo: {
text: txt,
fontData: fontdat,
color: "#00f0f1",
size: 16,
},
},
};
var signer = new Zga.PdfSigner(sopt);
var u8arr = await signer.sign(pdf);
return new Blob([u8arr], {"type" : "application/pdf"});
}
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");
/** @type {string} */
var txt = "I am a test string!";
/** @type {string} */
var fontPath = m_path.join(__dirname, "_test.ttf");
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 imgTy
