2022-09-18 09:49:56 +02:00
|
|
|
'use strict';
|
2022-09-17 14:55:09 +02:00
|
|
|
|
2022-10-02 14:45:06 +02:00
|
|
|
/**
|
|
|
|
* @param {Object<string, *>} z
|
|
|
|
*/
|
|
|
|
function supplyZgaSigner(z){
|
2022-09-18 09:49:56 +02:00
|
|
|
|
2022-09-24 06:59:30 +02:00
|
|
|
/** @type {Object<string, TsaServiceInfo>} */
|
2022-10-02 14:45:06 +02:00
|
|
|
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},
|
|
|
|
};
|
|
|
|
|
2022-10-14 14:12:43 +02:00
|
|
|
z.NewRef = class{
|
|
|
|
/**
|
|
|
|
* @param {PDFLib.PDFRef} ref
|
|
|
|
* @param {number=} num
|
|
|
|
* @param {string=} nm
|
|
|
|
*/
|
|
|
|
constructor(ref, num, nm){
|
|
|
|
/** @private @type {number} */
|
|
|
|
this.oriNumber = ref.objectNumber;
|
|
|
|
/** @private @type {number} */
|
|
|
|
this.oriGeneration = ref.generationNumber;
|
|
|
|
/** @private @type {string} */
|
|
|
|
this.name = nm ? nm : "";
|
|
|
|
/** @private @type {number} */
|
|
|
|
this.newNumber = num ? num : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
* @param {number} num
|
|
|
|
*/
|
|
|
|
setNewNumber(num){
|
|
|
|
this.newNumber = num;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
* @param {boolean=} restore
|
|
|
|
*/
|
|
|
|
changeNumber(restore){
|
|
|
|
if(!this.newNumber){
|
|
|
|
if(restore){
|
|
|
|
return;
|
|
|
|
}else{
|
|
|
|
throw new Error("Can NOT change number since new number is not set.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/** @type {PDFLib.PDFRef} */
|
|
|
|
var ref = PDFLib.PDFRef.of(this.oriNumber, this.oriGeneration);
|
|
|
|
ref.objectNumber = restore ? this.oriNumber : this.newNumber;
|
|
|
|
ref.tag = ref.objectNumber + " " + this.oriGeneration + " R";
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
* @return {string}
|
|
|
|
*/
|
|
|
|
toString(){
|
|
|
|
return this.name + " -> old:" + this.oriNumber + ", new:" + this.newNumber;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
z.NewRefMap = class extends Map{
|
|
|
|
|
|
|
|
constructor(){
|
|
|
|
super();
|
|
|
|
/** @private @type {number} */
|
|
|
|
this.idx = 0;
|
|
|
|
/** @private @type {PDFLib.PDFContext} */
|
|
|
|
this.pdfcont = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
* @param {PDFLib.PDFDocument} pdfdoc
|
|
|
|
* @param {boolean=} enc
|
|
|
|
* @return {PDFLib.PDFRef} If enc is true, the return value is the unique reference reserved for encrypting information.
|
|
|
|
*/
|
|
|
|
reorderPdfRefs(pdfdoc, enc){
|
|
|
|
this.pdfcont = pdfdoc.context;
|
|
|
|
/** @type {PDFLib.PDFRef} */
|
|
|
|
var encref = enc ? this.pdfcont.nextRef() : null;
|
|
|
|
|
|
|
|
pdfdoc.getPages().forEach(function(/** @type {PDFLib.PDFPage} */a_pg){
|
|
|
|
this.addAndFindRelates(a_pg.ref, "Page");
|
|
|
|
}.bind(this));
|
|
|
|
this.addAndFindRelates(this.pdfcont.trailerInfo.Root, "Catalog");
|
|
|
|
if(encref){
|
|
|
|
this.addAndFindRelates(encref, "Encrypt");
|
|
|
|
}
|
|
|
|
this.pdfcont.enumerateIndirectObjects().forEach(function(/** @type {PdfObjEntry} */a_oety){
|
|
|
|
/** @type {string} */
|
|
|
|
var a_tag = a_oety[0].tag;
|
|
|
|
/** @type {z.NewRef} */
|
|
|
|
var a_new = this.get(a_tag);
|
|
|
|
if(!a_new){
|
|
|
|
a_new = new z.NewRef(a_oety[0], ++this.idx);
|
|
|
|
this.set(a_tag, a_new);
|
|
|
|
}
|
|
|
|
}.bind(this));
|
2022-10-15 15:06:13 +02:00
|
|
|
this.changeAll();
|
2022-10-14 14:12:43 +02:00
|
|
|
return encref;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
*/
|
|
|
|
restoreAll(){
|
2022-10-15 15:06:13 +02:00
|
|
|
this.changeAll(true);
|
2022-10-14 14:12:43 +02:00
|
|
|
this.clear();
|
|
|
|
this.idx = 0;
|
|
|
|
this.pdfcont = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
* @param {PDFLib.PDFRef} a_ref
|
|
|
|
* @param {string=} a_nm
|
|
|
|
*/
|
|
|
|
addAndFindRelates(a_ref, a_nm){
|
|
|
|
if(!this.get(a_ref.tag)){
|
|
|
|
this.set(a_ref.tag, new z.NewRef(a_ref, ++this.idx, a_nm));
|
|
|
|
this.findRefs(this.pdfcont.lookup(a_ref), a_nm);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
* @param {PDFLib.PDFObject|Array|Map} a_val
|
|
|
|
* @param {string=} a_nm
|
|
|
|
*/
|
|
|
|
findRefs(a_val, a_nm){
|
|
|
|
if(!a_val || a_nm == "/Parent"){
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if(a_val instanceof PDFLib.PDFRef){
|
|
|
|
this.addAndFindRelates(a_val, a_nm);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if(a_val.array){
|
|
|
|
a_val = a_val.array;
|
|
|
|
}
|
|
|
|
if(Array.isArray(a_val)){
|
|
|
|
a_val.forEach(function(/** @type {PDFLib.PDFObject} */b_val){
|
|
|
|
this.findRefs(b_val, a_nm);
|
|
|
|
}.bind(this));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if(a_val instanceof PDFLib.PDFPage){
|
|
|
|
a_val = a_val.node;
|
|
|
|
}
|
|
|
|
while(a_val.dict && !(a_val instanceof Map)){
|
|
|
|
a_val = a_val.dict;
|
|
|
|
}
|
|
|
|
if(a_val instanceof Map){
|
|
|
|
/** @type {Iterator} */
|
|
|
|
var a_es = a_val.entries();
|
|
|
|
/** @type {IIterableResult<PdfObjEntry>} */
|
|
|
|
var a_result = a_es.next();
|
|
|
|
while(!a_result.done){
|
|
|
|
this.findRefs(a_result.value[1], a_result.value[0].encodedName);
|
|
|
|
a_result = a_es.next();
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2022-10-15 15:06:13 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
* @param {boolean=} restore
|
|
|
|
*/
|
|
|
|
changeAll(restore){
|
|
|
|
/** @type {Iterator} */
|
|
|
|
var es = this.entries();
|
|
|
|
/** @type {IIterableResult} */
|
|
|
|
var result = es.next();
|
|
|
|
while(!result.done){
|
|
|
|
result.value[1].changeNumber(restore);
|
|
|
|
result = es.next();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-14 14:12:43 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
/** @type {z.NewRefMap<string, z.NewRef>} */
|
|
|
|
z.newRefs = new z.NewRefMap();
|
|
|
|
|
2022-10-02 14:45:06 +02:00
|
|
|
z.PdfSigner = class{
|
2022-09-17 14:55:09 +02:00
|
|
|
/**
|
|
|
|
* @param {SignOption} signopt
|
|
|
|
*/
|
|
|
|
constructor(signopt){
|
2022-10-02 14:45:06 +02:00
|
|
|
/** @private @const {string} */
|
|
|
|
this.DEFAULT_BYTE_RANGE_PLACEHOLDER = "**********";
|
2022-09-17 14:55:09 +02:00
|
|
|
/** @private @type {SignOption} */
|
|
|
|
this.opt = signopt;
|
2022-10-06 15:02:11 +02:00
|
|
|
/** @type {forge_key} */
|
|
|
|
this.privateKey = null;
|
|
|
|
/** @type {Array<forge_cert>} */
|
|
|
|
this.certs = [];
|
|
|
|
/** @type {number} */
|
|
|
|
this.certIdx = 0;
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @private @type {?TsaServiceInfo} */
|
2022-09-24 06:59:30 +02:00
|
|
|
this.tsainf = null;
|
2022-10-02 14:45:06 +02:00
|
|
|
/** @private @type {number} */
|
|
|
|
this.siglen = 0;
|
|
|
|
/** @private @type {PDFLib.PDFHexString} */
|
|
|
|
this.sigContents = null;
|
2022-09-24 06:59:30 +02:00
|
|
|
/** @private @type {boolean} */
|
|
|
|
this.debug = false;
|
|
|
|
|
|
|
|
if(!globalThis.PDFLib){
|
|
|
|
throw new Error("pdf-lib is not imported.");
|
|
|
|
}
|
|
|
|
if(!globalThis.forge){
|
|
|
|
throw new Error("node-forge is not imported.");
|
|
|
|
}
|
|
|
|
if(signopt.signdate){
|
|
|
|
if(typeof signopt.signdate == "string"){
|
|
|
|
this.tsainf = {
|
|
|
|
url: signopt.signdate,
|
|
|
|
};
|
|
|
|
}else if(signopt.signdate.url){
|
2022-10-04 14:37:17 +02:00
|
|
|
this.tsainf = /** @type {TsaServiceInfo} */(Object.assign({}, signopt.signdate));
|
2022-09-24 06:59:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if(this.tsainf){
|
|
|
|
if(!globalThis.UrlFetchApp){
|
|
|
|
throw new Error("Because of the CORS security restrictions, signing with TSA is not supported in web browser.");
|
|
|
|
}
|
2022-10-02 14:45:06 +02:00
|
|
|
if(z.TSAURLS[this.tsainf.url]){
|
|
|
|
Object.assign(this.tsainf, z.TSAURLS[this.tsainf.url]);
|
2022-09-24 06:59:30 +02:00
|
|
|
}else if(!(new RegExp("^https?://")).test(this.tsainf.url)){
|
|
|
|
throw new Error("Unknown tsa data. " + JSON.stringify(this.tsainf));
|
|
|
|
}
|
|
|
|
if(!this.tsainf.len){
|
|
|
|
this.tsainf.len = 16000;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(typeof this.opt.debug == "boolean"){
|
|
|
|
this.debug = this.opt.debug;
|
|
|
|
}else if(globalThis.debug){
|
|
|
|
this.debug = true;
|
|
|
|
}
|
2022-09-17 14:55:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
2022-09-24 06:59:30 +02:00
|
|
|
* @param {PDFLib.PDFDocument|Array<number>|Uint8Array|ArrayBuffer|string} pdf
|
2022-10-02 14:45:06 +02:00
|
|
|
* @param {EncryptOption=} cypopt
|
2022-09-18 09:49:56 +02:00
|
|
|
* @return {Promise<Uint8Array>}
|
2022-09-17 14:55:09 +02:00
|
|
|
*/
|
2022-10-02 14:45:06 +02:00
|
|
|
async sign(pdf, cypopt){
|
|
|
|
if(cypopt && !z.PdfCryptor){
|
|
|
|
throw new Error("ZgaPdfCryptor is not imported.");
|
2022-09-17 14:55:09 +02:00
|
|
|
}
|
|
|
|
|
2022-10-02 14:45:06 +02:00
|
|
|
/** @type {PDFLib.PDFDocument} */
|
|
|
|
var pdfdoc = await z.loadPdf(pdf);
|
|
|
|
|
2022-09-17 14:55:09 +02:00
|
|
|
if(this.opt.drawinf && this.opt.drawinf.imgData && !this.opt.drawinf.img){
|
2022-09-24 06:59:30 +02:00
|
|
|
/** @type {Uint8Array|ArrayBuffer|string} */
|
|
|
|
var imgData2 = null;
|
|
|
|
if(Array.isArray(this.opt.drawinf.imgData)){
|
|
|
|
imgData2 = new Uint8Array(this.opt.drawinf.imgData);
|
|
|
|
}else{
|
|
|
|
imgData2 = this.opt.drawinf.imgData;
|
|
|
|
}
|
2022-09-17 14:55:09 +02:00
|
|
|
if(this.opt.drawinf.imgType == "png"){
|
2022-09-24 06:59:30 +02:00
|
|
|
this.opt.drawinf.img = await pdfdoc.embedPng(imgData2);
|
2022-09-17 14:55:09 +02:00
|
|
|
}else if(this.opt.drawinf.imgType == "jpg"){
|
2022-09-24 06:59:30 +02:00
|
|
|
this.opt.drawinf.img = await pdfdoc.embedJpg(imgData2);
|
2022-09-17 14:55:09 +02:00
|
|
|
}else{
|
|
|
|
throw new Error("Unkown image type. " + this.opt.drawinf.imgType);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-14 14:12:43 +02:00
|
|
|
/** @type {PDFLib.PDFRef} */
|
|
|
|
var encref = null;
|
|
|
|
if(this.addSignHolder(pdfdoc)){
|
|
|
|
// Signature in DocMDP mode may be invalid if the definitions of references are too chaotic
|
|
|
|
// So we make the order of references more neet.
|
|
|
|
await pdfdoc.flush();
|
|
|
|
encref = z.newRefs.reorderPdfRefs(pdfdoc, cypopt ? true : false);
|
|
|
|
}
|
2022-09-24 06:59:30 +02:00
|
|
|
this.log("A signature holder has been added to the pdf.");
|
|
|
|
|
2022-10-06 15:02:11 +02:00
|
|
|
/** @type {forge_cert} */
|
|
|
|
var cert = this.loadP12cert(this.opt.p12cert, this.opt.pwd);
|
|
|
|
if(cert){
|
|
|
|
z.fixCertAttributes(cert);
|
|
|
|
}
|
|
|
|
|
2022-10-02 14:45:06 +02:00
|
|
|
if(cypopt){
|
2022-10-06 15:02:11 +02:00
|
|
|
if(cypopt.pubkeys){
|
|
|
|
if(cypopt.pubkeys.length == 0){
|
|
|
|
cypopt.pubkeys.push({
|
|
|
|
c: cert,
|
|
|
|
});
|
|
|
|
}else{
|
|
|
|
cypopt.pubkeys.forEach(function(/** @type {PubKeyInfo} */a_pubkey){
|
|
|
|
// If there is no c in the PubKeyInfo, set cert to it.
|
|
|
|
if(!a_pubkey.c){
|
|
|
|
a_pubkey.c = cert;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2022-10-02 14:45:06 +02:00
|
|
|
/** @type {Zga.PdfCryptor} */
|
|
|
|
var cypt = new z.PdfCryptor(cypopt);
|
2022-10-14 14:12:43 +02:00
|
|
|
await cypt.encryptPdf(pdfdoc, encref);
|
2022-10-02 14:45:06 +02:00
|
|
|
this.log("Pdf data has been encrypted.");
|
|
|
|
}
|
|
|
|
|
2022-09-24 06:59:30 +02:00
|
|
|
/** @type {Uint8Array} */
|
2022-10-02 14:45:06 +02:00
|
|
|
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);
|
|
|
|
ret = await this.saveAndSign(pdfdoc);
|
|
|
|
}
|
|
|
|
if(ret){
|
|
|
|
this.log("Signing pdf accomplished.");
|
|
|
|
}else{
|
|
|
|
throw new Error("Failed to sign the pdf.");
|
|
|
|
}
|
|
|
|
|
2022-10-14 14:12:43 +02:00
|
|
|
// Because PDFRefs in PDFLib are stored staticly,
|
|
|
|
// we need to restore all changed PDFRefs
|
|
|
|
// for preparing the next execution.
|
|
|
|
if(z.newRefs.size > 0){
|
|
|
|
z.newRefs.restoreAll();
|
|
|
|
}
|
|
|
|
|
2022-09-24 06:59:30 +02:00
|
|
|
return ret;
|
2022-09-17 14:55:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
* @param {PDFLib.PDFDocument} pdfdoc
|
2022-10-02 14:45:06 +02:00
|
|
|
* @return {Promise<Uint8Array>}
|
2022-09-17 14:55:09 +02:00
|
|
|
*/
|
2022-10-02 14:45:06 +02:00
|
|
|
async saveAndSign(pdfdoc){
|
|
|
|
/** @type {Uint8Array} */
|
|
|
|
var uarr = await pdfdoc.save({"useObjectStreams": false});
|
|
|
|
/** @type {string} */
|
|
|
|
var pdfstr = z.u8arrToRaw(uarr);
|
|
|
|
return this.signPdf(pdfstr);
|
|
|
|
}
|
2022-09-17 14:55:09 +02:00
|
|
|
|
2022-10-02 14:45:06 +02:00
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
* @param {PDFLib.PDFDocument} pdfdoc
|
2022-10-14 14:12:43 +02:00
|
|
|
* @return {boolean} DocMDP mode or not.
|
2022-10-02 14:45:06 +02:00
|
|
|
*/
|
|
|
|
addSignHolder(pdfdoc){
|
2022-10-14 14:12:43 +02:00
|
|
|
/** @const {number} */
|
|
|
|
const docMdp = (this.opt.permission >= 1 && this.opt.permission <= 3) ? this.opt.permission : 0;
|
|
|
|
/** @const {PDFLib.PDFContext} */
|
|
|
|
const pdfcont = pdfdoc.context;
|
|
|
|
/** @const {z.SignatureCreator} */
|
|
|
|
const signcrt = new z.SignatureCreator(this.opt.drawinf);
|
2022-09-17 14:55:09 +02:00
|
|
|
/** @const {PDFLib.PDFPage} */
|
2022-10-14 14:12:43 +02:00
|
|
|
const page = pdfdoc.getPages()[signcrt.getPageIndex()];
|
|
|
|
/** @type {PDFLib.PDFRef} */
|
|
|
|
var strmRef = signcrt.createStream(pdfdoc, this.opt.signame);
|
|
|
|
|
|
|
|
if(docMdp && !strmRef){
|
|
|
|
strmRef = signcrt.createEmptyField(pdfcont);
|
|
|
|
}
|
2022-09-17 14:55:09 +02:00
|
|
|
|
2022-09-24 06:59:30 +02:00
|
|
|
/** @type {Date} */
|
|
|
|
var signdate = new Date();
|
|
|
|
if(this.opt.signdate instanceof Date && !this.tsainf){
|
|
|
|
signdate = this.opt.signdate;
|
2022-09-17 14:55:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/** @type {PDFLib.PDFArray} */
|
2022-10-14 14:12:43 +02:00
|
|
|
var bytrng = new PDFLib.PDFArray(pdfcont);
|
2022-09-17 14:55:09 +02:00
|
|
|
bytrng.push(PDFLib.PDFNumber.of(0));
|
2022-10-02 14:45:06 +02:00
|
|
|
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));
|
|
|
|
|
2022-10-04 14:37:17 +02:00
|
|
|
this.siglen = /** @type {number} */(this.tsainf ? this.tsainf.len : 3322);
|
2022-10-02 14:45:06 +02:00
|
|
|
this.sigContents = PDFLib.PDFHexString.of("0".repeat(this.siglen));
|
2022-09-17 14:55:09 +02:00
|
|
|
|
|
|
|
/** @type {Object<string, *>} */
|
|
|
|
var signObj = {
|
|
|
|
"Type": "Sig",
|
|
|
|
"Filter": "Adobe.PPKLite",
|
|
|
|
"SubFilter": "adbe.pkcs7.detached",
|
|
|
|
"ByteRange": bytrng,
|
2022-10-02 14:45:06 +02:00
|
|
|
"Contents": this.sigContents,
|
2022-09-24 06:59:30 +02:00
|
|
|
"M": PDFLib.PDFString.fromDate(signdate),
|
2022-10-14 14:12:43 +02:00
|
|
|
"Prop_Build": pdfcont.obj({
|
|
|
|
"App": pdfcont.obj({
|
2022-09-18 09:49:56 +02:00
|
|
|
"Name": "ZgaPdfSinger",
|
2022-09-17 14:55:09 +02:00
|
|
|
}),
|
|
|
|
}),
|
|
|
|
};
|
2022-10-14 14:12:43 +02:00
|
|
|
if(docMdp){
|
|
|
|
/** @type {PDFLib.PDFArray} */
|
|
|
|
var rfrc = new PDFLib.PDFArray(pdfcont);
|
|
|
|
rfrc.push(pdfcont.obj({
|
|
|
|
"Type": "SigRef",
|
|
|
|
"TransformMethod": "DocMDP",
|
|
|
|
"TransformParams": pdfcont.obj({
|
|
|
|
"Type": "TransformParams",
|
|
|
|
"P": docMdp,
|
|
|
|
"V": "1.2",
|
|
|
|
}),
|
|
|
|
}));
|
|
|
|
signObj["Reference"] = rfrc;
|
|
|
|
}
|
2022-09-17 14:55:09 +02:00
|
|
|
if(this.opt.reason){
|
2022-09-19 04:53:54 +02:00
|
|
|
signObj["Reason"] = this.convToPDFString(this.opt.reason);
|
2022-09-17 14:55:09 +02:00
|
|
|
}
|
|
|
|
if(this.opt.location){
|
2022-09-19 04:53:54 +02:00
|
|
|
signObj["Location"] = this.convToPDFString(this.opt.location);
|
2022-09-17 14:55:09 +02:00
|
|
|
}
|
|
|
|
if(this.opt.contact){
|
2022-09-19 04:53:54 +02:00
|
|
|
signObj["ContactInfo"] = this.convToPDFString(this.opt.contact);
|
2022-09-17 14:55:09 +02:00
|
|
|
}
|
2022-10-14 14:12:43 +02:00
|
|
|
/** @type {PDFLib.PDFRef} */
|
|
|
|
var signatureDictRef = pdfcont.register(pdfcont.obj(signObj));
|
2022-09-17 14:55:09 +02:00
|
|
|
|
|
|
|
/** @type {Object<string, *>} */
|
|
|
|
var widgetObj = {
|
|
|
|
"Type": "Annot",
|
|
|
|
"Subtype": "Widget",
|
|
|
|
"FT": "Sig",
|
2022-10-14 14:12:43 +02:00
|
|
|
"Rect": signcrt.getSignRect(),
|
2022-09-17 14:55:09 +02:00
|
|
|
"V": signatureDictRef,
|
2022-09-19 04:53:54 +02:00
|
|
|
"T": this.convToPDFString(this.opt.signame ? this.opt.signame : "Signature1"),
|
2022-09-17 14:55:09 +02:00
|
|
|
"F": 132,
|
|
|
|
"P": page.ref,
|
|
|
|
};
|
|
|
|
if(strmRef){
|
2022-10-14 14:12:43 +02:00
|
|
|
widgetObj["AP"] = pdfcont.obj({
|
2022-09-17 14:55:09 +02:00
|
|
|
"N": strmRef,
|
|
|
|
});
|
|
|
|
}
|
2022-10-14 14:12:43 +02:00
|
|
|
/** @type {PDFLib.PDFRef} */
|
|
|
|
var widgetDictRef = pdfcont.register(pdfcont.obj(widgetObj));
|
2022-09-17 14:55:09 +02:00
|
|
|
|
|
|
|
// Add our signature widget to the page
|
2022-10-14 14:12:43 +02:00
|
|
|
page.node.set(PDFLib.PDFName.of("Annots"), pdfcont.obj([widgetDictRef]));
|
2022-09-17 14:55:09 +02:00
|
|
|
|
|
|
|
// Create an AcroForm object containing our signature widget
|
|
|
|
pdfdoc.catalog.set(
|
|
|
|
PDFLib.PDFName.of("AcroForm"),
|
2022-10-14 14:12:43 +02:00
|
|
|
pdfcont.obj({
|
2022-09-17 14:55:09 +02:00
|
|
|
"SigFlags": 3,
|
|
|
|
"Fields": [widgetDictRef],
|
|
|
|
}),
|
|
|
|
);
|
2022-10-14 14:12:43 +02:00
|
|
|
if(docMdp){
|
|
|
|
pdfdoc.catalog.set(
|
|
|
|
PDFLib.PDFName.of("Perms"),
|
|
|
|
pdfcont.obj({
|
|
|
|
"DocMDP": signatureDictRef,
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
return true;
|
2022-09-17 14:55:09 +02:00
|
|
|
|
2022-10-14 14:12:43 +02:00
|
|
|
}else{
|
|
|
|
return false;
|
2022-10-02 14:45:06 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-06 15:02:11 +02:00
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
* @param {Array<number>|Uint8Array|ArrayBuffer|string} p12cert
|
|
|
|
* @param {string} pwd
|
|
|
|
* @return {forge_cert}
|
|
|
|
*/
|
|
|
|
loadP12cert(p12cert, pwd){
|
|
|
|
// load P12 certificate
|
|
|
|
if(typeof p12cert !== "string"){
|
|
|
|
p12cert = z.u8arrToRaw(new Uint8Array(p12cert));
|
|
|
|
}
|
|
|
|
// Convert Buffer P12 to a forge implementation.
|
|
|
|
/** @type {forge.asn1} */
|
|
|
|
var p12Asn1 = forge.asn1.fromDer(p12cert);
|
|
|
|
/** @type {forge.pkcs12} */
|
|
|
|
var p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, true, pwd);
|
|
|
|
// Extract safe bags by type.
|
|
|
|
// We will need all the certificates and the private key.
|
|
|
|
/** @type {Object<string|number, P12Bag>} */
|
|
|
|
var certBags = p12.getBags({
|
|
|
|
"bagType": forge.pki.oids.certBag,
|
|
|
|
})[forge.pki.oids.certBag];
|
|
|
|
/** @type {Object<string|number, P12Bag>} */
|
|
|
|
var keyBags = p12.getBags({
|
|
|
|
"bagType": forge.pki.oids.pkcs8ShroudedKeyBag,
|
|
|
|
})[forge.pki.oids.pkcs8ShroudedKeyBag];
|
|
|
|
this.privateKey = keyBags[0].key;
|
|
|
|
if(certBags){
|
|
|
|
// Get all the certificates (-cacerts & -clcerts)
|
|
|
|
// Keep track of the last found client certificate.
|
|
|
|
// This will be the public key that will be bundled in the signature.
|
|
|
|
Object.keys(certBags).forEach(function(a_ele){
|
|
|
|
/** @type {forge_cert} */
|
|
|
|
var a_cert = certBags[a_ele].cert;
|
|
|
|
|
|
|
|
this.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;
|
|
|
|
}
|
|
|
|
}.bind(this));
|
|
|
|
}
|
|
|
|
if(this.certIdx > 0){
|
|
|
|
return this.certs[--this.certIdx];
|
|
|
|
// z.fixCertAttributes(this.certs[this.certIdx]);
|
|
|
|
}else{
|
|
|
|
throw new Error("Failed to find a certificate.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-17 14:55:09 +02:00
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
* @param {string} pdfstr
|
2022-09-18 09:49:56 +02:00
|
|
|
* @return {Uint8Array}
|
2022-09-17 14:55:09 +02:00
|
|
|
*/
|
|
|
|
signPdf(pdfstr){
|
2022-09-24 06:59:30 +02:00
|
|
|
/** @type {Date} */
|
|
|
|
var signdate = new Date();
|
|
|
|
if(this.opt.signdate instanceof Date && !this.tsainf){
|
|
|
|
signdate = this.opt.signdate;
|
2022-09-17 14:55:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Finds ByteRange information within a given PDF Buffer if one exists
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {Array<string>} */
|
2022-09-17 14:55:09 +02:00
|
|
|
var byteRangeStrings = pdfstr.match(/\/ByteRange\s*\[{1}\s*(?:(?:\d*|\/\*{10})\s+){3}(?:\d+|\/\*{10}){1}\s*]{1}/g);
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {string|undefined} */
|
2022-09-17 14:55:09 +02:00
|
|
|
var byteRangePlaceholder = byteRangeStrings.find(function(a_str){
|
2022-10-02 14:45:06 +02:00
|
|
|
return a_str.includes("/"+this.DEFAULT_BYTE_RANGE_PLACEHOLDER);
|
|
|
|
}.bind(this));
|
2022-09-17 14:55:09 +02:00
|
|
|
if(!byteRangePlaceholder){
|
|
|
|
throw new Error("no signature placeholder");
|
|
|
|
}
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {number} */
|
2022-09-17 14:55:09 +02:00
|
|
|
var byteRangePos = pdfstr.indexOf(byteRangePlaceholder);
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {number} */
|
2022-09-17 14:55:09 +02:00
|
|
|
var byteRangeEnd = byteRangePos + byteRangePlaceholder.length;
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {number} */
|
2022-09-17 14:55:09 +02:00
|
|
|
var contentsTagPos = pdfstr.indexOf('/Contents ', byteRangeEnd);
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {number} */
|
2022-09-17 14:55:09 +02:00
|
|
|
var placeholderPos = pdfstr.indexOf('<', contentsTagPos);
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {number} */
|
2022-09-17 14:55:09 +02:00
|
|
|
var placeholderEnd = pdfstr.indexOf('>', placeholderPos);
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {number} */
|
2022-09-17 14:55:09 +02:00
|
|
|
var placeholderLengthWithBrackets = placeholderEnd + 1 - placeholderPos;
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {number} */
|
2022-09-17 14:55:09 +02:00
|
|
|
var placeholderLength = placeholderLengthWithBrackets - 2;
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {Array<number>} */
|
2022-09-17 14:55:09 +02:00
|
|
|
var byteRange = [0, 0, 0, 0];
|
|
|
|
byteRange[1] = placeholderPos;
|
|
|
|
byteRange[2] = byteRange[1] + placeholderLengthWithBrackets;
|
|
|
|
byteRange[3] = pdfstr.length - byteRange[2];
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {string} */
|
2022-09-17 14:55:09 +02:00
|
|
|
var actualByteRange = "/ByteRange [" + byteRange.join(" ") +"]";
|
|
|
|
actualByteRange += ' '.repeat(byteRangePlaceholder.length - actualByteRange.length);
|
|
|
|
// Replace the /ByteRange placeholder with the actual ByteRange
|
|
|
|
pdfstr = pdfstr.slice(0, byteRangePos) + actualByteRange + pdfstr.slice(byteRangeEnd);
|
|
|
|
// Remove the placeholder signature
|
|
|
|
pdfstr = pdfstr.slice(0, byteRange[1]) + pdfstr.slice(byteRange[2], byteRange[2] + byteRange[3]);
|
|
|
|
|
|
|
|
// Here comes the actual PKCS#7 signing.
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {forge.pkcs7} */
|
2022-09-17 14:55:09 +02:00
|
|
|
var p7 = forge.pkcs7.createSignedData();
|
|
|
|
// Start off by setting the content.
|
|
|
|
p7.content = forge.util.createBuffer(pdfstr);
|
|
|
|
|
2022-10-06 15:02:11 +02:00
|
|
|
// Add all the certificates (-cacerts & -clcerts) to p7
|
|
|
|
this.certs.forEach(function(a_cert){
|
|
|
|
p7.addCertificate(a_cert);
|
|
|
|
});
|
2022-09-17 14:55:09 +02:00
|
|
|
|
|
|
|
// Add a sha256 signer. That's what Adobe.PPKLite adbe.pkcs7.detached expects.
|
|
|
|
p7.addSigner({
|
2022-10-06 15:02:11 +02:00
|
|
|
key: this.privateKey,
|
|
|
|
certificate: this.certs[this.certIdx],
|
2022-09-17 14:55:09 +02:00
|
|
|
digestAlgorithm: forge.pki.oids.sha256,
|
|
|
|
authenticatedAttributes: [
|
|
|
|
{
|
|
|
|
"type": forge.pki.oids.contentType,
|
|
|
|
"value": forge.pki.oids.data,
|
|
|
|
}, {
|
|
|
|
"type": forge.pki.oids.messageDigest,
|
|
|
|
}, {
|
|
|
|
"type": forge.pki.oids.signingTime,
|
2022-09-24 06:59:30 +02:00
|
|
|
"value": signdate,
|
2022-09-17 14:55:09 +02:00
|
|
|
},
|
|
|
|
],
|
|
|
|
});
|
|
|
|
|
|
|
|
// Sign in detached mode.
|
|
|
|
p7.sign({"detached": true});
|
2022-09-24 06:59:30 +02:00
|
|
|
|
|
|
|
if(this.tsainf){
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {forge.asn1} */
|
2022-09-24 06:59:30 +02:00
|
|
|
var tsatoken = this.queryTsa(p7.signers[0].signature);
|
2022-10-14 14:12:43 +02:00
|
|
|
p7.signerInfos[0].value.push(tsatoken);
|
2022-09-24 06:59:30 +02:00
|
|
|
this.log("Timestamp from " + this.tsainf.url + " has been added to the signature.");
|
|
|
|
}
|
|
|
|
|
2022-09-17 14:55:09 +02:00
|
|
|
// Check if the PDF has a good enough placeholder to fit the signature.
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {string} */
|
2022-09-17 14:55:09 +02:00
|
|
|
var sighex = forge.asn1.toDer(p7.toAsn1()).toHex();
|
|
|
|
// placeholderLength represents the length of the HEXified symbols but we're
|
|
|
|
// checking the actual lengths.
|
2022-09-24 06:59:30 +02:00
|
|
|
this.log("Size of signature is " + sighex.length + "/" + placeholderLength);
|
2022-09-17 14:55:09 +02:00
|
|
|
if(sighex.length > placeholderLength){
|
2022-10-02 14:45:06 +02:00
|
|
|
// throw new Error("Signature is too big. Needs: " + sighex.length);
|
|
|
|
this.siglen = sighex.length;
|
|
|
|
return null;
|
2022-09-17 14:55:09 +02:00
|
|
|
}else{
|
|
|
|
// Pad the signature with zeroes so the it is the same length as the placeholder
|
|
|
|
sighex += "0".repeat(placeholderLength - sighex.length);
|
|
|
|
}
|
|
|
|
// Place it in the document.
|
|
|
|
pdfstr = pdfstr.slice(0, byteRange[1]) + "<" + sighex + ">" + pdfstr.slice(byteRange[1]);
|
|
|
|
|
2022-10-02 14:45:06 +02:00
|
|
|
return z.rawToU8arr(pdfstr);
|
2022-09-17 14:55:09 +02:00
|
|
|
}
|
2022-09-19 04:53:54 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
* @param {string} str
|
|
|
|
* @return {PDFLib.PDFString|PDFLib.PDFHexString}
|
|
|
|
*/
|
|
|
|
convToPDFString(str){
|
|
|
|
// Check if there is a multi-bytes char in the string.
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {boolean} */
|
2022-09-19 04:53:54 +02:00
|
|
|
var flg = false;
|
|
|
|
for(var i=0; i<str.length; i++){
|
|
|
|
if(str.charCodeAt(i) > 0xFF){
|
|
|
|
flg = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(flg){
|
|
|
|
return PDFLib.PDFHexString.fromText(str);
|
|
|
|
}else{
|
|
|
|
return PDFLib.PDFString.of(str);
|
|
|
|
}
|
|
|
|
}
|
2022-09-24 06:59:30 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @private
|
2022-10-04 14:37:17 +02:00
|
|
|
* @param {string=} signature
|
2022-09-24 06:59:30 +02:00
|
|
|
* @return {string}
|
|
|
|
*/
|
|
|
|
genTsrData(signature){
|
|
|
|
// Generate SHA256 hash from signature content for TSA
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {forge.md.digest} */
|
2022-09-24 06:59:30 +02:00
|
|
|
var md = forge.md.sha256.create();
|
|
|
|
md.update(signature);
|
|
|
|
// Generate TSA request
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {forge.asn1} */
|
2022-09-24 06:59:30 +02:00
|
|
|
var asn1Req = forge.asn1.create(
|
|
|
|
forge.asn1.Class.UNIVERSAL,
|
|
|
|
forge.asn1.Type.SEQUENCE,
|
|
|
|
true,
|
|
|
|
[
|
|
|
|
// Version
|
|
|
|
{
|
|
|
|
composed: false,
|
|
|
|
constructed: false,
|
|
|
|
tagClass: forge.asn1.Class.UNIVERSAL,
|
|
|
|
type: forge.asn1.Type.INTEGER,
|
|
|
|
value: forge.asn1.integerToDer(1).data,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
composed: true,
|
|
|
|
constructed: true,
|
|
|
|
tagClass: forge.asn1.Class.UNIVERSAL,
|
|
|
|
type: forge.asn1.Type.SEQUENCE,
|
|
|
|
value: [
|
|
|
|
{
|
|
|
|
composed: true,
|
|
|
|
constructed: true,
|
|
|
|
tagClass: forge.asn1.Class.UNIVERSAL,
|
|
|
|
type: forge.asn1.Type.SEQUENCE,
|
|
|
|
value: [
|
|
|
|
{
|
|
|
|
composed: false,
|
|
|
|
constructed: false,
|
|
|
|
tagClass: forge.asn1.Class.UNIVERSAL,
|
|
|
|
type: forge.asn1.Type.OID,
|
|
|
|
value: forge.asn1.oidToDer(forge.oids.sha256).data,
|
|
|
|
}, {
|
|
|
|
composed: false,
|
|
|
|
constructed: false,
|
|
|
|
tagClass: forge.asn1.Class.UNIVERSAL,
|
|
|
|
type: forge.asn1.Type.NULL,
|
|
|
|
value: ""
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}, {// Message imprint
|
|
|
|
composed: false,
|
|
|
|
constructed: false,
|
|
|
|
tagClass: forge.asn1.Class.UNIVERSAL,
|
|
|
|
type: forge.asn1.Type.OCTETSTRING,
|
|
|
|
value: md.digest().data,
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}, {
|
|
|
|
composed: false,
|
|
|
|
constructed: false,
|
|
|
|
tagClass: forge.asn1.Class.UNIVERSAL,
|
|
|
|
type: forge.asn1.Type.BOOLEAN,
|
|
|
|
value: 1, // Get REQ certificates
|
|
|
|
}
|
|
|
|
]
|
|
|
|
);
|
|
|
|
|
|
|
|
return forge.asn1.toDer(asn1Req).data;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @private
|
2022-10-04 14:37:17 +02:00
|
|
|
* @param {string=} signature
|
|
|
|
* @return {forge.asn1}
|
2022-09-24 06:59:30 +02:00
|
|
|
*/
|
|
|
|
queryTsa(signature){
|
2022-10-14 14:12:43 +02:00
|
|
|
/** @lends {forge.asn1} */
|
|
|
|
var asn1 = forge.asn1;
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {string} */
|
2022-09-24 06:59:30 +02:00
|
|
|
var tsr = this.genTsrData(signature);
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {Uint8Array} */
|
2022-10-02 14:45:06 +02:00
|
|
|
var tu8s = z.rawToU8arr(tsr);
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {UrlFetchParams} */
|
2022-09-24 06:59:30 +02:00
|
|
|
var options = {
|
|
|
|
"method": "POST",
|
|
|
|
"headers": {"Content-Type": "application/timestamp-query"},
|
|
|
|
"payload": tu8s,
|
|
|
|
};
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {GBlob} */
|
2022-09-24 06:59:30 +02:00
|
|
|
var tblob = UrlFetchApp.fetch(this.tsainf.url, options).getBlob();
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {string} */
|
2022-10-02 14:45:06 +02:00
|
|
|
var tstr = z.u8arrToRaw(new Uint8Array(tblob.getBytes()));
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {forge.asn1} */
|
2022-10-14 14:12:43 +02:00
|
|
|
var token = asn1.fromDer(tstr).value[1];
|
|
|
|
|
|
|
|
// create the asn1 to append to the signature
|
|
|
|
/** @type {string} *///forge.pki.oids.timeStampToken
|
|
|
|
var typstr = asn1.oidToDer("1.2.840.113549.1.9.16.2.14").getBytes();
|
|
|
|
return asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, [
|
|
|
|
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
|
|
|
// Attribute Type
|
|
|
|
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, typstr),
|
|
|
|
// Attribute Value
|
|
|
|
asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [token]),
|
|
|
|
]),
|
|
|
|
]);
|
2022-09-24 06:59:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
* @param {string} msg
|
|
|
|
*/
|
|
|
|
log(msg){
|
|
|
|
if(this.debug){
|
|
|
|
console.log(msg);
|
|
|
|
}
|
|
|
|
}
|
2022-10-02 14:45:06 +02:00
|
|
|
};
|
2022-09-17 14:55:09 +02:00
|
|
|
|
2022-10-14 14:12:43 +02:00
|
|
|
z.SignatureCreator = class{
|
2022-09-17 14:55:09 +02:00
|
|
|
/**
|
|
|
|
* @param {SignDrawInfo=} drawinf
|
|
|
|
*/
|
|
|
|
constructor(drawinf){
|
|
|
|
/** @private @type {number} */
|
|
|
|
this.pgidx = 0;
|
|
|
|
/** @private @type {Array<number>} */
|
|
|
|
this.rect = [0, 0, 0, 0];
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @private @type {?SignDrawInfo} */
|
2022-09-17 14:55:09 +02:00
|
|
|
this.drawinf = null;
|
|
|
|
|
|
|
|
if(drawinf){
|
|
|
|
this.drawinf = drawinf;
|
|
|
|
if(this.drawinf.pageidx){
|
|
|
|
this.pgidx = this.drawinf.pageidx;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
* @return {number}
|
|
|
|
*/
|
|
|
|
getPageIndex(){
|
|
|
|
return this.pgidx;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
* @return {Array<number>}
|
|
|
|
*/
|
|
|
|
getSignRect(){
|
|
|
|
return this.rect;
|
|
|
|
}
|
|
|
|
|
2022-10-14 14:12:43 +02:00
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
* @param {PDFLib.PDFContext} pdfcont
|
|
|
|
* @return {PDFLib.PDFRef}
|
|
|
|
*/
|
|
|
|
createEmptyField(pdfcont){
|
|
|
|
return pdfcont.register(pdfcont.obj({
|
|
|
|
"Type": "XObject",
|
|
|
|
"Subtype": "Form",
|
|
|
|
"FormType": 1,
|
|
|
|
"BBox": [0, 0, 0, 0],
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2022-09-17 14:55:09 +02:00
|
|
|
/**
|
|
|
|
* @public
|
|
|
|
* @param {PDFLib.PDFDocument} pdfdoc
|
|
|
|
* @param {string=} signame
|
|
|
|
* @return {PDFLib.PDFRef} The unique reference assigned to the signature stream
|
|
|
|
*/
|
|
|
|
createStream(pdfdoc, signame){
|
|
|
|
if(!this.drawinf){
|
|
|
|
return null;
|
|
|
|
}else if(!(this.drawinf.img || (this.drawinf.font && this.drawinf.font))){
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @type {Array<PDFLib.PDFPage>} */
|
|
|
|
var pages = pdfdoc.getPages();
|
|
|
|
/** @type {PDFLib.PDFPage} */
|
|
|
|
var page = null;
|
|
|
|
if(this.pgidx < pages.length){
|
|
|
|
page = pages[this.pgidx];
|
|
|
|
}else{
|
|
|
|
throw new Error("Page index is overflow to pdf pages.");
|
|
|
|
}
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {PDFLib.Rotation} */
|
2022-09-17 14:55:09 +02:00
|
|
|
var pgrot = page.getRotation();
|
|
|
|
pgrot.angle = PDFLib.toDegrees(pgrot) % 360;
|
|
|
|
pgrot.type = PDFLib.RotationTypes.Degrees;
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {PdfSize} */
|
2022-09-17 14:55:09 +02:00
|
|
|
var pgsz = page.getSize();
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {SignAreaInfo} */
|
2022-09-17 14:55:09 +02:00
|
|
|
var areainf = this.calcAreaInf(pgsz, pgrot.angle, this.drawinf.area);
|
|
|
|
|
|
|
|
// resources object
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {Object<string, *>} */
|
2022-09-17 14:55:09 +02:00
|
|
|
var rscObj = {};
|
|
|
|
/** @type {Array<PDFLib.PDFOperator>} */
|
|
|
|
var sigOprs = [];
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {string} */
|
2022-09-17 14:55:09 +02:00
|
|
|
var imgName = signame ? signame.concat("Img") : "SigImg";
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {string} */
|
2022-09-17 14:55:09 +02:00
|
|
|
var fontName = signame ? signame.concat("Font") : "SigFont";
|
|
|
|
if(this.drawinf.img){
|
|
|
|
// Get scaled image size
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {PdfSize} */
|
2022-09-17 14:55:09 +02:00
|
|
|
var imgsz = this.drawinf.img.size();
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {number} */
|
2022-09-17 14:55:09 +02:00
|
|
|
var tmp = areainf.w * imgsz.height / imgsz.width;
|
|
|
|
if(tmp <= areainf.h){
|
|
|
|
areainf.h = tmp;
|
|
|
|
}else{
|
|
|
|
areainf.w = areainf.h * imgsz.width / imgsz.height;
|
|
|
|
}
|
|
|
|
|
|
|
|
rscObj["XObject"] = {
|
|
|
|
[imgName]: this.drawinf.img.ref,
|
|
|
|
};
|
|
|
|
sigOprs = sigOprs.concat(PDFLib.drawImage(imgName, this.calcDrawImgInf(pgrot, areainf)));
|
|
|
|
}
|
|
|
|
if(this.drawinf.font){
|
|
|
|
rscObj["Font"] = {
|
|
|
|
[fontName]: this.drawinf.font.ref,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
this.rect = this.calcRect(pgrot.angle, areainf);
|
|
|
|
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {PDFLib.PDFObject} */
|
2022-09-17 14:55:09 +02:00
|
|
|
var frmDict = pdfdoc.context.obj({
|
|
|
|
"Type": "XObject",
|
|
|
|
"Subtype": "Form",
|
|
|
|
"FormType": 1,
|
|
|
|
"BBox": [0, 0, areainf.w, areainf.h],
|
|
|
|
"Resources": rscObj,
|
|
|
|
});
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {PDFLib.PDFContentStream} */
|
2022-10-14 14:12:43 +02:00
|
|
|
var strm = PDFLib.PDFContentStream.of(frmDict, sigOprs, true);
|
2022-09-17 14:55:09 +02:00
|
|
|
return pdfdoc.context.register(strm);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calculate area informations for drawing signature after rotate
|
|
|
|
*
|
|
|
|
* @private
|
2022-10-04 14:37:17 +02:00
|
|
|
* @param {PdfSize} pgsz
|
2022-09-17 14:55:09 +02:00
|
|
|
* @param {number} angle
|
|
|
|
* @param {SignAreaInfo} visinf
|
|
|
|
* @return {SignAreaInfo}
|
|
|
|
*/
|
|
|
|
calcAreaInf(pgsz, angle, visinf){
|
2022-10-04 14:37:17 +02:00
|
|
|
var ret = /** @type {SignAreaInfo} */(Object.assign({}, visinf));
|
2022-09-17 14:55:09 +02:00
|
|
|
// Calculate position after rotate
|
|
|
|
switch(angle){
|
|
|
|
case 90:
|
|
|
|
ret.w = visinf.h;
|
|
|
|
ret.h = visinf.w;
|
2022-10-01 10:20:53 +02:00
|
|
|
ret.x = visinf.y + visinf.h;
|
2022-09-17 14:55:09 +02:00
|
|
|
ret.y = visinf.x;
|
|
|
|
break;
|
|
|
|
case 180:
|
|
|
|
case -180:
|
|
|
|
ret.x = pgsz.width - visinf.x;
|
2022-10-01 10:20:53 +02:00
|
|
|
ret.y = visinf.y + visinf.h;
|
2022-09-17 14:55:09 +02:00
|
|
|
break;
|
|
|
|
case 270:
|
|
|
|
case -90:
|
|
|
|
ret.w = visinf.h;
|
|
|
|
ret.h = visinf.w;
|
2022-10-01 10:20:53 +02:00
|
|
|
ret.x = pgsz.width - visinf.y - visinf.h;
|
2022-09-17 14:55:09 +02:00
|
|
|
ret.y = pgsz.height - visinf.x;
|
|
|
|
break;
|
|
|
|
default:
|
2022-10-01 10:20:53 +02:00
|
|
|
ret.y = pgsz.height - visinf.y - visinf.h;
|
2022-09-17 14:55:09 +02:00
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
* @param {number} angle
|
|
|
|
* @param {SignAreaInfo} areainf // { x, y, w, h }
|
|
|
|
* @return {Array<number>}
|
|
|
|
*/
|
|
|
|
calcRect(angle, areainf){
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {Array<number>} */
|
2022-09-17 14:55:09 +02:00
|
|
|
var rect = [0, 0, 0, 0];
|
|
|
|
rect[0] = areainf.x;
|
|
|
|
rect[1] = areainf.y;
|
|
|
|
switch(angle){
|
|
|
|
case 90:
|
|
|
|
rect[2] = areainf.x - areainf.h;
|
|
|
|
rect[3] = areainf.y + areainf.w;
|
|
|
|
break;
|
|
|
|
case 180:
|
|
|
|
case -180:
|
|
|
|
rect[2] = areainf.x - areainf.w;
|
|
|
|
rect[3] = areainf.y - areainf.h;
|
|
|
|
break;
|
|
|
|
case 270:
|
|
|
|
case -90:
|
|
|
|
rect[2] = areainf.x + areainf.h;
|
|
|
|
rect[3] = areainf.y - areainf.w;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
rect[2] = areainf.x + areainf.w;
|
|
|
|
rect[3] = areainf.y + areainf.h;
|
|
|
|
}
|
|
|
|
return rect;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calculate informations for drawing image after rotate
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @param {PDFLib.Rotation} rot
|
|
|
|
* @param {SignAreaInfo} areainf // { x, y, w, h }
|
2022-10-04 14:37:17 +02:00
|
|
|
* @return {PdfDrawimgOption}
|
2022-09-17 14:55:09 +02:00
|
|
|
*/
|
|
|
|
calcDrawImgInf(rot, areainf){
|
2022-10-04 14:37:17 +02:00
|
|
|
/** @type {PdfDrawimgOption} */
|
2022-09-17 14:55:09 +02:00
|
|
|
var ret = {
|
|
|
|
"x": 0,
|
|
|
|
"y": 0,
|
|
|
|
"width": areainf.w,
|
|
|
|
"height": areainf.h,
|
|
|
|
"rotate": rot,
|
|
|
|
"xSkew": PDFLib.degrees(0),
|
|
|
|
"ySkew": PDFLib.degrees(0),
|
|
|
|
};
|
|
|
|
switch(rot.angle){
|
|
|
|
case 90:
|
|
|
|
ret["x"] = areainf.w;
|
|
|
|
ret["width"] = areainf.h;
|
|
|
|
ret["height"] = areainf.w;
|
|
|
|
break;
|
|
|
|
case 180:
|
|
|
|
case -180:
|
|
|
|
ret["x"] = areainf.w;
|
|
|
|
ret["y"] = areainf.h;
|
|
|
|
break;
|
|
|
|
case 270:
|
|
|
|
case -90:
|
|
|
|
ret["y"] = areainf.h;
|
|
|
|
ret["width"] = areainf.h;
|
|
|
|
ret["height"] = areainf.w;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
2022-10-02 14:45:06 +02:00
|
|
|
};
|
2022-09-18 09:49:56 +02:00
|
|
|
|
2022-10-02 14:45:06 +02:00
|
|
|
}
|
2022-09-18 09:49:56 +02:00
|
|
|
|
2022-10-02 14:45:06 +02:00
|
|
|
if(!globalThis.Zga){
|
|
|
|
globalThis.Zga = {};
|
|
|
|
}
|
|
|
|
supplyZgaSigner(globalThis.Zga);
|