Working on LTV, Completeness is 90%.
parent
3f0dd0af9f
commit
dd52e4e8be
|
@ -0,0 +1,2 @@
|
|||
java="C:\java8\jre\bin\java.exe"
|
||||
closure="C:\closure-compiler\closure-compiler-v20220104.jar"
|
|
@ -1,3 +1,5 @@
|
|||
<div align="center"><img src="logo.png" title="zgapdfsigner"></div>
|
||||
|
||||
# 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 and nodejs.
|
||||
|
|
|
@ -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();
|
|
@ -164,6 +164,11 @@ Zga.PdfCryptor = function(encopt){};
|
|||
* @return {Promise<PDFLib.PDFDocument>}
|
||||
*/
|
||||
Zga.PdfCryptor.prototype.encryptPdf = function(pdf, ref){};
|
||||
/**
|
||||
* @param {number} num
|
||||
* @param {PDFLib.PDFObject} val
|
||||
*/
|
||||
Zga.PdfCryptor.prototype.encryptObject = function(num, val){};
|
||||
/**
|
||||
* @constructor
|
||||
* @param {Array<forge_cert|forge.asn1|string>=} certs
|
||||
|
@ -198,6 +203,10 @@ Zga.CertsChain.prototype.prepareDSSInf = function(crlOnly){};
|
|||
* @param {TsaServiceInfo} inf
|
||||
*/
|
||||
Zga.TsaFetcher = function(inf){};
|
||||
/** @type {string} */
|
||||
Zga.TsaFetcher.prototype.url;
|
||||
/** @type {number} */
|
||||
Zga.TsaFetcher.prototype.len;
|
||||
/**
|
||||
* @param {string=} data
|
||||
* @return {Promise<string>}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* @param {Object<string, *>} z
|
||||
*/
|
||||
|
@ -877,9 +879,9 @@ z.TsaFetcher = class{
|
|||
this.asnc = forge.asn1.Class;
|
||||
/** @private @lends {forge.asn1.Type} */
|
||||
this.asnt = forge.asn1.Type;
|
||||
/** @private @type {string} */
|
||||
/** @public @type {string} */
|
||||
this.url = inf.url;
|
||||
/** @private @type {number} */
|
||||
/** @public @type {number} */
|
||||
this.len = inf.len ? inf.len : 0;
|
||||
/** @private @type {Object<string, *>|undefined} */
|
||||
this.headers = inf.headers;
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* @return {Object<string, *>}
|
||||
*/
|
||||
|
@ -59,11 +61,17 @@ function genZga(){
|
|||
return z;
|
||||
}
|
||||
|
||||
//Only for nodejs Start//
|
||||
if(typeof exports === "object" && typeof module !== "undefined"){
|
||||
module.exports = genZga();
|
||||
}else if(!globalThis.Zga){
|
||||
globalThis.Zga = genZga();
|
||||
supplyZgaCertsChain(globalThis.Zga);
|
||||
supplyZgaCryptor(globalThis.Zga);
|
||||
supplyZgaSigner(globalThis.Zga);
|
||||
}else{
|
||||
//Only for nodejs End//
|
||||
if(!globalThis.Zga){
|
||||
globalThis.Zga = genZga();
|
||||
supplyZgaCertsChain(globalThis.Zga);
|
||||
supplyZgaCryptor(globalThis.Zga);
|
||||
supplyZgaSigner(globalThis.Zga);
|
||||
}
|
||||
//Only for nodejs Start//
|
||||
}
|
||||
//Only for nodejs End//
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
// This module was migrated from [TCPDF](http://www.tcpdf.org)
|
||||
/**
|
||||
* @param {Object<string, *>} z
|
||||
|
@ -428,6 +430,8 @@ z.PdfCryptor = class{
|
|||
* @return {Promise<PDFLib.PDFDocument>}
|
||||
*/
|
||||
async encryptPdf(pdf, ref){
|
||||
/** @const {z.PdfCryptor} */
|
||||
const _this = this;
|
||||
/** @type {PDFLib.PDFDocument} */
|
||||
var pdfdoc = await z.loadPdf(pdf);
|
||||
if(pdfdoc === pdf && !ref){
|
||||
|
@ -437,45 +441,10 @@ z.PdfCryptor = class{
|
|||
/** @type {PDFLib.PDFContext} */
|
||||
var pdfcont = pdfdoc.context;
|
||||
/** @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){
|
||||
func(a_arr[0].objectNumber, a_arr[1]);
|
||||
_this.encryptObject(a_arr[0].objectNumber, a_arr[1]);
|
||||
});
|
||||
|
||||
if(ref){
|
||||
|
@ -488,6 +457,43 @@ z.PdfCryptor = class{
|
|||
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.
|
||||
*
|
||||
|
@ -955,6 +961,8 @@ z.PdfCryptor = class{
|
|||
|
||||
}
|
||||
|
||||
//Only for nodejs Start//
|
||||
if(typeof exports === "object" && typeof module !== "undefined"){
|
||||
module.exports = supplyZgaCryptor;
|
||||
}
|
||||
//Only for nodejs End//
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* @param {Object<string, *>} z
|
||||
*/
|
||||
|
@ -215,6 +217,8 @@ z.PdfSigner = class{
|
|||
constructor(signopt){
|
||||
/** @public @type {Zga.TsaFetcher} */
|
||||
this.tsaFetcher = null;
|
||||
/** @public @type {Zga.PdfCryptor} */
|
||||
this.cyptr = null;
|
||||
|
||||
/** @private @const {string} */
|
||||
this.DEFAULT_BYTE_RANGE_PLACEHOLDER = "**********";
|
||||
|
@ -226,8 +230,6 @@ z.PdfSigner = class{
|
|||
this.privateKey = null;
|
||||
/** @type {Zga.CertsChain} */
|
||||
this.cchain = null;
|
||||
/** @private @type {?TsaServiceInfo} */
|
||||
this.tsainf = null;
|
||||
/** @private @type {string} */
|
||||
this.signature = "";
|
||||
/** @private @type {number} */
|
||||
|
@ -250,35 +252,38 @@ z.PdfSigner = class{
|
|||
if(!(globalThis.forge || forge)){
|
||||
throw new Error("node-forge is not imported.");
|
||||
}
|
||||
/** @type {?TsaServiceInfo} */
|
||||
var tsainf = null;
|
||||
if(signopt.signdate){
|
||||
if(typeof signopt.signdate == "string"){
|
||||
this.tsainf = {
|
||||
tsainf = {
|
||||
url: signopt.signdate,
|
||||
};
|
||||
}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){
|
||||
throw new Error("Because of the CORS security restrictions, signing with TSA is not supported in web browser.");
|
||||
}
|
||||
if(z.TSAURLS[this.tsainf.url]){
|
||||
Object.assign(this.tsainf, z.TSAURLS[this.tsainf.url]);
|
||||
}else if(!(new RegExp("^https?://")).test(this.tsainf.url)){
|
||||
throw new Error("Unknown tsa data. " + JSON.stringify(this.tsainf));
|
||||
if(z.TSAURLS[tsainf.url]){
|
||||
Object.assign(tsainf, z.TSAURLS[tsainf.url]);
|
||||
}else if(!(new RegExp("^https?://")).test(tsainf.url)){
|
||||
throw new Error("Unknown tsa data. " + JSON.stringify(tsainf));
|
||||
}
|
||||
if(!this.tsainf.len){
|
||||
this.tsainf.len = 16000;
|
||||
if(!tsainf.len){
|
||||
tsainf.len = 16000;
|
||||
}
|
||||
this.tsaFetcher = new z.TsaFetcher(/** @type {TsaServiceInfo} */(tsainf));
|
||||
}
|
||||
if(signopt.ltv && !z.urlFetch){
|
||||
throw new Error("Because of the CORS security restrictions, signing with LTV is not supported in web browser.");
|
||||
}
|
||||
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)");
|
||||
throw new Error("When set full DocMDP protection, LTV can't be enabled.");
|
||||
}
|
||||
// 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)");
|
||||
// throw new Error("When set full DocMDP protection, LTV can't be enabled.");
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -302,7 +307,7 @@ z.PdfSigner = class{
|
|||
if(Array.isArray(pdf)){
|
||||
_this.oriU8pdf = new Uint8Array(pdf);
|
||||
}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);
|
||||
}
|
||||
|
@ -338,7 +343,7 @@ z.PdfSigner = class{
|
|||
}
|
||||
}
|
||||
z.fixCertAttributes(cert);
|
||||
}else if(_this.tsainf){
|
||||
}else if(_this.tsaFetcher){
|
||||
z.log("No certificate is specified, so only add a document timestamp.")
|
||||
}else{
|
||||
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
|
||||
var apmode = _this.addSignHolder(pdfdoc);
|
||||
await pdfdoc.flush();
|
||||
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(_this.oriU8pdf){
|
||||
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} */
|
||||
var cypt = new z.PdfCryptor(cypopt);
|
||||
await cypt.encryptPdf(pdfdoc, encref);
|
||||
_this.cyptr = new z.PdfCryptor(cypopt);
|
||||
await _this.cyptr.encryptPdf(pdfdoc, encref);
|
||||
z.log("Pdf data has been encrypted.");
|
||||
}
|
||||
}
|
||||
|
@ -408,22 +428,11 @@ z.PdfSigner = class{
|
|||
throw new Error("Failed to sign the pdf.");
|
||||
}
|
||||
|
||||
if(_this.opt.ltv == 1 || _this.opt.ltv == 2){
|
||||
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.");
|
||||
}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.");
|
||||
}else{
|
||||
throw new Error("Failed to enable the LTV.");
|
||||
}
|
||||
}
|
||||
pdfdoc = await _this.addDss(ret);
|
||||
if(pdfdoc){
|
||||
await _this.findChangedObjects(pdfdoc, true);
|
||||
ret = _this.appendIncrement(pdfdoc);
|
||||
z.log("LTV has been enabled.");
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
@ -465,6 +474,9 @@ z.PdfSigner = class{
|
|||
/** @type {PDFLib.PDFObject} */
|
||||
var a_obj = oriPdfdoc.context.lookup(a_ele[0]);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -720,7 +732,7 @@ z.PdfSigner = class{
|
|||
|
||||
/** @type {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;
|
||||
}
|
||||
|
||||
|
@ -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));
|
||||
|
||||
_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));
|
||||
|
||||
/** @type {Object<string, *>} */
|
||||
|
@ -803,7 +815,11 @@ z.PdfSigner = class{
|
|||
var ans = page.node.Annots();
|
||||
if(!ans){
|
||||
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);
|
||||
|
||||
|
@ -922,7 +938,7 @@ z.PdfSigner = class{
|
|||
if(_this.cchain){
|
||||
/** @type {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;
|
||||
}
|
||||
|
||||
|
@ -959,7 +975,7 @@ z.PdfSigner = class{
|
|||
// Sign in detached mode.
|
||||
p7.sign({"detached": true});
|
||||
|
||||
if(_this.tsainf){
|
||||
if(_this.tsaFetcher){
|
||||
/** @type {forge.asn1} */
|
||||
var tsatoken = await _this.queryTsa(p7.signers[0].signature, true);
|
||||
p7.signerInfos[0].value.push(tsatoken);
|
||||
|
@ -1001,7 +1017,6 @@ z.PdfSigner = class{
|
|||
async queryTsa(data, forP7){
|
||||
/** @const {z.PdfSigner} */
|
||||
const _this = this;
|
||||
_this.tsaFetcher = new z.TsaFetcher(/** @type {TsaServiceInfo} */(_this.tsainf));
|
||||
/** @type {?string} */
|
||||
var err = await _this.tsaFetcher.queryTsa(data);
|
||||
if(err){
|
||||
|
@ -1009,42 +1024,44 @@ z.PdfSigner = class{
|
|||
}else{
|
||||
/** @type {forge.asn1} */
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @param {PDFLib.PDFDocument} pdfdoc
|
||||
* @param {Zga.CertsChain} chain
|
||||
* @param {boolean=} crlOnly
|
||||
* @return {Promise<Uint8Array>}
|
||||
* @private
|
||||
* @param {PDFLib.PDFDocument|Uint8Array} pdf
|
||||
* @return {Promise<PDFLib.PDFDocument>}
|
||||
*/
|
||||
async applyLTV(pdfdoc, chain, crlOnly){
|
||||
async addDss(pdf){
|
||||
/** @const {z.PdfSigner} */
|
||||
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} */
|
||||
var dssinf = await chain.prepareDSSInf(crlOnly);
|
||||
var dssinf = await cchain.prepareDSSInf(crlOnly);
|
||||
if(!dssinf){
|
||||
return null;
|
||||
}
|
||||
if(!_this.addDss(pdfdoc, _this.signature, dssinf)){
|
||||
return null;
|
||||
}
|
||||
await pdfdoc.flush();
|
||||
await _this.findChangedObjects(pdfdoc, true);
|
||||
return _this.appendIncrement(pdfdoc);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {PDFLib.PDFDocument} pdfdoc
|
||||
* @param {string} sig signature
|
||||
* @param {DSSInfo=} dssinf
|
||||
* @return {boolean}
|
||||
*/
|
||||
addDss(pdfdoc, sig, dssinf){
|
||||
/** @type {PDFLib.PDFDocument} */
|
||||
var pdfdoc = null;
|
||||
if(pdf.addPage){
|
||||
pdfdoc = /** @type {PDFLib.PDFDocument} */(pdf);
|
||||
}else{
|
||||
_this.oriU8pdf = /** @type {Uint8Array} */(pdf);
|
||||
pdfdoc = await PDFLib.PDFDocument.load(_this.oriU8pdf, {ignoreEncryption: true});
|
||||
}
|
||||
/** @type {PDFLib.PDFContext} */
|
||||
var pdfcont = pdfdoc.context;
|
||||
/** @type {Array<PDFLib.PDFRef>} */
|
||||
|
@ -1073,7 +1090,7 @@ z.PdfSigner = class{
|
|||
|
||||
if(!(ocspRefs || crlRefs)){
|
||||
// Nothing to do.
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
if(dssinf && dssinf.certs && dssinf.certs.length > 0){
|
||||
|
@ -1087,11 +1104,14 @@ z.PdfSigner = class{
|
|||
});
|
||||
}
|
||||
|
||||
/** @type {forge.md.digest} */
|
||||
var md = forge.md.sha1.create();
|
||||
md.update(sig);
|
||||
/** @type {string} */
|
||||
var sighex = md.digest().toHex().toUpperCase();
|
||||
var sighex = "";
|
||||
if(_this.signature){
|
||||
/** @type {forge.md.digest} */
|
||||
var md = forge.md.sha1.create();
|
||||
md.update(_this.signature);
|
||||
sighex = md.digest().toHex().toUpperCase();
|
||||
}
|
||||
|
||||
var dss = /** @type {PDFLib.PDFDict} */(pdfdoc.catalog.lookupMaybe(PDFLib.PDFName.of("DSS"), PDFLib.PDFDict));
|
||||
/** @type {PDFLib.PDFArray} */
|
||||
|
@ -1103,9 +1123,12 @@ z.PdfSigner = class{
|
|||
/** @type {PDFLib.PDFDict} */
|
||||
var vri = null;
|
||||
/** @type {Object<string, *>} */
|
||||
var vriObj = {
|
||||
TU: PDFLib.PDFString.fromDate(new Date()),
|
||||
};
|
||||
var vriObj = null;
|
||||
if(sighex){
|
||||
vriObj = {
|
||||
TU: PDFLib.PDFString.fromDate(new Date()),
|
||||
};
|
||||
}
|
||||
/** @type {PDFLib.PDFArray} */
|
||||
var sigcertsarr = null;
|
||||
/** @type {PDFLib.PDFArray} */
|
||||
|
@ -1122,47 +1145,63 @@ z.PdfSigner = class{
|
|||
pdfdoc.catalog.set(PDFLib.PDFName.of("DSS"), pdfcont.register(dss));
|
||||
}
|
||||
if(certRefs){
|
||||
sigcertsarr = new PDFLib.PDFArray(pdfcont);
|
||||
vriObj["Cert"] = sigcertsarr;
|
||||
if(vriObj){
|
||||
sigcertsarr = new PDFLib.PDFArray(pdfcont);
|
||||
vriObj["Cert"] = sigcertsarr;
|
||||
}
|
||||
if(!certsarr){
|
||||
certsarr = new PDFLib.PDFArray(pdfcont);
|
||||
dss.set(PDFLib.PDFName.of("Certs"), pdfcont.register(certsarr));
|
||||
}
|
||||
certRefs.forEach(function(/** @type {PDFLib.PDFRef} */a_ref){
|
||||
sigcertsarr.push(a_ref);
|
||||
if(sigcertsarr){
|
||||
sigcertsarr.push(a_ref);
|
||||
}
|
||||
certsarr.push(a_ref);
|
||||
});
|
||||
}
|
||||
if(ocspRefs){
|
||||
sigocpsarr = new PDFLib.PDFArray(pdfcont);
|
||||
vriObj["OCSP"] = sigocpsarr;
|
||||
if(vriObj){
|
||||
sigocpsarr = new PDFLib.PDFArray(pdfcont);
|
||||
vriObj["OCSP"] = sigocpsarr;
|
||||
}
|
||||
if(!ocpsarr){
|
||||
ocpsarr = new PDFLib.PDFArray(pdfcont);
|
||||
dss.set(PDFLib.PDFName.of("OCSPs"), pdfcont.register(ocpsarr));
|
||||
}
|
||||
ocspRefs.forEach(function(/** @type {PDFLib.PDFRef} */a_ref){
|
||||
sigocpsarr.push(a_ref);
|
||||
if(sigocpsarr){
|
||||
sigocpsarr.push(a_ref);
|
||||
}
|
||||
ocpsarr.push(a_ref);
|
||||
});
|
||||
}
|
||||
if(crlRefs){
|
||||
sigcrlsarr = new PDFLib.PDFArray(pdfcont);
|
||||
vriObj["CRL"] = sigcrlsarr;
|
||||
if(vriObj){
|
||||
sigcrlsarr = new PDFLib.PDFArray(pdfcont);
|
||||
vriObj["CRL"] = sigcrlsarr;
|
||||
}
|
||||
if(!crlsarr){
|
||||
crlsarr = new PDFLib.PDFArray(pdfcont);
|
||||
dss.set(PDFLib.PDFName.of("CRLs"), pdfcont.register(crlsarr));
|
||||
}
|
||||
crlRefs.forEach(function(/** @type {PDFLib.PDFRef} */a_ref){
|
||||
sigcrlsarr.push(a_ref);
|
||||
if(sigcrlsarr){
|
||||
sigcrlsarr.push(a_ref);
|
||||
}
|
||||
crlsarr.push(a_ref);
|
||||
});
|
||||
}
|
||||
if(!vri){
|
||||
vri = /** @type {PDFLib.PDFDict} */(pdfcont.obj({}));
|
||||
dss.set(PDFLib.PDFName.of("VRI"), pdfcont.register(vri));
|
||||
if(sighex && vriObj){
|
||||
if(!vri){
|
||||
vri = /** @type {PDFLib.PDFDict} */(pdfcont.obj({}));
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"name": "zgapdfsigner",
|
||||
"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.",
|
||||
"homepage": "https://github.com/zboris12/zgapdfsigner",
|
||||
"private": false,
|
||||
|
@ -26,7 +27,8 @@
|
|||
"TSA"
|
||||
],
|
||||
"scripts": {
|
||||
"testsign": "node test4node.js"
|
||||
"build": "node closure.js",
|
||||
"test": "node test4node.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"pdf-lib": "1.17.1",
|
||||
|
|
201
test4node.js
201
test4node.js
|
@ -4,6 +4,111 @@ const Zga = require("./lib/zganode.js");
|
|||
|
||||
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(){
|
||||
/** @type {string} */
|
||||
var pdfPath = m_path.join(__dirname, workpath+"_test.pdf");
|
||||
|
@ -26,101 +131,15 @@ async function main(){
|
|||
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);
|
||||
await sign_protect(pdfPath, pfxPath, ps, 1, imgPath);
|
||||
pdfPath = await sign_protect(pdfPath, pfxPath, ps, 2, imgPath);
|
||||
await addtsa(pdfPath);
|
||||
}else{
|
||||
await addtsa(pdfPath);
|
||||
}
|
||||
|
||||
/** @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");
|
||||
}
|
||||
|
||||
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();
|
||||
// main2();
|
||||
|
|
Loading…
Reference in New Issue