Changed folder structure and working on LTV.

pull/2/head
zboris12 2022-11-06 18:03:48 +09:00
parent 8b008ccc34
commit 3f0dd0af9f
11 changed files with 1717 additions and 349 deletions

View File

@ -5,12 +5,12 @@ doskey csr=%csr% $*
set externs=--externs closure\google-ext.js --externs closure\forge-ext.js --externs closure\pdflib-ext.js --externs closure\zb-externs.js
rem main
set src=.
set jss=--js %src%\zgapdfcryptor.js --js %src%\zgapdfsigner.js
set src=lib
set jss=--js %src%\zgacertsutil.js --js %src%\zgapdfcryptor.js --js %src%\zgapdfsigner.js --js %src%\zgaindex.js
echo $
set chkj=%%externs%% --checks_only %%jss%%
echo chkj=csr %chkj%
doskey chkj=%csr% %chkj%
set csrj=%%externs%% %%jss%% --js_output_file %src%\dist\zgapdfsigner.min.js
set csrj=%%externs%% %%jss%% --js_output_file dist\zgapdfsigner.min.js
echo csrj=csr %csrj%
doskey csrj=%csr% %csrj%

View File

@ -67,6 +67,13 @@ forge.util.hexToBytes = function(hex){};
* @return {string}
*/
forge.util.decodeUtf8 = function(value){};
forge.util.binary = {};
forge.util.binary.hex = {};
/**
* @param {string} str
* @return {Uint8Array}
*/
forge.util.binary.hex.decode = function(str){};
/** @constructor */
forge.asn1 = function(){};
@ -119,6 +126,12 @@ forge.asn1.Type.OID;
forge.asn1.Type.NULL;
/** @type {number} */
forge.asn1.Type.OCTETSTRING;
/** @type {number} */
forge.asn1.Type.PRINTABLESTRING;
/** @type {number} */
forge.asn1.Type.ENUMERATED;
/** @type {number} */
forge.asn1.Type.BITSTRING;
forge.asn1.Class = {};
/** @type {number} */
forge.asn1.Class.UNIVERSAL;
@ -133,8 +146,21 @@ forge.asn1.Class.CONTEXT_SPECIFIC;
* @return {forge.asn1}
*/
forge.asn1.create = function(tagClass, type, constructed, value, options){};
/** @type {Array<forge.asn1>} */
/**
* @param {forge.asn1} obj
* @param {Object<string, *>} v
* @param {Object<string, *>} capture
* @param {Array<string>} errors
*/
forge.asn1.validate = function(obj, v, capture, errors){};
/** @type {Array<forge.asn1>|string} */
forge.asn1.prototype.value;
/** @type {number} */
forge.asn1.prototype.tagClass;
/** @type {number} */
forge.asn1.prototype.type;
/** @type {boolean} */
forge.asn1.prototype.constructed;
/** @constructor */
const forge_BigInteger = function(){};
@ -149,6 +175,20 @@ const forge_cert = function(){};
forge_cert.prototype.publicKey;
/** @type {forge_cert_issuer} */
forge_cert.prototype.issuer;
/** @type {string} */
forge_cert.prototype.serialNumber;
/** @type {forge_cert_issuer} */
forge_cert.prototype.subject;
/**
* @param {forge_cert} parent
* @return {boolean}
*/
forge_cert.prototype.isIssuer = function(parent){};
/**
* @param {string|forge_cert_extension} nm
* @return {forge_cert_extension}
*/
forge_cert.prototype.getExtension = function(nm){};
/** @constructor */
const forge_key = function(){};
/** @type {forge_BigInteger} */
@ -159,6 +199,23 @@ forge_key.prototype.e;
const forge_cert_issuer = function(){};
/** @type {Array<forge_cert_attr>} */
forge_cert_issuer.prototype.attributes;
/**
* @param {string} sn
* @return {forge.asn1}
*/
forge_cert_issuer.prototype.getField = function(sn){};
/**
* @typedef
* {{
* id: (string|undefined),
* name: (string|undefined),
* critical: (boolean|undefined),
* value: (string|undefined),
* cA: (boolean|undefined),
* }}
*/
var forge_cert_extension;
/**
* @typedef
* {{
@ -263,9 +320,6 @@ var P12Bag;
*/
forge.pkcs12.prototype.getBags = function(filter){};
forge.oids = {};
/** @type {string} */
forge.oids.sha256;
forge.pki = {};
forge.pki.oids = {};
/** @type {string} */
@ -273,6 +327,8 @@ forge.pki.oids.certBag;
/** @type {string} */
forge.pki.oids.pkcs8ShroudedKeyBag;
/** @type {string} */
forge.pki.oids.sha1;
/** @type {string} */
forge.pki.oids.sha256;
/** @type {string} */
forge.pki.oids.contentType;
@ -282,12 +338,35 @@ forge.pki.oids.data;
forge.pki.oids.messageDigest;
/** @type {string} */
forge.pki.oids.signingTime;
/** @type {string} */
forge.pki.oids.rsaEncryption;
/** @type {string} */
forge.pki.oids.sha256WithRSAEncryption;
/** @type {string} */
forge.pki.oids.commonName;
/** @lends {forge.pki.oids} */
forge.oids = forge.pki.oids;
/**
* @param {forge.asn1} obj
* @param {boolean=} computeHash
* @return {forge_cert}
*/
forge.pki.certificateFromAsn1 = function(obj, computeHash){};
/**
* @param {forge_cert} cert
* @return {forge.asn1}
*/
forge.pki.certificateToAsn1 = function(cert){};
/**
* @param {forge_cert_issuer} obj
* @return {forge.asn1}
*/
forge.pki.distinguishedNameToAsn1 = function(obj){};
/**
* @param {forge_key} key
* @return {forge.asn1}
*/
forge.pki.publicKeyToRSAPublicKey = function(key){};
forge.md = {};
/** @constructor */

View File

@ -108,7 +108,10 @@ PDFLib.PDFAcroForm.prototype.addField = function(field){};
/** @type {PDFLib.PDFDict} */
PDFLib.PDFAcroForm.prototype.dict;
/** @constructor */
/**
* @constructor
* @extends {PDFLib.PDFDict}
*/
PDFLib.PDFCatalog = function(){};
/**
* @param {PDFLib.PDFName} name
@ -196,6 +199,7 @@ PDFLib.PDFContext.prototype.enumerateIndirectObjects = function(){};
* @typedef
* {{
* Root: PDFLib.PDFRef,
* Info: PDFLib.PDFRef,
* ID: (PDFLib.PDFArray|undefined),
* }}
*/
@ -221,6 +225,12 @@ PDFLib.PDFContext.prototype.nextRef = function(){};
* @return {PDFLib.PDFObject}
*/
PDFLib.PDFContext.prototype.obj = function(literal){};
/**
* @param {string|Uint8Array} contents
* @param {PDFLib.PDFDict=} dict
* @return {PDFLib.PDFRawStream}
*/
PDFLib.PDFContext.prototype.flateStream = function(contents, dict){};
/**
* @param {PDFLib.PDFRef} ref
* @return {PDFLib.PDFObject}
@ -425,6 +435,11 @@ PDFLib.PDFContentStream = function(){};
* @return {PDFLib.PDFContentStream}
*/
PDFLib.PDFContentStream.of = function(dict, operators, encode){};
/**
* @constructor
* @extends {PDFLib.PDFStream}
*/
PDFLib.PDFRawStream = function(){};
/**
* @constructor

View File

@ -34,12 +34,16 @@ var SignAreaInfo;
var SignDrawInfo;
/**
* In the case of adding a document timestamp, the p12cert and pwd must be omitted. But meanwhile the tsa must be provided.
*
*
* permission: (DocMDP) The modification permissions granted for this document. 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.
*
* ltv: Type of Long-Term Validation. 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.
*
* @typedef
* {{
* p12cert: (Array<number>|Uint8Array|ArrayBuffer|string|undefined),
@ -51,6 +55,7 @@ var SignDrawInfo;
* signdate: (Date|TsaServiceInfo|string|undefined),
* signame: (string|undefined),
* drawinf: (SignDrawInfo|undefined),
* ltv: (number|undefined),
* debug: (boolean|undefined),
* }}
*/
@ -110,6 +115,23 @@ var CFType;
* }}
*/
var RC4LastInfo;
/**
* @typedef
* {{
* certs: (Array<forge_cert>|undefined),
* ocsps: (Array<Uint8Array>|undefined),
* crls: (Array<Uint8Array>|undefined),
* }}
*/
var DSSInfo;
/**
* @typedef
* {{
* resp: (Uint8Array|undefined),
* cchainIdx: (number|undefined),
* }}
*/
var OcspData;
var Zga = {};
/**
@ -142,6 +164,55 @@ Zga.PdfCryptor = function(encopt){};
* @return {Promise<PDFLib.PDFDocument>}
*/
Zga.PdfCryptor.prototype.encryptPdf = function(pdf, ref){};
/**
* @constructor
* @param {Array<forge_cert|forge.asn1|string>=} certs
*/
Zga.CertsChain = function(certs){};
/**
* @return {forge_cert}
*/
Zga.CertsChain.prototype.getSignCert = function(){};
/**
* @public
* @return {boolean}
*/
Zga.CertsChain.prototype.isSelfSignedCert = function(){};
/**
* @return {Array<forge_cert>}
*/
Zga.CertsChain.prototype.getAllCerts = function(){};
/**
* @public
* @param {forge_cert} cert
* @return {Promise<boolean>}
*/
Zga.CertsChain.prototype.buildChain = function(cert){};
/**
* @param {boolean=} crlOnly
* @return {Promise<DSSInfo>}
*/
Zga.CertsChain.prototype.prepareDSSInf = function(crlOnly){};
/**
* @constructor
* @param {TsaServiceInfo} inf
*/
Zga.TsaFetcher = function(inf){};
/**
* @param {string=} data
* @return {Promise<string>}
*/
Zga.TsaFetcher.prototype.queryTsa = function(data){};
/**
* @param {boolean=} forP7
* @return {forge.asn1}
*/
Zga.TsaFetcher.prototype.getToken = function(forP7){};
/**
* @return {Zga.CertsChain}
*/
Zga.TsaFetcher.prototype.getCertsChain = function(){};
/**
* @constructor
* @param {SignOption} signopt

1017
lib/zgacertsutil.js Normal file

File diff suppressed because it is too large Load Diff

69
lib/zgaindex.js Normal file
View File

@ -0,0 +1,69 @@
/**
* @return {Object<string, *>}
*/
function genZga(){
/** @const {Object<string, *>} */
const z = {};
/**
* @param {string} msg
*/
z.log = function(msg){
if(z.debug){
console.log(msg);
}
};
/**
* @param {Uint8Array} uarr
* @return {string}
*/
z.u8arrToRaw = function(uarr){
/** @type {Array<string>} */
var arr = [];
for(var i=0; i<uarr.length; i++){
arr.push(String.fromCharCode(uarr[i]));
}
return arr.join("");
};
/**
* @param {string} raw
* @return {Uint8Array}
*/
z.rawToU8arr = function(raw){
/** @type {Uint8Array} */
var arr = new Uint8Array(raw.length);
for(var i=0; i<raw.length; i++){
arr[i] = raw.charCodeAt(i);
}
return arr;
};
// Google Apps Script
if(globalThis.UrlFetchApp){
/**
* @param {string} url
* @param {UrlFetchParams} params
* @return {Promise<Uint8Array>}
*/
z.urlFetch = function(url, params){
return new Promise(function(resolve){
/** @type {GBlob} */
var tblob = UrlFetchApp.fetch(url, params).getBlob();
resolve(new Uint8Array(tblob.getBytes()));
});
};
}
return z;
}
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);
}

75
lib/zganode.js Normal file
View File

@ -0,0 +1,75 @@
const m_urlparser = require("url");
const m_h = {
"http:": require("http"),
"https:": require("https"),
};
const z = require("./zgaindex.js");
z.forge = require("node-forge");
z.PDFLib = require("pdf-lib");
/**
* @param {string} url
* @param {UrlFetchParams} params
* @return {Promise<Uint8Array>}
*/
z.urlFetch = function(url, params){
return new Promise(function(resolve, reject){
/** @type {URL} */
var opts = m_urlparser.parse(url);
var http = m_h[opts.protocol];
/** @type {string|Buffer} */
var dat = null;
var encoding = undefined;
opts.method = "GET";
if(params){
if(params.payload instanceof Buffer){
dat = params.payload;
}else if(params.payload instanceof Uint8Array){
dat = Buffer.from(params.payload.buffer);
}else if(params.payload instanceof ArrayBuffer){
dat = Buffer.from(params.payload);
}else{
dat = params.payload;
encoding = "binary";
}
if(params.headers){
opts.headers = params.headers;
}
if(params.method){
opts.method = params.method;
}
if(params.validateHttpsCertificates === false){
opts.rejectUnauthorized = false;
}
}
/** @type {http.ClientRequest} */
var hreq = http.request(opts, function(/** @type {http.IncomingMessage} */a_res){
if(a_res.statusCode !== 200){
var a_err = new Error("Failed to request url. " + url + "\n Status Code: " + a_res.statusCode);
a_res.resume();
throw a_err;
}
/** @type {Array<Buffer>} */
var a_bufs = [];
var a_bufs_len = 0;
a_res.on("data", function(/** @type {Buffer} */b_chunk){
a_bufs.push(b_chunk);
a_bufs_len += b_chunk.length;
});
a_res.on("end", function(){
/** @type {Buffer} */
var b_bdat = Buffer.concat(a_bufs, a_bufs_len);
resolve(b_bdat);
});
});
hreq.on("error", function(a_err){
throw a_err;
});
hreq.end(dat, encoding);
});
};
require("./zgacertsutil.js")(z);
require("./zgapdfcryptor.js")(z);
require("./zgapdfsigner.js")(z);
module.exports = z;

View File

@ -1,11 +1,18 @@
'use strict';
// This module was migrated from [TCPDF](http://www.tcpdf.org)
/**
* @param {Object<string, *>} z
*/
function supplyZgaCryptor(z){
//Only for nodejs Start//
if(z.forge){
var forge = z.forge;
}
if(z.PDFLib){
var PDFLib = z.PDFLib;
}
//Only for nodejs End//
/**
* @param {PDFLib.PDFDocument|Array<number>|Uint8Array|ArrayBuffer|string} pdf
* @return {Promise<PDFLib.PDFDocument>}
@ -23,47 +30,6 @@ z.loadPdf = async function(pdf){
return pdfdoc;
};
/**
* @param {Uint8Array} uarr
* @return {string}
*/
z.u8arrToRaw = function(uarr){
/** @type {Array<string>} */
var arr = [];
for(var i=0; i<uarr.length; i++){
arr.push(String.fromCharCode(uarr[i]));
}
return arr.join("");
};
/**
* @param {string} raw
* @return {Uint8Array}
*/
z.rawToU8arr = function(raw){
/** @type {Uint8Array} */
var arr = new Uint8Array(raw.length);
for(var i=0; i<raw.length; i++){
arr[i] = raw.charCodeAt(i);
}
return arr;
};
/**
* When converting to asn1, forge will encode the value of issuer to utf8 if the valueTagClass is UTF8.
* But the value load from a file which is DER format, is already utf8 encoded,
* so the encoding action will break the final data.
* To avoid the broken data issue, we decode the value before the later actions.
* @param {forge_cert} cert
*/
z.fixCertAttributes = function(cert){
cert.issuer.attributes.forEach(function(a_ele){
if(a_ele.valueTagClass === forge.asn1.Type.UTF8){
a_ele.value = forge.util.decodeUtf8(a_ele.value);
}
});
};
z.Crypto = {
/**
* @enum {number}
@ -953,9 +919,7 @@ z.PdfCryptor = class{
}else{
a_cerstr = z.u8arrToRaw(new Uint8Array(/** @type {Array<number>|ArrayBuffer|Uint8Array} */(a_pubkey.c)));
}
/** @type {forge.asn1} */
var a_asn1 = forge.asn1.fromDer(a_cerstr);
a_cert = forge.pki.certificateFromAsn1(a_asn1);
a_cert = z.loadCert(a_cerstr);
z.fixCertAttributes(a_cert);
}
}else{
@ -991,7 +955,6 @@ z.PdfCryptor = class{
}
if(!globalThis.Zga){
globalThis.Zga = {};
if(typeof exports === "object" && typeof module !== "undefined"){
module.exports = supplyZgaCryptor;
}
supplyZgaCryptor(globalThis.Zga);

View File

@ -1,37 +1,28 @@
'use strict';
/**
* @param {Object<string, *>} z
*/
function supplyZgaSigner(z){
//Only for nodejs Start//
if(z.forge){
var forge = z.forge;
}
if(z.PDFLib){
var PDFLib = z.PDFLib;
}
//Only for nodejs End//
/** @type {Object<string, TsaServiceInfo>} */
z.TSAURLS = {
"1": {url: "http://ts.ssl.com", len: 15600},
"2": {url: "http://timestamp.digicert.com", len: 15400},
"3": {url: "http://timestamp.sectigo.com", len: 13400},
"4": {url: "http://timestamp.entrust.net/TSS/RFC3161sha2TS", len: 14400},
"5": {url: "http://timestamp.apple.com/ts01", len: 12100},
"6": {url: "http://www.langedge.jp/tsa", len: 9200},
"7": {url: "https://freetsa.org/tsr", len: 14500},
"1": {url: "http://ts.ssl.com", len: 12100},
"2": {url: "http://timestamp.digicert.com", len: 11900},
"3": {url: "http://timestamp.sectigo.com", len: 9900},
"4": {url: "http://timestamp.entrust.net/TSS/RFC3161sha2TS", len: 10850},
"5": {url: "http://timestamp.apple.com/ts01", len: 8600},
"6": {url: "http://www.langedge.jp/tsa", len: 5700},
"7": {url: "https://freetsa.org/tsr", len: 11000},
};
// Google Apps Script
if(globalThis.UrlFetchApp){
/**
* @param {string} url
* @param {UrlFetchParams} params
* @return {Promise<Uint8Array>}
*/
z.urlFetch = function(url, params){
return new Promise(function(resolve){
/** @type {GBlob} */
var tblob = UrlFetchApp.fetch(url, params).getBlob();
resolve(new Uint8Array(tblob.getBytes()));
});
};
}
z.NewRef = class{
/**
* @param {PDFLib.PDFRef} ref
@ -222,6 +213,9 @@ z.PdfSigner = class{
* @param {SignOption} signopt
*/
constructor(signopt){
/** @public @type {Zga.TsaFetcher} */
this.tsaFetcher = null;
/** @private @const {string} */
this.DEFAULT_BYTE_RANGE_PLACEHOLDER = "**********";
/** @private @const {number} */
@ -230,27 +224,30 @@ z.PdfSigner = class{
this.opt = signopt;
/** @type {forge_key} */
this.privateKey = null;
/** @type {Array<forge_cert>} */
this.certs = [];
/** @type {number} */
this.certIdx = 0;
/** @type {Zga.CertsChain} */
this.cchain = null;
/** @private @type {?TsaServiceInfo} */
this.tsainf = null;
/** @private @type {string} */
this.signature = "";
/** @private @type {number} */
this.siglen = 0;
/** @private @type {PDFLib.PDFHexString} */
this.sigContents = null;
/** @private @type {Uint8Array} */
this.oriU8pdf = null;
/** @type {Array<PdfObjEntry>} */
this.apobjs = [];
/** @private @type {boolean} */
this.debug = false;
/** @private @type {Array<PdfObjEntry>} */
this.apobjs = null;
if(!globalThis.PDFLib){
if(typeof this.opt.debug == "boolean"){
z.debug = this.opt.debug;
}else if(globalThis.debug){
z.debug = true;
}
if(!(globalThis.PDFLib || PDFLib)){
throw new Error("pdf-lib is not imported.");
}
if(!globalThis.forge){
if(!(globalThis.forge || forge)){
throw new Error("node-forge is not imported.");
}
if(signopt.signdate){
@ -275,10 +272,12 @@ z.PdfSigner = class{
this.tsainf.len = 16000;
}
}
if(typeof this.opt.debug == "boolean"){
this.debug = this.opt.debug;
}else if(globalThis.debug){
this.debug = true;
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.");
}
}
@ -327,10 +326,20 @@ z.PdfSigner = class{
/** @type {forge_cert} */
var cert = _this.loadP12cert(_this.opt.p12cert, _this.opt.pwd);
/** @type {Zga.CertsChain} */
var cchain = null;
if(cert){
if(z.urlFetch){
cchain = new z.CertsChain();
/** @type {?boolean} */
var rootok = await cchain.buildChain(cert);
if(rootok){
_this.cchain = cchain;
}
}
z.fixCertAttributes(cert);
}else if(_this.tsainf){
_this.log("No certificate is specified, so only add a document timestamp.")
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.");
}
@ -338,25 +347,17 @@ z.PdfSigner = class{
/** @type {boolean} *///append mode or not
var apmode = _this.addSignHolder(pdfdoc);
await pdfdoc.flush();
_this.log("A signature holder has been added to the pdf.");
z.log("A signature holder has been added to the pdf.");
if(apmode){
if(_this.oriU8pdf){
_this.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.");
}else{
throw new Error("When adding a new signature to a signed pdf, the original literal datas are necessary.");
}
// Find the changed objects
/** @type {PDFLib.PDFDocument} */
var oriPdfdoc = await PDFLib.PDFDocument.load(_this.oriU8pdf);
pdfdoc.context.enumerateIndirectObjects().forEach(function(/** @type {PdfObjEntry} */a_ele){
/** @type {PDFLib.PDFObject} */
var a_obj = oriPdfdoc.context.lookup(a_ele[0]);
if(!(a_obj && _this.isamePdfObject(a_ele[1], a_obj))){
_this.apobjs.push(a_ele);
}
});
await _this.findChangedObjects(pdfdoc);
}else{
// If the definitions of references are too chaotic, a signature contains DocMDP or after adding a new signature,
@ -382,22 +383,17 @@ z.PdfSigner = class{
/** @type {Zga.PdfCryptor} */
var cypt = new z.PdfCryptor(cypopt);
await cypt.encryptPdf(pdfdoc, encref);
_this.log("Pdf data has been encrypted.");
z.log("Pdf data has been encrypted.");
}
}
/** @type {Uint8Array} */
var ret = await _this.saveAndSign(pdfdoc);
if(!ret){
_this.log("Change size of signature's placeholder and retry.");
_this.sigContents.value = "0".repeat(_this.siglen);
z.log("Change size of signature's placeholder and retry.");
_this.sigContents.value = "0".repeat(_this.siglen + 10);
ret = await _this.saveAndSign(pdfdoc);
}
if(ret){
_this.log("Signing pdf accomplished.");
}else{
throw new Error("Failed to sign the pdf.");
}
// Because PDFRefs in PDFLib are stored staticly,
// we need to restore all changed PDFRefs
@ -406,6 +402,30 @@ z.PdfSigner = class{
z.newRefs.restoreAll();
}
if(ret){
z.log("Pdf has been signed.");
}else{
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.");
}
}
}
return ret;
}
@ -417,8 +437,8 @@ z.PdfSigner = class{
async saveAndSign(pdfdoc){
/** @type {Uint8Array} */
var uarr = null;
if(this.apobjs.length > 0){
uarr = this.appendSignature(pdfdoc);
if(this.apobjs && this.apobjs.length > 0){
uarr = this.appendIncrement(pdfdoc);
}else{
uarr = await pdfdoc.save({"useObjectStreams": false});
}
@ -427,12 +447,36 @@ z.PdfSigner = class{
return await this.signPdf(pdfstr);
}
/**
* @private
* @param {PDFLib.PDFDocument} pdfdoc
* @param {boolean=} ignoreInfo
* @return {Promise}
*/
async findChangedObjects(pdfdoc, ignoreInfo){
/** @const {z.PdfSigner} */
const _this = this;
// Find the changed objects
/** @type {PDFLib.PDFDocument} */
var oriPdfdoc = await PDFLib.PDFDocument.load(_this.oriU8pdf, {ignoreEncryption: true});
_this.apobjs = [];
pdfdoc.context.enumerateIndirectObjects().forEach(function(/** @type {PdfObjEntry} */a_ele){
if(!(ignoreInfo && a_ele[0] == pdfdoc.context.trailerInfo.Info)){
/** @type {PDFLib.PDFObject} */
var a_obj = oriPdfdoc.context.lookup(a_ele[0]);
if(!(a_obj && _this.isamePdfObject(a_ele[1], a_obj))){
_this.apobjs.push(a_ele);
}
}
});
}
/**
* @private
* @param {PDFLib.PDFDocument} pdfdoc
* @return {Uint8Array}
*/
appendSignature(pdfdoc){
appendIncrement(pdfdoc){
/** @const {z.PdfSigner} */
const _this = this;
/** @type {PDFLib.PDFCrossRefSection} */
@ -476,7 +520,7 @@ z.PdfSigner = class{
*/
findPrev(u8pdf){
/** @const {Uint8Array} */
const eof = Zga.rawToU8arr("%%EOF");
const eof = z.rawToU8arr("%%EOF");
/** @const {number} */
const c0 = "0".charCodeAt(0);
/** @const {number} */
@ -571,6 +615,8 @@ z.PdfSigner = class{
* @return {forge_cert}
*/
loadP12cert(p12cert, pwd){
/** @const {z.PdfSigner} */
const _this = this;
// load P12 certificate
if(!p12cert){
return null;
@ -592,7 +638,12 @@ z.PdfSigner = class{
var keyBags = p12.getBags({
"bagType": forge.pki.oids.pkcs8ShroudedKeyBag,
})[forge.pki.oids.pkcs8ShroudedKeyBag];
this.privateKey = keyBags[0].key;
_this.privateKey = keyBags[0].key;
/** @type {Array<forge_cert>} */
var certs = [];
/** @type {number} */
var certIdx = -1;
if(certBags){
// Get all the certificates (-cacerts & -clcerts)
// Keep track of the last found client certificate.
@ -601,18 +652,22 @@ z.PdfSigner = class{
/** @type {forge_cert} */
var a_cert = certBags[a_ele].cert;
this.certs.push(a_cert);
certs.push(a_cert);
// Try to find the certificate that matches the private key.
if(this.privateKey.n.compareTo(a_cert.publicKey.n) === 0
&& this.privateKey.e.compareTo(a_cert.publicKey.e) === 0){
this.certIdx = this.certs.length;
if(_this.privateKey.n.compareTo(a_cert.publicKey.n) === 0
&& _this.privateKey.e.compareTo(a_cert.publicKey.e) === 0){
certIdx = certs.length;
}
}.bind(this));
});
}
if(this.certIdx > 0){
return this.certs[--this.certIdx];
// z.fixCertAttributes(this.certs[this.certIdx]);
if(certIdx > 0){
certIdx--;
_this.cchain = new z.CertsChain(certs);
if(_this.cchain.getSignCert() != certs[certIdx]){
throw new Error("Chain of certificates is invalid.");
}
return certs[certIdx];
}else{
throw new Error("Failed to find a certificate.");
}
@ -627,7 +682,7 @@ z.PdfSigner = class{
/** @const {z.PdfSigner} */
const _this = this;
/** @const {number} */
const docMdp = (_this.certs.length > 0 && _this.opt.permission >= 1 && _this.opt.permission <= 3) ? _this.opt.permission : 0;
const docMdp = (_this.cchain && _this.opt.permission >= 1 && _this.opt.permission <= 3) ? _this.opt.permission : 0;
/** @const {PDFLib.PDFContext} */
const pdfcont = pdfdoc.context;
/** @const {z.SignatureCreator} */
@ -692,7 +747,7 @@ z.PdfSigner = class{
}),
}),
};
if(_this.certs.length > 0){
if(_this.cchain){
signObj.M = PDFLib.PDFString.fromDate(signdate);
}else{
signObj.Type = "DocTimeStamp";
@ -748,7 +803,7 @@ z.PdfSigner = class{
var ans = page.node.Annots();
if(!ans){
ans = new PDFLib.PDFArray(pdfcont);
page.node.set(PDFLib.PDFName.Annots, ans);
page.node.set(PDFLib.PDFName.Annots, pdfcont.register(ans));
}
ans.push(widgetDictRef);
@ -823,13 +878,15 @@ z.PdfSigner = class{
* @return {Promise<Uint8Array>}
*/
async signPdf(pdfstr){
/** @const {z.PdfSigner} */
const _this = this;
// Finds ByteRange information within a given PDF Buffer if one exists
/** @type {Array<string>} */
var byteRangeStrings = pdfstr.match(/\/ByteRange\s*\[{1}\s*(?:(?:\d*|\/\*{10})\s+){3}(?:\d+|\/\*{10}){1}\s*]{1}/g);
/** @type {string|undefined} */
var byteRangePlaceholder = byteRangeStrings.find(function(a_str){
return a_str.includes("/"+this.DEFAULT_BYTE_RANGE_PLACEHOLDER);
}.bind(this));
var byteRangePlaceholder = byteRangeStrings.find(function(/** @type {string} */a_str){
return a_str.includes("/"+_this.DEFAULT_BYTE_RANGE_PLACEHOLDER);
});
if(!byteRangePlaceholder){
throw new Error("no signature placeholder");
}
@ -862,11 +919,11 @@ z.PdfSigner = class{
/** @type {forge.asn1} */
var asn1sig = null;
if(this.certs.length > 0){
if(_this.cchain){
/** @type {Date} */
var signdate = new Date();
if(this.opt.signdate instanceof Date && !this.tsainf){
signdate = this.opt.signdate;
if(_this.opt.signdate instanceof Date && !_this.tsainf){
signdate = _this.opt.signdate;
}
// Here comes the actual PKCS#7 signing.
@ -877,14 +934,14 @@ z.PdfSigner = class{
p7.content = forge.util.createBuffer(pdfstr);
// Add all the certificates (-cacerts & -clcerts) to p7
this.certs.forEach(function(a_cert){
_this.cchain.getAllCerts().forEach(function(/** @type {forge_cert} */a_cert){
p7.addCertificate(a_cert);
});
// Add a sha256 signer. That's what Adobe.PPKLite adbe.pkcs7.detached expects.
p7.addSigner({
key: this.privateKey,
certificate: this.certs[this.certIdx],
key: _this.privateKey,
certificate: _this.cchain.getSignCert(),
digestAlgorithm: forge.pki.oids.sha256,
authenticatedAttributes: [
{
@ -902,25 +959,28 @@ z.PdfSigner = class{
// Sign in detached mode.
p7.sign({"detached": true});
if(this.tsainf){
if(_this.tsainf){
/** @type {forge.asn1} */
var tsatoken = await this.queryTsa(p7.signers[0].signature);
var tsatoken = await _this.queryTsa(p7.signers[0].signature, true);
p7.signerInfos[0].value.push(tsatoken);
}
asn1sig = p7.toAsn1();
}else{
asn1sig = await this.queryTsa(pdfstr, true);
asn1sig = await _this.queryTsa(pdfstr);
}
// Check if the PDF has a good enough placeholder to fit the signature.
/** @type {forge.util.ByteStringBuffer} */
var sigbuf = forge.asn1.toDer(asn1sig);
/** @type {string} */
var sighex = forge.asn1.toDer(asn1sig).toHex();
var sighex = sigbuf.toHex();
_this.signature = sigbuf.getBytes();
// placeholderLength represents the length of the HEXified symbols but we're
// checking the actual lengths.
this.log("Size of signature is " + sighex.length + "/" + placeholderLength);
z.log("Size of signature is " + sighex.length + "/" + placeholderLength);
if(sighex.length > placeholderLength){
// throw new Error("Signature is too big. Needs: " + sighex.length);
this.siglen = sighex.length;
_this.siglen = sighex.length;
return null;
}else{
// Pad the signature with zeroes so the it is the same length as the placeholder
@ -935,137 +995,174 @@ z.PdfSigner = class{
/**
* @private
* @param {string=} data
* @param {boolean=} nocert
* @param {boolean=} forP7
* @return {Promise<forge.asn1>}
*/
async queryTsa(data, nocert){
/** @lends {forge.asn1} */
const asn1 = forge.asn1;
/** @lends {forge.asn1.Class} */
const asnc = asn1.Class;
/** @lends {forge.asn1.Type} */
const asnt = asn1.Type;
/**
* @param {string|number|boolean|forge.asn1|Array<forge.asn1>} aval
* @param {number=} atyp
* @param {number=} atag
* @return {forge.asn1}
*/
var asncreate = function(aval, atyp, atag){
/** @type {string|number|forge.asn1|Array<forge.asn1>} */
var a_val = null;
/** @type {number} */
var a_typ = (atyp || atyp === asnt.NONE) ? atyp : -1;
/** @type {boolean} */
var a_con = false;
if(Array.isArray(aval)){
a_con = true;
}else{
switch(typeof aval){
case "string":
if(a_typ == asnt.OID){
a_val = asn1.oidToDer(aval).getBytes();
}else if(a_typ < 0){
a_typ = asnt.OCTETSTRING;
}
break;
case "number":
a_val = asn1.integerToDer(aval).getBytes();
if(a_typ < 0){
a_typ = asnt.INTEGER;
}
break;
case "boolean":
if(aval){
a_val = 1;
}else{
a_val = 0;
}
if(a_typ < 0){
a_typ = asnt.BOOLEAN;
}
}
}
if(a_typ < 0){
a_typ = asnt.SEQUENCE;
}
if(!a_val && a_val !== 0){
a_val = /** @type {Array|forge.asn1|number|string} */(aval);
}
return asn1.create(atag ? atag : asnc.UNIVERSAL, a_typ, a_con, a_val);
};
// Generate SHA256 hash from data for TSA
/** @type {forge.md.digest} */
var md = forge.md.sha256.create();
md.update(data);
// Generate TSA request
/** @type {forge.asn1} */
var asn1Req = asncreate([
// Version
asncreate(1),
asncreate([
asncreate([
asncreate(forge.oids.sha256, asnt.OID),
asncreate("", asnt.NULL),
]),
// Message imprint
asncreate(md.digest().getBytes()),
]),
// Get REQ certificates
asncreate(true),
]);
/** @type {string} */
var tsr = asn1.toDer(asn1Req).getBytes();
/** @type {Uint8Array} */
var tu8s = z.rawToU8arr(tsr);
/** @type {Object<string, *>} */
var hds = this.tsainf.headers ? this.tsainf.headers : {};
if(!hds["Content-Type"]){
hds["Content-Type"] = "application/timestamp-query";
}
/** @type {UrlFetchParams} */
var options = {
"method": "POST",
"headers": hds,
"payload": tu8s,
};
/** @type {Uint8Array} */
var tesp = await z.urlFetch(this.tsainf.url, options);
/** @type {string} */
var tstr = z.u8arrToRaw(tesp);
/** @type {forge.asn1} */
var token = asn1.fromDer(tstr).value[1];
/** @type {forge.asn1} */
var ret = null;
if(nocert){
ret = token;
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){
throw new Error(err);
}else{
// create the asn1 to append to the signature
ret = asncreate([
asncreate([
// Attribute Type (forge.pki.oids.timeStampToken)
asncreate("1.2.840.113549.1.9.16.2.14", asnt.OID),
// Attribute Value
asncreate([token], asnt.SET),
]),
], 1, asnc.CONTEXT_SPECIFIC);
/** @type {forge.asn1} */
var asn1 = _this.tsaFetcher.getToken(forP7);
z.log("Timestamp from " + _this.tsainf.url + " has been obtained.");
return asn1;
}
this.log("Timestamp from " + this.tsainf.url + " has been obtained.");
return ret;
}
/**
* @public
* @param {PDFLib.PDFDocument} pdfdoc
* @param {Zga.CertsChain} chain
* @param {boolean=} crlOnly
* @return {Promise<Uint8Array>}
*/
async applyLTV(pdfdoc, chain, crlOnly){
/** @const {z.PdfSigner} */
const _this = this;
/** @type {?DSSInfo} */
var dssinf = await chain.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 {string} msg
* @param {PDFLib.PDFDocument} pdfdoc
* @param {string} sig signature
* @param {DSSInfo=} dssinf
* @return {boolean}
*/
log(msg){
if(this.debug){
console.log(msg);
addDss(pdfdoc, sig, dssinf){
/** @type {PDFLib.PDFContext} */
var pdfcont = pdfdoc.context;
/** @type {Array<PDFLib.PDFRef>} */
var certRefs = null;
/** @type {Array<PDFLib.PDFRef>} */
var ocspRefs = null;
/** @type {Array<PDFLib.PDFRef>} */
var crlRefs = null;
if(dssinf && dssinf.ocsps && dssinf.ocsps.length > 0){
ocspRefs = [];
dssinf.ocsps.forEach(function(/** @type {string|Uint8Array} */a_ocsp){
/** @type {PDFLib.PDFRawStream} */
var a_strmOcsp = pdfcont.flateStream(a_ocsp);
ocspRefs.push(pdfcont.register(a_strmOcsp));
});
}
if(dssinf && dssinf.crls && dssinf.crls.length > 0){
crlRefs = [];
dssinf.crls.forEach(function(/** @type {string|Uint8Array} */a_crl){
/** @type {PDFLib.PDFRawStream} */
var a_strmCrl = pdfcont.flateStream(a_crl);
crlRefs.push(pdfcont.register(a_strmCrl));
});
}
if(!(ocspRefs || crlRefs)){
// Nothing to do.
return false;
}
if(dssinf && dssinf.certs && dssinf.certs.length > 0){
certRefs = [];
dssinf.certs.forEach(function(/** @type {forge_cert} */a_cert){
/** @type {forge.asn1} */
var a_asn1Cert = forge.pki.certificateToAsn1(a_cert);
/** @type {PDFLib.PDFRawStream} */
var a_strmCert = pdfcont.flateStream(forge.asn1.toDer(a_asn1Cert).getBytes());
certRefs.push(pdfcont.register(a_strmCert));
});
}
/** @type {forge.md.digest} */
var md = forge.md.sha1.create();
md.update(sig);
/** @type {string} */
var sighex = md.digest().toHex().toUpperCase();
var dss = /** @type {PDFLib.PDFDict} */(pdfdoc.catalog.lookupMaybe(PDFLib.PDFName.of("DSS"), PDFLib.PDFDict));
/** @type {PDFLib.PDFArray} */
var certsarr = null;
/** @type {PDFLib.PDFArray} */
var ocpsarr = null;
/** @type {PDFLib.PDFArray} */
var crlsarr = null;
/** @type {PDFLib.PDFDict} */
var vri = null;
/** @type {Object<string, *>} */
var vriObj = {
TU: PDFLib.PDFString.fromDate(new Date()),
};
/** @type {PDFLib.PDFArray} */
var sigcertsarr = null;
/** @type {PDFLib.PDFArray} */
var sigocpsarr = null;
/** @type {PDFLib.PDFArray} */
var sigcrlsarr = null;
if(dss){
certsarr = /** @type {PDFLib.PDFArray} */(dss.lookupMaybe(PDFLib.PDFName.of("Certs"), PDFLib.PDFArray));
crlsarr = /** @type {PDFLib.PDFArray} */(dss.lookupMaybe(PDFLib.PDFName.of("CRLs"), PDFLib.PDFArray));
ocpsarr = /** @type {PDFLib.PDFArray} */(dss.lookupMaybe(PDFLib.PDFName.of("OCSPs"), PDFLib.PDFArray));
vri = /** @type {PDFLib.PDFDict} */(dss.lookupMaybe(PDFLib.PDFName.of("VRI"), PDFLib.PDFDict));
}else{
dss = /** @type {PDFLib.PDFDict} */(pdfcont.obj({}));
pdfdoc.catalog.set(PDFLib.PDFName.of("DSS"), pdfcont.register(dss));
}
if(certRefs){
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);
certsarr.push(a_ref);
});
}
if(ocspRefs){
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);
ocpsarr.push(a_ref);
});
}
if(crlRefs){
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);
crlsarr.push(a_ref);
});
}
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)));
return true;
}
};
@ -1309,7 +1406,8 @@ z.SignatureCreator = class{
}
if(!globalThis.Zga){
globalThis.Zga = {};
//Only for nodejs Start//
if(typeof exports === "object" && typeof module !== "undefined"){
module.exports = supplyZgaSigner;
}
supplyZgaSigner(globalThis.Zga);
//Only for nodejs End//

View File

@ -1,10 +1,32 @@
{
"name": "zgapdfsigner",
"version": "2.3.0",
"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,
"repository": {
"type": "git",
"url": "https://github.com/zboris12/zgapdfsigner"
},
"bugs": {
"url": "https://github.com/zboris12/zgapdfsigner/issues"
},
"license": "MIT",
"main": "lib/zganode.js",
"files": [
"lib/*.js"
],
"keywords": [
"pdf-sign",
"pdf-signature",
"pdf-protection",
"pdf-encryption",
"google-apps-script",
"LTV",
"TSA"
],
"scripts": {
"dev": "",
"build": ""
"testsign": "node test4node.js"
},
"dependencies": {
"pdf-lib": "1.17.1",

View File

@ -1,86 +1,18 @@
const m_fs = require("fs");
const m_path = require("path");
const m_urlparser = require("url");
const m_h = {
"http:": require("http"),
"https:": require("http"),
};
const Zga = require("./lib/zganode.js");
globalThis.PDFLib = require("pdf-lib");
globalThis.forge = require("node-forge");
require("./zgapdfcryptor.js");
require("./zgapdfsigner.js");
/**
* @param {string} url
* @param {UrlFetchParams} params
* @return {Promise<Uint8Array>}
*/
Zga.urlFetch = function(url, params){
return new Promise(function(resolve, reject){
/** @type {URL} */
var opts = m_urlparser.parse(url);
var http = m_h[opts.protocol];
/** @type {string|Buffer} */
var dat = null;
var encoding = undefined;
if(params.payload instanceof Buffer){
dat = params.payload;
}else if(params.payload instanceof Uint8Array){
dat = Buffer.from(params.payload.buffer);
}else if(params.payload instanceof ArrayBuffer){
dat = Buffer.from(params.payload);
}else{
dat = params.payload;
encoding = "binary";
}
if(params.headers){
opts.headers = params.headers;
}
if(params.method){
opts.method = params.method;
}
if(params.validateHttpsCertificates === false){
opts.rejectUnauthorized = false;
}
/** @type {http.ClientRequest} */
var hreq = http.request(opts, function(/** @type {http.IncomingMessage} */a_res){
if(a_res.statusCode !== 200){
var a_err = new Error("Failed to request url. " + url + "\n Status Code: " + a_res.statusCode);
a_res.resume();
throw a_err;
}
/** @type {Array<Buffer>} */
var a_bufs = [];
var a_bufs_len = 0;
a_res.on("data", function(/** @type {Buffer} */b_chunk){
a_bufs.push(b_chunk);
a_bufs_len += b_chunk.length;
});
a_res.on("end", function(){
/** @type {Buffer} */
var b_bdat = Buffer.concat(a_bufs, a_bufs_len);
resolve(b_bdat);
});
});
hreq.on("error", function(a_err){
throw a_err;
});
hreq.end(dat, encoding);
});
};
const workpath = "test/";
async function main(){
/** @type {string} */
var pdfPath = m_path.join(__dirname, "test/_test.pdf");
var pdfPath = m_path.join(__dirname, workpath+"_test.pdf");
/** @type {string} */
var pfxPath = m_path.join(__dirname, "test/_test.pfx");
var pfxPath = m_path.join(__dirname, workpath+"_test.pfx");
/** @type {string} */
var ps = "";
/** @type {string} */
var imgPath = m_path.join(__dirname, "test/_test.png");
var imgPath = m_path.join(__dirname, workpath+"_test.png");
if(process.argv.length > 3){
pfxPath = process.argv[2];
@ -116,11 +48,12 @@ async function main(){
sopt = {
p12cert: pfx,
pwd: ps,
permission: pfx ? 1 : 0,
// 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){
@ -157,11 +90,37 @@ async function main(){
if(u8dat){
/** @type {string} */
var outPath = m_path.join(__dirname, "test/test_test2.pdf");
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();