Working on LTV, Completeness is 90%.

pull/2/head
zboris12 2022-11-14 22:20:19 +09:00
parent 3f0dd0af9f
commit dd52e4e8be
11 changed files with 494 additions and 223 deletions

2
.env.sample Normal file
View File

@ -0,0 +1,2 @@
java="C:\java8\jre\bin\java.exe"
closure="C:\closure-compiler\closure-compiler-v20220104.jar"

View File

@ -1,3 +1,5 @@
<div align="center"><img src="logo.png" title="zgapdfsigner"></div>
# ZgaPdfSigner # ZgaPdfSigner
A javascript tool to sign a pdf or set protection of a pdf in web browser. 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 and nodejs. And it also can be used in Google Apps Script and nodejs.

180
closure.js Normal file
View File

@ -0,0 +1,180 @@
const m_fs = require("fs");
const m_path = require("path");
const m_cp = require("child_process");
/** @const {string} */
const m_libpath = "lib/";
/** @const {string} */
const m_distpath = "dist/_";
/** @const {Array<string>} */
const m_targets = ["zgacertsutil.js", "zgapdfcryptor.js", "zgapdfsigner.js", "zgaindex.js"];
/** @const {Array<string>} */
const m_dists = [];
/**
* @param {string} raw
* @return {Uint8Array}
*/
function rawToU8arr(raw){
/** @type {Uint8Array} */
var arr = new Uint8Array(raw.length);
for(var i=0; i<raw.length; i++){
arr[i] = raw.charCodeAt(i);
}
return arr;
}
/** @const {number} */
const m_repcode = "*".charCodeAt(0);
/** @const {Uint8Array} */
const m_cprbufst = rawToU8arr("//Only for nodejs Start");
/** @const {number} */
const m_repidxst = 1;
/** @const {Uint8Array} */
const m_cprbufed = rawToU8arr("Only for nodejs End//");
/** @const {number} */
const m_repidxed = 19;
/** @type {boolean} */
var m_debug = false;
/**
* @param {Uint8Array} tgtbuf
* @param {number} idx
* @param {Uint8Array} cprbuf
* @return {boolean}
*/
function sameBuffer(tgtbuf, idx, cprbuf){
for(var i=0; i<cprbuf.length; i++,idx++){
if(idx >= tgtbuf.length){
return false;
}else if(tgtbuf[idx] != cprbuf[i]){
return false;
}
}
return true;
}
/**
* @param {string} js
*/
function fixjs(js){
/** @type {string} */
var jspath = m_path.join(__dirname, m_libpath + js);
/** @type {Buffer} */
var jsbuf = m_fs.readFileSync(jspath);
for(var i=0; i<jsbuf.length; i++){
if(sameBuffer(jsbuf, i, m_cprbufst)){
jsbuf[i + m_repidxst] = m_repcode;
}else if(sameBuffer(jsbuf, i, m_cprbufed)){
jsbuf[i + m_repidxed] = m_repcode;
}
}
jspath = m_distpath + js;
m_dists.push(jspath);
jspath = m_path.join(__dirname, jspath);
m_fs.writeFileSync(jspath, jsbuf);
if(m_debug){
console.log("Output file: " + jspath);
}
}
/**
* @param {string} js
*/
function deltmpjs(js){
/** @type {string} */
var jspath = m_path.join(__dirname, js);
m_fs.rmSync(jspath);
if(m_debug){
console.log("Deleted file: " + jspath);
}
}
/**
* @param {envfil}
* @return {Object<string, string>}
*/
function loadEnv(envfil){
/** @type {Object<string, string>} */
var retobj = {};
/** @type {string} */
var envpath = m_path.join(__dirname, envfil);
/** @type {Array<string>} */
var envs = m_fs.readFileSync(envpath, "utf8").split("\n");
envs.forEach(function(/** @type {string} */a_env){
a_env = a_env.trimStart();
if(a_env.charAt(0) != "#"){
var a_idx = a_env.indexOf("=");
if(a_idx > 0){
retobj[a_env.substring(0, a_idx)] = a_env.substring(a_idx + 1);
}
}
});
if(m_debug){
console.log("Environment:");
console.log(retobj);
}
return retobj;
}
function main(){
if(process.argv.indexOf("-debug") > 0){
m_debug = true;
}
/** @type {Object<string, string>} */
var env = loadEnv(".env");
/** @type {boolean} */
var flg = true;
if(!env.java){
console.error("Can't find java's execution path in .env file.");
flg = false;
}
if(!env.closure){
console.error("Can't find closure complier's path in .env file.");
flg = false;
}
if(!flg){
return;
}
m_targets.forEach(fixjs);
/** @type {Array<string>} */
var cmd = [env.java];
cmd.push("-jar " + env.closure);
cmd.push("--charset UTF-8");
cmd.push("--compilation_level SIMPLE_OPTIMIZATIONS");
cmd.push("--warning_level VERBOSE");
cmd.push("--externs closure/google-ext.js");
cmd.push("--externs closure/forge-ext.js");
cmd.push("--externs closure/pdflib-ext.js");
cmd.push("--externs closure/zb-externs.js");
m_dists.forEach(function(a_js){
cmd.push("--js " + a_js);
});
cmd.push("--js_output_file dist/zgapdfsigner.min.js");
if(m_debug){
console.log(cmd.join(" "));
}
console.log("Excuting google closure compiler...\n");
m_cp.exec(cmd.join(" "), function(a_err, a_stdout, a_stderr){
const a_rex = new RegExp("^" + m_distpath, "g");
// if(a_err){
// console.log(a_err);
// }
if(a_stdout){
console.log(a_stdout.replaceAll(a_rex, m_libpath));
}
if(a_stderr){
console.log(a_stderr.replaceAll(a_rex, m_libpath));
}
m_dists.forEach(deltmpjs);
console.log("Done");
});
}
main();

View File

@ -164,6 +164,11 @@ Zga.PdfCryptor = function(encopt){};
* @return {Promise<PDFLib.PDFDocument>} * @return {Promise<PDFLib.PDFDocument>}
*/ */
Zga.PdfCryptor.prototype.encryptPdf = function(pdf, ref){}; Zga.PdfCryptor.prototype.encryptPdf = function(pdf, ref){};
/**
* @param {number} num
* @param {PDFLib.PDFObject} val
*/
Zga.PdfCryptor.prototype.encryptObject = function(num, val){};
/** /**
* @constructor * @constructor
* @param {Array<forge_cert|forge.asn1|string>=} certs * @param {Array<forge_cert|forge.asn1|string>=} certs
@ -198,6 +203,10 @@ Zga.CertsChain.prototype.prepareDSSInf = function(crlOnly){};
* @param {TsaServiceInfo} inf * @param {TsaServiceInfo} inf
*/ */
Zga.TsaFetcher = function(inf){}; Zga.TsaFetcher = function(inf){};
/** @type {string} */
Zga.TsaFetcher.prototype.url;
/** @type {number} */
Zga.TsaFetcher.prototype.len;
/** /**
* @param {string=} data * @param {string=} data
* @return {Promise<string>} * @return {Promise<string>}

View File

@ -1,3 +1,5 @@
'use strict';
/** /**
* @param {Object<string, *>} z * @param {Object<string, *>} z
*/ */
@ -877,9 +879,9 @@ z.TsaFetcher = class{
this.asnc = forge.asn1.Class; this.asnc = forge.asn1.Class;
/** @private @lends {forge.asn1.Type} */ /** @private @lends {forge.asn1.Type} */
this.asnt = forge.asn1.Type; this.asnt = forge.asn1.Type;
/** @private @type {string} */ /** @public @type {string} */
this.url = inf.url; this.url = inf.url;
/** @private @type {number} */ /** @public @type {number} */
this.len = inf.len ? inf.len : 0; this.len = inf.len ? inf.len : 0;
/** @private @type {Object<string, *>|undefined} */ /** @private @type {Object<string, *>|undefined} */
this.headers = inf.headers; this.headers = inf.headers;

View File

@ -1,3 +1,5 @@
'use strict';
/** /**
* @return {Object<string, *>} * @return {Object<string, *>}
*/ */
@ -59,11 +61,17 @@ function genZga(){
return z; return z;
} }
//Only for nodejs Start//
if(typeof exports === "object" && typeof module !== "undefined"){ if(typeof exports === "object" && typeof module !== "undefined"){
module.exports = genZga(); module.exports = genZga();
}else if(!globalThis.Zga){ }else{
//Only for nodejs End//
if(!globalThis.Zga){
globalThis.Zga = genZga(); globalThis.Zga = genZga();
supplyZgaCertsChain(globalThis.Zga); supplyZgaCertsChain(globalThis.Zga);
supplyZgaCryptor(globalThis.Zga); supplyZgaCryptor(globalThis.Zga);
supplyZgaSigner(globalThis.Zga); supplyZgaSigner(globalThis.Zga);
} }
//Only for nodejs Start//
}
//Only for nodejs End//

View File

@ -1,3 +1,5 @@
'use strict';
// This module was migrated from [TCPDF](http://www.tcpdf.org) // This module was migrated from [TCPDF](http://www.tcpdf.org)
/** /**
* @param {Object<string, *>} z * @param {Object<string, *>} z
@ -428,6 +430,8 @@ z.PdfCryptor = class{
* @return {Promise<PDFLib.PDFDocument>} * @return {Promise<PDFLib.PDFDocument>}
*/ */
async encryptPdf(pdf, ref){ async encryptPdf(pdf, ref){
/** @const {z.PdfCryptor} */
const _this = this;
/** @type {PDFLib.PDFDocument} */ /** @type {PDFLib.PDFDocument} */
var pdfdoc = await z.loadPdf(pdf); var pdfdoc = await z.loadPdf(pdf);
if(pdfdoc === pdf && !ref){ if(pdfdoc === pdf && !ref){
@ -437,45 +441,10 @@ z.PdfCryptor = class{
/** @type {PDFLib.PDFContext} */ /** @type {PDFLib.PDFContext} */
var pdfcont = pdfdoc.context; var pdfcont = pdfdoc.context;
/** @type {PDFLib.PDFObject} */ /** @type {PDFLib.PDFObject} */
var trobj = this.prepareEncrypt(pdfcont); var trobj = _this.prepareEncrypt(pdfcont);
/**
* @param {number} a_num
* @param {PDFLib.PDFObject} a_val
*/
var func = function(a_num, a_val){
if(a_val instanceof PDFLib.PDFContentStream){
/** @type {Uint8Array} */
var a_dat = a_val.contentsCache.access();
if(a_dat){
a_val.contentsCache.value = this.encryptU8arr(a_num, a_dat);
}
}else if(a_val instanceof PDFLib.PDFStream){
if(a_val.contents){
a_val.contents = this.encryptU8arr(a_num, a_val.contents);
}
}else if(a_val instanceof PDFLib.PDFHexString){
if(a_val.value){
a_val.value = this.encryptHexstr(a_num, a_val.value);
}
}else if(a_val instanceof PDFLib.PDFString){
if(a_val.value){
a_val.value = z.Crypto._escape(this._encrypt_data(a_num, a_val.value));
}
}
if(a_val.dict instanceof Map){
/** @type {Iterator} */
var a_es = a_val.dict.entries();
/** @type {IIterableResult<PdfObjEntry>} */
var a_res = a_es.next();
while(!a_res.done){
func(a_num, a_res.value[1]);
a_res = a_es.next();
}
}
}.bind(this);
pdfcont.enumerateIndirectObjects().forEach(function(/** @type {PdfObjEntry} */a_arr){ pdfcont.enumerateIndirectObjects().forEach(function(/** @type {PdfObjEntry} */a_arr){
func(a_arr[0].objectNumber, a_arr[1]); _this.encryptObject(a_arr[0].objectNumber, a_arr[1]);
}); });
if(ref){ if(ref){
@ -488,6 +457,43 @@ z.PdfCryptor = class{
return pdfdoc; return pdfdoc;
} }
/**
* @public
* @param {number} num
* @param {PDFLib.PDFObject} val
*/
encryptObject(num, val){
if(val instanceof PDFLib.PDFContentStream){
/** @type {Uint8Array} */
var dat = val.contentsCache.access();
if(dat){
val.contentsCache.value = this.encryptU8arr(num, dat);
}
}else if(val instanceof PDFLib.PDFStream){
if(val.contents){
val.contents = this.encryptU8arr(num, val.contents);
}
}else if(val instanceof PDFLib.PDFHexString){
if(val.value){
val.value = this.encryptHexstr(num, val.value);
}
}else if(val instanceof PDFLib.PDFString){
if(val.value){
val.value = z.Crypto._escape(this._encrypt_data(num, val.value));
}
}
if(val.dict instanceof Map){
/** @type {Iterator} */
var es = val.dict.entries();
/** @type {IIterableResult<PdfObjEntry>} */
var res = es.next();
while(!res.done){
this.encryptObject(num, res.value[1]);
res = es.next();
}
}
}
/** /**
* Prepare for encryption and create the object for saving in trailer. * Prepare for encryption and create the object for saving in trailer.
* *
@ -955,6 +961,8 @@ z.PdfCryptor = class{
} }
//Only for nodejs Start//
if(typeof exports === "object" && typeof module !== "undefined"){ if(typeof exports === "object" && typeof module !== "undefined"){
module.exports = supplyZgaCryptor; module.exports = supplyZgaCryptor;
} }
//Only for nodejs End//

View File

@ -1,3 +1,5 @@
'use strict';
/** /**
* @param {Object<string, *>} z * @param {Object<string, *>} z
*/ */
@ -215,6 +217,8 @@ z.PdfSigner = class{
constructor(signopt){ constructor(signopt){
/** @public @type {Zga.TsaFetcher} */ /** @public @type {Zga.TsaFetcher} */
this.tsaFetcher = null; this.tsaFetcher = null;
/** @public @type {Zga.PdfCryptor} */
this.cyptr = null;
/** @private @const {string} */ /** @private @const {string} */
this.DEFAULT_BYTE_RANGE_PLACEHOLDER = "**********"; this.DEFAULT_BYTE_RANGE_PLACEHOLDER = "**********";
@ -226,8 +230,6 @@ z.PdfSigner = class{
this.privateKey = null; this.privateKey = null;
/** @type {Zga.CertsChain} */ /** @type {Zga.CertsChain} */
this.cchain = null; this.cchain = null;
/** @private @type {?TsaServiceInfo} */
this.tsainf = null;
/** @private @type {string} */ /** @private @type {string} */
this.signature = ""; this.signature = "";
/** @private @type {number} */ /** @private @type {number} */
@ -250,35 +252,38 @@ z.PdfSigner = class{
if(!(globalThis.forge || forge)){ if(!(globalThis.forge || forge)){
throw new Error("node-forge is not imported."); throw new Error("node-forge is not imported.");
} }
/** @type {?TsaServiceInfo} */
var tsainf = null;
if(signopt.signdate){ if(signopt.signdate){
if(typeof signopt.signdate == "string"){ if(typeof signopt.signdate == "string"){
this.tsainf = { tsainf = {
url: signopt.signdate, url: signopt.signdate,
}; };
}else if(signopt.signdate.url){ }else if(signopt.signdate.url){
this.tsainf = /** @type {TsaServiceInfo} */(Object.assign({}, signopt.signdate)); tsainf = /** @type {TsaServiceInfo} */(Object.assign({}, signopt.signdate));
} }
} }
if(this.tsainf){ if(tsainf){
if(!z.urlFetch){ if(!z.urlFetch){
throw new Error("Because of the CORS security restrictions, signing with TSA is not supported in web browser."); throw new Error("Because of the CORS security restrictions, signing with TSA is not supported in web browser.");
} }
if(z.TSAURLS[this.tsainf.url]){ if(z.TSAURLS[tsainf.url]){
Object.assign(this.tsainf, z.TSAURLS[this.tsainf.url]); Object.assign(tsainf, z.TSAURLS[tsainf.url]);
}else if(!(new RegExp("^https?://")).test(this.tsainf.url)){ }else if(!(new RegExp("^https?://")).test(tsainf.url)){
throw new Error("Unknown tsa data. " + JSON.stringify(this.tsainf)); throw new Error("Unknown tsa data. " + JSON.stringify(tsainf));
} }
if(!this.tsainf.len){ if(!tsainf.len){
this.tsainf.len = 16000; tsainf.len = 16000;
} }
this.tsaFetcher = new z.TsaFetcher(/** @type {TsaServiceInfo} */(tsainf));
} }
if(signopt.ltv && !z.urlFetch){ if(signopt.ltv && !z.urlFetch){
throw new Error("Because of the CORS security restrictions, signing with LTV is not supported in web browser."); throw new Error("Because of the CORS security restrictions, signing with LTV is not supported in web browser.");
} }
if(signopt.permission == 1 && signopt.ltv){ // if(signopt.permission == 1 && signopt.ltv){
z.log("To enable LTV we need to append informations after signing, this will destroy the signature if full DocMDP protection is set. (Sign with permission = 1)"); // z.log("To enable LTV we need to append informations after signing, this will destroy the signature if full DocMDP protection is set. (Sign with permission = 1)");
throw new Error("When set full DocMDP protection, LTV can't be enabled."); // throw new Error("When set full DocMDP protection, LTV can't be enabled.");
} // }
} }
/** /**
@ -302,7 +307,7 @@ z.PdfSigner = class{
if(Array.isArray(pdf)){ if(Array.isArray(pdf)){
_this.oriU8pdf = new Uint8Array(pdf); _this.oriU8pdf = new Uint8Array(pdf);
}else{ }else{
_this.oriU8pdf = PDFLib.toUint8Array(/** @type {(ArrayBuffer|Uint8Array|string)} */(pdf)); _this.oriU8pdf = PDFLib.toUint8Array(/** @type {ArrayBuffer|Uint8Array|string} */(pdf));
} }
pdfdoc = await PDFLib.PDFDocument.load(_this.oriU8pdf); pdfdoc = await PDFLib.PDFDocument.load(_this.oriU8pdf);
} }
@ -338,7 +343,7 @@ z.PdfSigner = class{
} }
} }
z.fixCertAttributes(cert); z.fixCertAttributes(cert);
}else if(_this.tsainf){ }else if(_this.tsaFetcher){
z.log("No certificate is specified, so only add a document timestamp.") z.log("No certificate is specified, so only add a document timestamp.")
}else{ }else{
throw new Error("Nothing to do because no certificate nor tsa is specified."); throw new Error("Nothing to do because no certificate nor tsa is specified.");
@ -346,9 +351,24 @@ z.PdfSigner = class{
/** @type {boolean} *///append mode or not /** @type {boolean} *///append mode or not
var apmode = _this.addSignHolder(pdfdoc); var apmode = _this.addSignHolder(pdfdoc);
await pdfdoc.flush();
z.log("A signature holder has been added to the pdf."); z.log("A signature holder has been added to the pdf.");
if(_this.opt.permission == 1 && (_this.opt.ltv == 1 || _this.opt.ltv == 2)){
if(!_this.cchain){
// Query a timestamp from tsa with dummy string to obtain the certificates.
await _this.queryTsa("dummy");
}
/** @type {PDFLib.PDFDocument} */
var dmydoc = await _this.addDss(pdfdoc);
if(dmydoc){
z.log("In order to enable LTV, DSS informations has been added to the pdf.");
}
// Clear ltv
_this.opt.ltv = 0;
}else{
await pdfdoc.flush();
}
if(apmode){ if(apmode){
if(_this.oriU8pdf){ if(_this.oriU8pdf){
z.log("The pdf has been signed already, so we add a new signature to it."); z.log("The pdf has been signed already, so we add a new signature to it.");
@ -381,8 +401,8 @@ z.PdfSigner = class{
} }
} }
/** @type {Zga.PdfCryptor} */ /** @type {Zga.PdfCryptor} */
var cypt = new z.PdfCryptor(cypopt); _this.cyptr = new z.PdfCryptor(cypopt);
await cypt.encryptPdf(pdfdoc, encref); await _this.cyptr.encryptPdf(pdfdoc, encref);
z.log("Pdf data has been encrypted."); z.log("Pdf data has been encrypted.");
} }
} }
@ -408,22 +428,11 @@ z.PdfSigner = class{
throw new Error("Failed to sign the pdf."); throw new Error("Failed to sign the pdf.");
} }
if(_this.opt.ltv == 1 || _this.opt.ltv == 2){ pdfdoc = await _this.addDss(ret);
cchain = _this.cchain ? _this.cchain : _this.tsaFetcher.getCertsChain(); if(pdfdoc){
if(cchain.isSelfSignedCert()){ await _this.findChangedObjects(pdfdoc, true);
z.log("No need to enable LTV because the certificate is a self signed one."); ret = _this.appendIncrement(pdfdoc);
}else{
/** @type {boolean} */
var crlOnly = (_this.opt.ltv == 2);
_this.oriU8pdf = ret;
pdfdoc = await PDFLib.PDFDocument.load(_this.oriU8pdf, {ignoreEncryption: true});
ret = await _this.applyLTV(pdfdoc, cchain, crlOnly);
if(ret){
z.log("LTV has been enabled."); z.log("LTV has been enabled.");
}else{
throw new Error("Failed to enable the LTV.");
}
}
} }
return ret; return ret;
@ -465,6 +474,9 @@ z.PdfSigner = class{
/** @type {PDFLib.PDFObject} */ /** @type {PDFLib.PDFObject} */
var a_obj = oriPdfdoc.context.lookup(a_ele[0]); var a_obj = oriPdfdoc.context.lookup(a_ele[0]);
if(!(a_obj && _this.isamePdfObject(a_ele[1], a_obj))){ if(!(a_obj && _this.isamePdfObject(a_ele[1], a_obj))){
if(_this.cyptr){
_this.cyptr.encryptObject(a_ele[0].objectNumber, a_ele[1]);
}
_this.apobjs.push(a_ele); _this.apobjs.push(a_ele);
} }
} }
@ -720,7 +732,7 @@ z.PdfSigner = class{
/** @type {Date} */ /** @type {Date} */
var signdate = new Date(); var signdate = new Date();
if(_this.opt.signdate instanceof Date && !_this.tsainf){ if(_this.opt.signdate instanceof Date && !_this.tsaFetcher){
signdate = _this.opt.signdate; signdate = _this.opt.signdate;
} }
@ -731,7 +743,7 @@ z.PdfSigner = class{
bytrng.push(PDFLib.PDFName.of(_this.DEFAULT_BYTE_RANGE_PLACEHOLDER)); bytrng.push(PDFLib.PDFName.of(_this.DEFAULT_BYTE_RANGE_PLACEHOLDER));
bytrng.push(PDFLib.PDFName.of(_this.DEFAULT_BYTE_RANGE_PLACEHOLDER)); bytrng.push(PDFLib.PDFName.of(_this.DEFAULT_BYTE_RANGE_PLACEHOLDER));
_this.siglen = /** @type {number} */(_this.tsainf ? _this.tsainf.len : 3322); _this.siglen = /** @type {number} */(_this.tsaFetcher ? _this.tsaFetcher.len : 3322);
_this.sigContents = PDFLib.PDFHexString.of("0".repeat(_this.siglen)); _this.sigContents = PDFLib.PDFHexString.of("0".repeat(_this.siglen));
/** @type {Object<string, *>} */ /** @type {Object<string, *>} */
@ -803,7 +815,11 @@ z.PdfSigner = class{
var ans = page.node.Annots(); var ans = page.node.Annots();
if(!ans){ if(!ans){
ans = new PDFLib.PDFArray(pdfcont); ans = new PDFLib.PDFArray(pdfcont);
page.node.set(PDFLib.PDFName.Annots, pdfcont.register(ans)); // if(docMdp){
page.node.set(PDFLib.PDFName.Annots, ans);
// }else{
// page.node.set(PDFLib.PDFName.Annots, pdfcont.register(ans));
// }
} }
ans.push(widgetDictRef); ans.push(widgetDictRef);
@ -922,7 +938,7 @@ z.PdfSigner = class{
if(_this.cchain){ if(_this.cchain){
/** @type {Date} */ /** @type {Date} */
var signdate = new Date(); var signdate = new Date();
if(_this.opt.signdate instanceof Date && !_this.tsainf){ if(_this.opt.signdate instanceof Date && !_this.tsaFetcher){
signdate = _this.opt.signdate; signdate = _this.opt.signdate;
} }
@ -959,7 +975,7 @@ z.PdfSigner = class{
// Sign in detached mode. // Sign in detached mode.
p7.sign({"detached": true}); p7.sign({"detached": true});
if(_this.tsainf){ if(_this.tsaFetcher){
/** @type {forge.asn1} */ /** @type {forge.asn1} */
var tsatoken = await _this.queryTsa(p7.signers[0].signature, true); var tsatoken = await _this.queryTsa(p7.signers[0].signature, true);
p7.signerInfos[0].value.push(tsatoken); p7.signerInfos[0].value.push(tsatoken);
@ -1001,7 +1017,6 @@ z.PdfSigner = class{
async queryTsa(data, forP7){ async queryTsa(data, forP7){
/** @const {z.PdfSigner} */ /** @const {z.PdfSigner} */
const _this = this; const _this = this;
_this.tsaFetcher = new z.TsaFetcher(/** @type {TsaServiceInfo} */(_this.tsainf));
/** @type {?string} */ /** @type {?string} */
var err = await _this.tsaFetcher.queryTsa(data); var err = await _this.tsaFetcher.queryTsa(data);
if(err){ if(err){
@ -1009,42 +1024,44 @@ z.PdfSigner = class{
}else{ }else{
/** @type {forge.asn1} */ /** @type {forge.asn1} */
var asn1 = _this.tsaFetcher.getToken(forP7); var asn1 = _this.tsaFetcher.getToken(forP7);
z.log("Timestamp from " + _this.tsainf.url + " has been obtained."); z.log("Timestamp from " + _this.tsaFetcher.url + " has been obtained.");
return asn1; return asn1;
} }
} }
/** /**
* @public * @private
* @param {PDFLib.PDFDocument} pdfdoc * @param {PDFLib.PDFDocument|Uint8Array} pdf
* @param {Zga.CertsChain} chain * @return {Promise<PDFLib.PDFDocument>}
* @param {boolean=} crlOnly
* @return {Promise<Uint8Array>}
*/ */
async applyLTV(pdfdoc, chain, crlOnly){ async addDss(pdf){
/** @const {z.PdfSigner} */ /** @const {z.PdfSigner} */
const _this = this; const _this = this;
if(_this.opt.ltv != 1 && _this.opt.ltv != 2){
return null;
}
/** @type {Zga.CertsChain} */
var cchain = _this.cchain ? _this.cchain : _this.tsaFetcher.getCertsChain();
if(cchain.isSelfSignedCert()){
z.log("No need to enable LTV because the certificate is a self signed one.");
return null;
}
/** @type {boolean} */
var crlOnly = (_this.opt.ltv == 2);
/** @type {?DSSInfo} */ /** @type {?DSSInfo} */
var dssinf = await chain.prepareDSSInf(crlOnly); var dssinf = await cchain.prepareDSSInf(crlOnly);
if(!dssinf){ if(!dssinf){
return null; return null;
} }
if(!_this.addDss(pdfdoc, _this.signature, dssinf)){
return null;
}
await pdfdoc.flush();
await _this.findChangedObjects(pdfdoc, true);
return _this.appendIncrement(pdfdoc);
}
/** /** @type {PDFLib.PDFDocument} */
* @private var pdfdoc = null;
* @param {PDFLib.PDFDocument} pdfdoc if(pdf.addPage){
* @param {string} sig signature pdfdoc = /** @type {PDFLib.PDFDocument} */(pdf);
* @param {DSSInfo=} dssinf }else{
* @return {boolean} _this.oriU8pdf = /** @type {Uint8Array} */(pdf);
*/ pdfdoc = await PDFLib.PDFDocument.load(_this.oriU8pdf, {ignoreEncryption: true});
addDss(pdfdoc, sig, dssinf){ }
/** @type {PDFLib.PDFContext} */ /** @type {PDFLib.PDFContext} */
var pdfcont = pdfdoc.context; var pdfcont = pdfdoc.context;
/** @type {Array<PDFLib.PDFRef>} */ /** @type {Array<PDFLib.PDFRef>} */
@ -1073,7 +1090,7 @@ z.PdfSigner = class{
if(!(ocspRefs || crlRefs)){ if(!(ocspRefs || crlRefs)){
// Nothing to do. // Nothing to do.
return false; return null;
} }
if(dssinf && dssinf.certs && dssinf.certs.length > 0){ if(dssinf && dssinf.certs && dssinf.certs.length > 0){
@ -1087,11 +1104,14 @@ z.PdfSigner = class{
}); });
} }
/** @type {string} */
var sighex = "";
if(_this.signature){
/** @type {forge.md.digest} */ /** @type {forge.md.digest} */
var md = forge.md.sha1.create(); var md = forge.md.sha1.create();
md.update(sig); md.update(_this.signature);
/** @type {string} */ sighex = md.digest().toHex().toUpperCase();
var sighex = md.digest().toHex().toUpperCase(); }
var dss = /** @type {PDFLib.PDFDict} */(pdfdoc.catalog.lookupMaybe(PDFLib.PDFName.of("DSS"), PDFLib.PDFDict)); var dss = /** @type {PDFLib.PDFDict} */(pdfdoc.catalog.lookupMaybe(PDFLib.PDFName.of("DSS"), PDFLib.PDFDict));
/** @type {PDFLib.PDFArray} */ /** @type {PDFLib.PDFArray} */
@ -1103,9 +1123,12 @@ z.PdfSigner = class{
/** @type {PDFLib.PDFDict} */ /** @type {PDFLib.PDFDict} */
var vri = null; var vri = null;
/** @type {Object<string, *>} */ /** @type {Object<string, *>} */
var vriObj = { var vriObj = null;
if(sighex){
vriObj = {
TU: PDFLib.PDFString.fromDate(new Date()), TU: PDFLib.PDFString.fromDate(new Date()),
}; };
}
/** @type {PDFLib.PDFArray} */ /** @type {PDFLib.PDFArray} */
var sigcertsarr = null; var sigcertsarr = null;
/** @type {PDFLib.PDFArray} */ /** @type {PDFLib.PDFArray} */
@ -1122,47 +1145,63 @@ z.PdfSigner = class{
pdfdoc.catalog.set(PDFLib.PDFName.of("DSS"), pdfcont.register(dss)); pdfdoc.catalog.set(PDFLib.PDFName.of("DSS"), pdfcont.register(dss));
} }
if(certRefs){ if(certRefs){
if(vriObj){
sigcertsarr = new PDFLib.PDFArray(pdfcont); sigcertsarr = new PDFLib.PDFArray(pdfcont);
vriObj["Cert"] = sigcertsarr; vriObj["Cert"] = sigcertsarr;
}
if(!certsarr){ if(!certsarr){
certsarr = new PDFLib.PDFArray(pdfcont); certsarr = new PDFLib.PDFArray(pdfcont);
dss.set(PDFLib.PDFName.of("Certs"), pdfcont.register(certsarr)); dss.set(PDFLib.PDFName.of("Certs"), pdfcont.register(certsarr));
} }
certRefs.forEach(function(/** @type {PDFLib.PDFRef} */a_ref){ certRefs.forEach(function(/** @type {PDFLib.PDFRef} */a_ref){
if(sigcertsarr){
sigcertsarr.push(a_ref); sigcertsarr.push(a_ref);
}
certsarr.push(a_ref); certsarr.push(a_ref);
}); });
} }
if(ocspRefs){ if(ocspRefs){
if(vriObj){
sigocpsarr = new PDFLib.PDFArray(pdfcont); sigocpsarr = new PDFLib.PDFArray(pdfcont);
vriObj["OCSP"] = sigocpsarr; vriObj["OCSP"] = sigocpsarr;
}
if(!ocpsarr){ if(!ocpsarr){
ocpsarr = new PDFLib.PDFArray(pdfcont); ocpsarr = new PDFLib.PDFArray(pdfcont);
dss.set(PDFLib.PDFName.of("OCSPs"), pdfcont.register(ocpsarr)); dss.set(PDFLib.PDFName.of("OCSPs"), pdfcont.register(ocpsarr));
} }
ocspRefs.forEach(function(/** @type {PDFLib.PDFRef} */a_ref){ ocspRefs.forEach(function(/** @type {PDFLib.PDFRef} */a_ref){
if(sigocpsarr){
sigocpsarr.push(a_ref); sigocpsarr.push(a_ref);
}
ocpsarr.push(a_ref); ocpsarr.push(a_ref);
}); });
} }
if(crlRefs){ if(crlRefs){
if(vriObj){
sigcrlsarr = new PDFLib.PDFArray(pdfcont); sigcrlsarr = new PDFLib.PDFArray(pdfcont);
vriObj["CRL"] = sigcrlsarr; vriObj["CRL"] = sigcrlsarr;
}
if(!crlsarr){ if(!crlsarr){
crlsarr = new PDFLib.PDFArray(pdfcont); crlsarr = new PDFLib.PDFArray(pdfcont);
dss.set(PDFLib.PDFName.of("CRLs"), pdfcont.register(crlsarr)); dss.set(PDFLib.PDFName.of("CRLs"), pdfcont.register(crlsarr));
} }
crlRefs.forEach(function(/** @type {PDFLib.PDFRef} */a_ref){ crlRefs.forEach(function(/** @type {PDFLib.PDFRef} */a_ref){
if(sigcrlsarr){
sigcrlsarr.push(a_ref); sigcrlsarr.push(a_ref);
}
crlsarr.push(a_ref); crlsarr.push(a_ref);
}); });
} }
if(sighex && vriObj){
if(!vri){ if(!vri){
vri = /** @type {PDFLib.PDFDict} */(pdfcont.obj({})); vri = /** @type {PDFLib.PDFDict} */(pdfcont.obj({}));
dss.set(PDFLib.PDFName.of("VRI"), pdfcont.register(vri)); dss.set(PDFLib.PDFName.of("VRI"), pdfcont.register(vri));
} }
vri.set(PDFLib.PDFName.of(sighex), pdfcont.register(pdfcont.obj(vriObj))); vri.set(PDFLib.PDFName.of(sighex), pdfcont.register(pdfcont.obj(vriObj)));
return true; }
await pdfdoc.flush();
return pdfdoc;
} }
}; };

BIN
logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -1,6 +1,7 @@
{ {
"name": "zgapdfsigner", "name": "zgapdfsigner",
"version": "2.3.0", "version": "2.3.0",
"author": "zboris12",
"description": "A javascript tool to sign a pdf or set protection to a pdf in web browser, Google Apps Script and nodejs.", "description": "A javascript tool to sign a pdf or set protection to a pdf in web browser, Google Apps Script and nodejs.",
"homepage": "https://github.com/zboris12/zgapdfsigner", "homepage": "https://github.com/zboris12/zgapdfsigner",
"private": false, "private": false,
@ -26,7 +27,8 @@
"TSA" "TSA"
], ],
"scripts": { "scripts": {
"testsign": "node test4node.js" "build": "node closure.js",
"test": "node test4node.js"
}, },
"dependencies": { "dependencies": {
"pdf-lib": "1.17.1", "pdf-lib": "1.17.1",

View File

@ -4,6 +4,111 @@ const Zga = require("./lib/zganode.js");
const workpath = "test/"; const workpath = "test/";
/**
* @param {string} pdfPath
* @param {string} pfxPath
* @param {string} ps
* @param {number} perm
* @param {string=} imgPath
* @return {Promise<string>} output path
*/
async function sign_protect(pdfPath, pfxPath, ps, perm, imgPath){
/** @type {Buffer} */
var pdf = m_fs.readFileSync(pdfPath);
/** @type {Buffer} */
var pfx = m_fs.readFileSync(pfxPath);
/** @type {Buffer} */
var img = null;
/** @type {string} */
var imgType = "";
if(perm == 1){
console.log("\nTest signing pdf with full protection. (permission 1 and password encryption)");
}else{
console.log("\nTest signing pdf with permission "+perm);
}
if(imgPath){
img = m_fs.readFileSync(imgPath);
imgType = m_path.extname(imgPath).slice(1);
}
/** @type {SignOption} */
var sopt = {
p12cert: pfx,
pwd: ps,
permission: perm,
signdate: "1",
reason: "I have a test reason "+perm+".",
location: "I am on the earth "+perm+".",
contact: "zga"+perm+"@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 {EncryptOption} */
var eopt = undefined;
if(perm == 1){
eopt = {
mode: Zga.Crypto.Mode.AES_256,
permissions: ["copy", "copy-extract", "print-high"],
userpwd: "123",
};
}
/** @type {Zga.PdfSigner} */
var ser = new Zga.PdfSigner(sopt);
/** @type {Uint8Array} */
var u8dat = await ser.sign(pdf, eopt);
if(u8dat){
/** @type {string} */
var outPath = m_path.join(__dirname, workpath+"test_perm"+perm+".pdf");
m_fs.writeFileSync(outPath, u8dat);
console.log("Output file: " + outPath);
}
return outPath;
}
/**
* @param {string} pdfPath
* @return {Promise<string>} output path
*/
async function addtsa(pdfPath){
console.log("\nTest signing pdf by a timestamp.");
/** @type {Buffer} */
var pdf = m_fs.readFileSync(pdfPath);
/** @type {SignOption} */
var sopt = {
signdate: "2",
reason: "I have a test reason tsa.",
location: "I am on the earth tsa.",
contact: "zgatsa@zga.com",
ltv: 1,
debug: true,
};
/** @type {Zga.PdfSigner} */
var ser = new Zga.PdfSigner(sopt);
/** @type {Uint8Array} */
var u8dat = await ser.sign(pdf);
/** @type {string} */
var outPath = m_path.join(__dirname, workpath+"test_tsa.pdf");
m_fs.writeFileSync(outPath, u8dat);
console.log("Output file: " + outPath);
return outPath;
}
async function main(){ async function main(){
/** @type {string} */ /** @type {string} */
var pdfPath = m_path.join(__dirname, workpath+"_test.pdf"); var pdfPath = m_path.join(__dirname, workpath+"_test.pdf");
@ -26,101 +131,15 @@ async function main(){
pfxPath = ""; pfxPath = "";
} }
/** @type {Buffer} */
var pdf = m_fs.readFileSync(pdfPath);
/** @type {Buffer} */
var pfx = null;
if(pfxPath){ if(pfxPath){
pfx = m_fs.readFileSync(pfxPath); await sign_protect(pdfPath, pfxPath, ps, 1, imgPath);
} pdfPath = await sign_protect(pdfPath, pfxPath, ps, 2, imgPath);
/** @type {Buffer} */ await addtsa(pdfPath);
var img = null; }else{
/** @type {string} */ await addtsa(pdfPath);
var imgType = "";
if(imgPath){
img = m_fs.readFileSync(imgPath);
imgType = m_path.extname(imgPath).slice(1);
} }
/** @type {SignOption} */
var sopt = null;
if(pdf){
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,
},
// pageidx: 2,
imgData: img,
imgType: imgType,
};
}
}
/** @type {EncryptOption} */
var eopt = undefined;
eopt = {
mode: Zga.Crypto.Mode.AES_256,
permissions: ["copy", "copy-extract", "print-high"],
userpwd: "123",
};
// eopt.pubkeys = [];
/** @type {Uint8Array} */
var u8dat = null;
if(sopt){
/** @type {Zga.PdfSigner} */
var ser = new Zga.PdfSigner(sopt);
u8dat = await ser.sign(pdf, eopt);
}
if(u8dat){
/** @type {string} */
var outPath = m_path.join(__dirname, workpath+"test_signed.pdf");
m_fs.writeFileSync(outPath, u8dat);
console.log("Output file: " + outPath);
}
console.log("Done"); console.log("Done");
} }
async function main2(){
/** @type {string} */
var pdfPath = m_path.join(__dirname, workpath+"test_signed.pdf");
/** @type {Buffer} */
var pdf = m_fs.readFileSync(pdfPath);
/** @type {SignOption} */
var sopt = {
signdate: "2",
reason: "I have a test reason.",
location: "I am on the earth.",
contact: "zga@zga.com",
ltv: 1,
debug: true,
};
/** @type {Zga.PdfSigner} */
var ser = new Zga.PdfSigner(sopt);
/** @type {Uint8Array} */
var u8dat = await ser.sign(pdf);
/** @type {string} */
var outPath = m_path.join(__dirname, workpath+"test_signed_tsa.pdf");
m_fs.writeFileSync(outPath, u8dat);
console.log("Output file: " + outPath);
return;
}
main(); main();
// main2();