Added functions of setting protection to a pdf.
parent
8fbedd2c8a
commit
8c2a614a08
81
README.md
81
README.md
|
@ -1,5 +1,5 @@
|
|||
# ZgaPdfSigner
|
||||
A javascript tool to sign a pdf in web browser.
|
||||
A javascript tool to sign a pdf or set protection of a pdf in web browser.
|
||||
And it also can be used in Google Apps Script.
|
||||
|
||||
PS: __ZGA__ is the abbreviation of my father's name.
|
||||
|
@ -156,6 +156,85 @@ fld.createFile(Utilities.newBlob(u8arr, "application/pdf").setName("signed_test.
|
|||
* __text__: string :point_right: (Optional) A text drawing on signature, __not implemented yet__
|
||||
* __fontData__: PDFLib.StandardFonts|Array<number>|Uint8Array|ArrayBuffer|string :point_right: (Optional) The font's data for drawing text, __not implemented yet__
|
||||
|
||||
## Let's protect the pdf
|
||||
|
||||
Set protection to the pdf.
|
||||
|
||||
```js
|
||||
/**
|
||||
* @param {ArrayBuffer} pdf
|
||||
* @param {string} upwd
|
||||
* @param {string} opwd
|
||||
* @return {Promise<Blob>}
|
||||
*/
|
||||
async function protect1(pdf, upwd, opwd){
|
||||
/** @type {EncryptOption} */
|
||||
var eopt = {
|
||||
mode: Zga.Crypto.Mode.AES_256,
|
||||
permissions: ["modify", "annot-forms", "fill-forms", "extract", "assemble"],
|
||||
userpwd: upwd,
|
||||
ownerpwd: opwd,
|
||||
};
|
||||
var cyptor = new Zga.PdfCryptor(eopt);
|
||||
var pdfdoc = await cyptor.encryptPdf(pdf);
|
||||
u8arr = await pdfdoc.save({"useObjectStreams": false});
|
||||
return new Blob([u8arr], {"type" : "application/pdf"});
|
||||
}
|
||||
```
|
||||
|
||||
Sign and set protection.
|
||||
|
||||
```js
|
||||
/**
|
||||
* @param {ArrayBuffer} pdf
|
||||
* @param {ArrayBuffer} cert
|
||||
* @param {string} pwd
|
||||
* @param {string} opwd
|
||||
* @return {Promise<Blob>}
|
||||
*/
|
||||
async function sign1(pdf, cert, pwd, opwd){
|
||||
/** @type {SignOption} */
|
||||
var sopt = {
|
||||
p12cert: cert,
|
||||
pwd: pwd,
|
||||
};
|
||||
/** @type {EncryptOption} */
|
||||
var eopt = {
|
||||
mode: Zga.Crypto.Mode.RC4_128,
|
||||
permissions: ["modify", "annot-forms", "fill-forms", "extract", "assemble"],
|
||||
ownerpwd: opwd,
|
||||
};
|
||||
var signer = new Zga.PdfSigner(sopt);
|
||||
var u8arr = await signer.sign(pdf, eopt);
|
||||
return new Blob([u8arr], {"type" : "application/pdf"});
|
||||
}
|
||||
```
|
||||
|
||||
## Detail of EncryptOption
|
||||
|
||||
* __mode__: Zga.Crypto.Mode :point_right: The values of Zga.Crypto.Mode
|
||||
* RC4_40: 40bit RC4 Encryption
|
||||
* RC4_128: 128bit RC4 Encryption
|
||||
* AES_128: 128bit AES Encryption
|
||||
* AES_256: 256bit AES Encryption
|
||||
* __permissions__: Array<Zga.Crypto.Permission> :point_right: (Optional) The set of permissions you want to block
|
||||
* "print": Print the document;
|
||||
* "modify": Modify the contents of the document by operations other than those controlled by 'fill-forms', 'extract' and 'assemble';
|
||||
* "copy": Copy or otherwise extract text and graphics from the document;
|
||||
* "annot-forms": Add or modify text annotations, fill in interactive form fields, and, if 'modify' is also set, create or modify interactive form fields (including signature fields);
|
||||
* "fill-forms": Fill in existing interactive form fields (including signature fields), even if 'annot-forms' is not specified;
|
||||
* "extract": Extract text and graphics (in support of accessibility to users with disabilities or for other purposes);
|
||||
* "assemble": Assemble the document (insert, rotate, or delete pages and create bookmarks or thumbnail images), even if 'modify' is not set;
|
||||
* "print-high": Print the document to a representation from which a faithful digital copy of the PDF content could be generated. When this is not set, printing is limited to a low-level representation of the appearance, possibly of degraded quality.
|
||||
* "owner": (inverted logic - only for public-key) when set permits change of encryption and enables all other permissions.
|
||||
* __userpwd__: string :point_right: (Optional) User password. Used when opening the pdf.
|
||||
* __ownerpwd__: string :point_right: (Optional) Owner password. If not specified, a random value is used.
|
||||
* __pubkeys__: Array<_PubKeyInfo_> :point_right: (Optional) Array of recipients containing public-key certificates ('c') and permissions ('p'). <ins>Not supported yet.</ins>
|
||||
* __c__: string :point_right: A public-key certificate
|
||||
* __p__: Array<Zga.Crypto.Permission> :point_right: (Optional) Permissions
|
||||
|
||||
## Thanks
|
||||
* The module of setting protection was migrated from [TCPDF](http://www.tcpdf.org).
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
@ -46,3 +46,58 @@ var SignDrawInfo;
|
|||
* }}
|
||||
*/
|
||||
var SignOption;
|
||||
|
||||
/**
|
||||
* @typedef
|
||||
* {{
|
||||
* c: string,
|
||||
* p: (Array<Zga.Crypto.Permission>|undefined),
|
||||
* }}
|
||||
*/
|
||||
var PubKeyInfo;
|
||||
/**
|
||||
* permissions: The set of permissions (specify the ones you want to block):
|
||||
* print : Print the document;
|
||||
* modify : Modify the contents of the document by operations other than those controlled by 'fill-forms', 'extract' and 'assemble';
|
||||
* copy : Copy or otherwise extract text and graphics from the document;
|
||||
* annot-forms : Add or modify text annotations, fill in interactive form fields, and, if 'modify' is also set, create or modify interactive form fields (including signature fields);
|
||||
* fill-forms : Fill in existing interactive form fields (including signature fields), even if 'annot-forms' is not specified;
|
||||
* extract : Extract text and graphics (in support of accessibility to users with disabilities or for other purposes);
|
||||
* assemble : Assemble the document (insert, rotate, or delete pages and create bookmarks or thumbnail images), even if 'modify' is not set;
|
||||
* print-high : Print the document to a representation from which a faithful digital copy of the PDF content could be generated. When this is not set, printing is limited to a low-level representation of the appearance, possibly of degraded quality.
|
||||
* owner : (inverted logic - only for public-key) when set permits change of encryption and enables all other permissions.
|
||||
*
|
||||
* ownerpwd: Owner password If not specified, a random value is used.
|
||||
*
|
||||
* pubkeys: Array of recipients containing public-key certificates ('c') and permissions ('p').
|
||||
*
|
||||
* @typedef
|
||||
* {{
|
||||
* mode: Zga.Crypto.Mode,
|
||||
* permissions: (Array<Zga.Crypto.Permission>|undefined),
|
||||
* userpwd: (string|undefined),
|
||||
* ownerpwd: (string|undefined),
|
||||
* pubkeys: (Array<PubKeyInfo>|undefined),
|
||||
* }}
|
||||
*/
|
||||
var EncryptOption;
|
||||
/**
|
||||
* @typedef
|
||||
* {{
|
||||
* CFM: string,
|
||||
* Length: (number|undefined),
|
||||
* EncryptMetadata: (boolean|undefined),
|
||||
* AuthEvent: (string|undefined),
|
||||
* }}
|
||||
*/
|
||||
var CFType;
|
||||
/**
|
||||
* enckey: Last RC4 key encrypted.
|
||||
* enckeyc: Last RC4 computed key.
|
||||
* @typedef
|
||||
* {{
|
||||
* enckey: string,
|
||||
* enckeyc: Array<number>,
|
||||
* }}
|
||||
*/
|
||||
var RC4LastInfo;
|
||||
|
|
|
@ -0,0 +1,812 @@
|
|||
'use strict';
|
||||
|
||||
// This module was migrated from [TCPDF](http://www.tcpdf.org)
|
||||
/**
|
||||
* @param {Object<string, *>} z
|
||||
*/
|
||||
function supplyZgaCryptor(z){
|
||||
|
||||
/**
|
||||
* @param {PDFLib.PDFDocument|Array<number>|Uint8Array|ArrayBuffer|string} pdf
|
||||
* @return {Promise<PDFLib.PDFDocument>}
|
||||
*/
|
||||
z.loadPdf = async function(pdf){
|
||||
/** @type {PDFLib.PDFDocument} */
|
||||
var pdfdoc = null;
|
||||
if(pdf.addPage){
|
||||
pdfdoc = pdf;
|
||||
}else if(Array.isArray(pdf)){
|
||||
pdfdoc = await PDFLib.PDFDocument.load(new Uint8Array(pdf));
|
||||
}else{
|
||||
pdfdoc = await PDFLib.PDFDocument.load(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){
|
||||
var arr = new Uint8Array(raw.length);
|
||||
for(var i=0; i<raw.length; i++){
|
||||
arr[i] = raw.charCodeAt(i);
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
|
||||
z.Crypto = {
|
||||
/**
|
||||
* @enum {number}
|
||||
*/
|
||||
Mode: {
|
||||
RC4_40: 0,
|
||||
RC4_128: 1,
|
||||
AES_128: 2,
|
||||
AES_256: 3,
|
||||
},
|
||||
|
||||
/**
|
||||
* @enum {number}
|
||||
*/
|
||||
Permission: {
|
||||
"owner": 2, // bit 2 -- inverted logic: cleared by default
|
||||
"print": 4, // bit 3
|
||||
"modify": 8, // bit 4
|
||||
"copy": 16, // bit 5
|
||||
"annot-forms": 32, // bit 6
|
||||
"fill-forms": 256, // bit 9
|
||||
"extract": 512, // bit 10
|
||||
"assemble": 1024,// bit 11
|
||||
"print-high": 2048 // bit 12
|
||||
},
|
||||
|
||||
/** @type {string} */
|
||||
EncPadding: "\x28\xBF\x4E\x5E\x4E\x75\x8A\x41\x64\x00\x4E\x56\xFF\xFA\x01\x08\x2E\x2E\x00\xB6\xD0\x68\x3E\x80\x2F\x0C\xA9\xFE\x64\x53\x69\x7A",
|
||||
|
||||
/**
|
||||
* Add "\" before "\", "(" and ")", and chr(13) => '\r'
|
||||
* @param {string} s string to escape.
|
||||
* @return {string} escaped string.
|
||||
*/
|
||||
_escape: function(s){
|
||||
if(!s){
|
||||
return s;
|
||||
}
|
||||
const CHARS = "\\()".split("");
|
||||
var arr = [];
|
||||
for(var i=0; i<s.length; i++){
|
||||
var c = s.charAt(i);
|
||||
if(c == "\r"){
|
||||
arr.push("\\r");
|
||||
}else{
|
||||
if(CHARS.indexOf(c) >= 0){
|
||||
arr.push("\\");
|
||||
}
|
||||
arr.push(c);
|
||||
}
|
||||
}
|
||||
return arr.join("");
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns a string containing random data to be used as a seed for encryption methods.
|
||||
* @param {string=} seed starting seed value
|
||||
* @return {string} containing random data
|
||||
*/
|
||||
getRandomSeed: function(seed){
|
||||
var ret = forge.random.getBytesSync(256);
|
||||
if(seed){
|
||||
ret += seed;
|
||||
}
|
||||
ret += new Date().getTime();
|
||||
return ret;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the input text encrypted using RC4 algorithm and the specified key.
|
||||
* RC4 is the standard encryption algorithm used in PDF format
|
||||
* @param {string} key Encryption key.
|
||||
* @param {string} txt Input text to be encrypted.
|
||||
* @param {RC4LastInfo} lastinf Last RC4 information.
|
||||
* @return {string} encrypted text
|
||||
*/
|
||||
_RC4: function(key, txt, lastinf){
|
||||
/** @type {Array<number>} */
|
||||
var rc4 = null;
|
||||
var i = 0;
|
||||
var j = 0;
|
||||
var t = 0;
|
||||
if(lastinf.enckey != key){
|
||||
var k = key.repeat(256 / key.length + 1);
|
||||
rc4 = new Array(256);
|
||||
// Initialize rc4
|
||||
for(i=0; i<rc4.length; i++){
|
||||
rc4[i] = i;
|
||||
}
|
||||
for(i=0; i<rc4.length; i++){
|
||||
t = rc4[i];
|
||||
j = (j + t + k.charCodeAt(i)) % 256;
|
||||
rc4[i] = rc4[j];
|
||||
rc4[j] = t;
|
||||
}
|
||||
lastinf.enckey = key;
|
||||
lastinf.enckeyc = [].concat(rc4);
|
||||
}else{
|
||||
rc4 = [].concat(lastinf.enckeyc);
|
||||
}
|
||||
var len = txt.length;
|
||||
var a = 0;
|
||||
var b = 0;
|
||||
var out = "";
|
||||
for(i=0; i<len; i++){
|
||||
a = (a + 1) % 256;
|
||||
t = rc4[a];
|
||||
b = (b + t) % 256;
|
||||
rc4[a] = rc4[b];
|
||||
rc4[b] = t;
|
||||
k = rc4[(rc4[a] + rc4[b]) % 256];
|
||||
out += String.fromCharCode(txt.charCodeAt(i) ^ k);
|
||||
}
|
||||
return out;
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the permission code used on encryption (P value).
|
||||
*
|
||||
* @param {Array<Zga.Crypto.Permission>=} permissions the set of permissions (specify the ones you want to block).
|
||||
* @param {Zga.Crypto.Mode=} mode
|
||||
* @return {number}
|
||||
*/
|
||||
getUserPermissionCode: function(permissions, mode){
|
||||
var protection = 2147422012; // 32 bit: (01111111 11111111 00001111 00111100)
|
||||
if(permissions){
|
||||
permissions.forEach(function(a_itm){
|
||||
var a_p = z.Crypto.Permission[a_itm];
|
||||
if(a_p){
|
||||
if(mode > 0 || a_p <= 32){
|
||||
// set only valid permissions
|
||||
if(a_p == 2){
|
||||
// the logic for bit 2 is inverted (cleared by default)
|
||||
protection += a_p;
|
||||
}else{
|
||||
protection -= a_p;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return protection;
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert encryption P value to a string of bytes, low-order byte first.
|
||||
* @param {number} protection 32bit encryption permission value (P value)
|
||||
* @return {string}
|
||||
*/
|
||||
getEncPermissionsString: function(protection){
|
||||
var buff = new forge.util.ByteStringBuffer();
|
||||
buff.putInt32Le(protection);
|
||||
return buff.getBytes();
|
||||
},
|
||||
|
||||
/**
|
||||
* Encrypts a string using MD5 and returns it's value as a binary string.
|
||||
* @param {string} str input string
|
||||
* @return {string} MD5 encrypted binary string
|
||||
*/
|
||||
_md5_16: function(str){
|
||||
var md = forge.md.md5.create();
|
||||
md.update(str);
|
||||
return md.digest().getBytes();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the input text encrypted using AES algorithm and the specified key.
|
||||
* Text is padded to 16bytes blocks
|
||||
* @param {string} key encryption key
|
||||
* @param {string} txt input text to be encrypted
|
||||
* @return {string} encrypted text
|
||||
*/
|
||||
_AES: function(key, txt){
|
||||
// padding (RFC 2898, PKCS #5: Password-Based Cryptography Specification Version 2.0)
|
||||
/** @type {string} */
|
||||
var padding = 16 - (txt.length % 16);
|
||||
var buff = forge.util.createBuffer(txt);
|
||||
buff.fillWithByte(padding, padding);
|
||||
|
||||
var iv = forge.random.getBytesSync(16);
|
||||
var key2 = forge.util.createBuffer(key);
|
||||
var cipher = forge.cipher.createCipher("AES-CBC", key2);
|
||||
cipher.start({iv: iv});
|
||||
cipher.update(buff);
|
||||
cipher.finish();
|
||||
return iv + cipher.output.truncate(16).getBytes();
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the input text encrypted using AES algorithm and the specified key.
|
||||
* Text is not padded
|
||||
* @param {string} key encryption key
|
||||
* @param {string} txt input text to be encrypted
|
||||
* @return {string} encrypted text
|
||||
*/
|
||||
_AESnopad: function(key, txt) {
|
||||
var buff = forge.util.createBuffer(txt);
|
||||
var iv = String.fromCharCode(0).repeat(16);
|
||||
var key2 = forge.util.createBuffer(key);
|
||||
var cipher = forge.cipher.createCipher("AES-CBC", key2);
|
||||
cipher.start({iv: iv});
|
||||
cipher.update(buff);
|
||||
cipher.finish();
|
||||
return cipher.output.truncate(16).getBytes();
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
z.PdfCryptor = class{
|
||||
/**
|
||||
* @constructor
|
||||
* @param {EncryptOption} encopt
|
||||
*/
|
||||
constructor(encopt){
|
||||
/** @private @type {string} */
|
||||
this.fileid = "";
|
||||
/** @private @type {string} */
|
||||
this.key = null;
|
||||
/** @private @type {Array<PubKeyInfo>} */
|
||||
this.pubkeys = encopt.pubkeys;
|
||||
/** @private @type {number} */
|
||||
this.mode = encopt.mode;
|
||||
/** @private @type {number} */
|
||||
this.protection = 0;
|
||||
/** @private @type {string} */
|
||||
this.userpwd = "";
|
||||
/** @private @type {string} */
|
||||
this.ownerpwd = "";
|
||||
|
||||
/** @private @type {string} */
|
||||
this.Filter = "Standard";
|
||||
/** @private @type {string} */
|
||||
this.StmF = "StdCF";
|
||||
/** @private @type {string} */
|
||||
this.StrF = "StdCF";
|
||||
|
||||
/** @private @type {number} */
|
||||
this.V = 1;
|
||||
/** @private @type {number} */
|
||||
this.Length = 0;
|
||||
/** @private @type {CFType} */
|
||||
this.CF = null;
|
||||
/** @private @type {string} */
|
||||
this.SubFilter = "";
|
||||
/** @private @type {Array<string>} */
|
||||
this.Recipients = null;
|
||||
|
||||
/** @private @type {string} */// User Validation Salt
|
||||
this.UVS = "";
|
||||
/** @private @type {string} */// User Key Salt
|
||||
this.UKS = "";
|
||||
/** @private @type {string} */// U value
|
||||
this.U = "";
|
||||
/** @private @type {string} */// UE value
|
||||
this.UE = "";
|
||||
|
||||
/** @private @type {string} */// Owner Validation Salt
|
||||
this.OVS = "";
|
||||
/** @private @type {string} */// Owner Key Salt
|
||||
this.OKS = "";
|
||||
/** @private @type {string} */// O value
|
||||
this.O = "";
|
||||
/** @private @type {string} */// OE value
|
||||
this.OE = "";
|
||||
/** @private @type {number} */// P value
|
||||
this.P = 0;
|
||||
/** @private @type {string} */
|
||||
this.perms = "";
|
||||
|
||||
/** @private @type {RC4LastInfo} */
|
||||
this.rc4inf = {
|
||||
enckey: "",
|
||||
enckeyc: null,
|
||||
};
|
||||
|
||||
if(this.pubkeys){
|
||||
throw new Error("Public key mode is not supported yet.");
|
||||
if(this.mode == z.Crypto.Mode.RC4_40){
|
||||
// public-Key Security requires at least 128 bit
|
||||
this.mode = z.Crypto.Mode.RC4_128;
|
||||
}
|
||||
this.Filter = "Adobe.PubSec";
|
||||
this.StmF = "DefaultCryptFilter";
|
||||
this.StrF = "DefaultCryptFilter";
|
||||
}
|
||||
|
||||
if(encopt.userpwd){
|
||||
this.userpwd = encopt.userpwd;
|
||||
}
|
||||
if(encopt.ownerpwd){
|
||||
this.ownerpwd = encopt.ownerpwd;
|
||||
}else{
|
||||
var md = forge.md.md5.create();
|
||||
md.update(z.Crypto.getRandomSeed());
|
||||
this.ownerpwd = md.digest().toHex();
|
||||
}
|
||||
|
||||
switch(this.mode){
|
||||
case z.Crypto.Mode.RC4_40:
|
||||
this.V = 1;
|
||||
this.Length = 40;
|
||||
this.CF = {CFM: "V2"};
|
||||
break;
|
||||
case z.Crypto.Mode.RC4_128:
|
||||
this.V = 2;
|
||||
this.Length = 128;
|
||||
this.CF = {CFM: "V2"};
|
||||
if(this.pubkeys){
|
||||
this.SubFilter = "adbe.pkcs7.s4";
|
||||
this.Recipients = [];
|
||||
}
|
||||
break;
|
||||
case z.Crypto.Mode.AES_128:
|
||||
this.V = 4;
|
||||
this.Length = 128;
|
||||
this.CF = {CFM: "AESV2", Length: 128};
|
||||
if(this.pubkeys){
|
||||
this.SubFilter = "adbe.pkcs7.s5";
|
||||
this.Recipients = [];
|
||||
}
|
||||
break;
|
||||
case z.Crypto.Mode.AES_256:
|
||||
this.V = 5;
|
||||
this.Length = 256;
|
||||
this.CF = {CFM: "AESV3", Length: 256};
|
||||
if(this.pubkeys){
|
||||
this.SubFilter = "adbe.pkcs7.s5";
|
||||
this.Recipients = [];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
this.protection = z.Crypto.getUserPermissionCode(encopt.permissions, this.mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
* @param {PDFLib.PDFDocument|Array<number>|Uint8Array|ArrayBuffer|string} pdf
|
||||
* @param {boolean=} reload
|
||||
* @return {Promise<PDFLib.PDFDocument>}
|
||||
*
|
||||
* If the parameter of pdf is PDFLib.PDFDocument, and some embedded contents have been added to it,
|
||||
* then the parameter of reload needs to be true. Because before the encryption, all changes must be applied.
|
||||
* And if reload is true, the return value is a new pdf document, else is pdf itself.
|
||||
*/
|
||||
async encryptPdf(pdf, reload){
|
||||
/** @type {PDFLib.PDFDocument} */
|
||||
var pdfdoc = await z.loadPdf(pdf);
|
||||
if(pdfdoc === pdf && reload){
|
||||
// Temporaryly save the pdf and reload it to apply all changes.
|
||||
/** @type {Uint8Array} */
|
||||
var newpdf = await pdfdoc.save({"useObjectStreams": false});
|
||||
pdfdoc = await PDFLib.PDFDocument.load(newpdf);
|
||||
}
|
||||
|
||||
/** @type {PDFLib.PDFContext} */
|
||||
var pdfcont = pdfdoc.context;
|
||||
/** @type {PDFLib.PDFObject} */
|
||||
var trobj = this.prepareEncrypt(pdfcont);
|
||||
|
||||
/**
|
||||
* @param {number} a_num
|
||||
* @param {*} a_val
|
||||
*/
|
||||
var func = function(a_num, a_val){
|
||||
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 {IteratorResult} */
|
||||
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(a_arr){
|
||||
func(a_arr[0].objectNumber, a_arr[1]);
|
||||
});
|
||||
|
||||
pdfcont.trailerInfo.Encrypt = pdfcont.register(trobj);
|
||||
|
||||
return pdfdoc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare for encryption and create the object for saving in trailer.
|
||||
*
|
||||
* @private
|
||||
* @param {PDFLib.PDFContext} pdfcont
|
||||
* @return {PDFLib.PDFObject}
|
||||
*/
|
||||
prepareEncrypt(pdfcont){
|
||||
if(!pdfcont.trailerInfo.ID){
|
||||
var md = forge.md.md5.create();
|
||||
md.update(z.Crypto.getRandomSeed());
|
||||
var res = md.digest();
|
||||
var idhex = res.toHex();
|
||||
this.fileid = res.getBytes();
|
||||
|
||||
var trIds = new PDFLib.PDFArray(pdfcont);
|
||||
trIds.push(PDFLib.PDFHexString.of(idhex));
|
||||
trIds.push(PDFLib.PDFHexString.of(idhex));
|
||||
pdfcont.trailerInfo.ID = trIds;
|
||||
|
||||
}else{
|
||||
this.fileid = forge.util.hexToBytes(pdfcont.trailerInfo.ID.get(0).value);
|
||||
}
|
||||
this._generateencryptionkey();
|
||||
|
||||
var obj = {};
|
||||
obj.Filter = this.Filter;
|
||||
if(this.SubFilter){
|
||||
obj.SubFilter = this.SubFilter;
|
||||
}
|
||||
// V is a code specifying the algorithm to be used in encrypting and decrypting the document
|
||||
obj.V = this.V;
|
||||
// The length of the encryption key, in bits. The value shall be a multiple of 8, in the range 40 to 256
|
||||
obj.Length = this.Length;
|
||||
if(this.V >= 4){
|
||||
// A dictionary whose keys shall be crypt filter names and whose values shall be the corresponding crypt filter dictionaries.
|
||||
if(this.CF){
|
||||
var objStmF = {
|
||||
Type: "CryptFilter",
|
||||
};
|
||||
// The method used
|
||||
if(this.CF.CFM){
|
||||
objStmF.CFM = this.CF.CFM;
|
||||
if(this.pubkeys){
|
||||
/** @type {PDFLib.PDFArray} */
|
||||
var recps = new PDFLib.PDFArray(pdfcont);
|
||||
this.Recipients.forEach(function(a_ele){
|
||||
recps.push(PDFLib.PDFHexString.of(a_ele));
|
||||
});
|
||||
objStmF.Recipients = recps;
|
||||
if(typeof this.CF.EncryptMetadata == "boolean" && !this.CF.EncryptMetadata){
|
||||
objStmF.EncryptMetadata = false;
|
||||
}else{
|
||||
objStmF.EncryptMetadata = true;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
objStmF.CFM = "None";
|
||||
}
|
||||
// The event to be used to trigger the authorization that is required to access encryption keys used by this filter.
|
||||
if(this.CF.AuthEvent){
|
||||
objStmF.AuthEvent = this.CF.AuthEvent;
|
||||
}else{
|
||||
objStmF.AuthEvent = "DocOpen";
|
||||
}
|
||||
// The bit length of the encryption key.
|
||||
if(this.CF.Length){
|
||||
objStmF.Length = this.CF.Length;
|
||||
}
|
||||
|
||||
var objCF = {
|
||||
[this.StmF]: pdfcont.obj(objStmF),
|
||||
};
|
||||
obj.CF = pdfcont.obj(objCF);
|
||||
}
|
||||
// The name of the crypt filter that shall be used by default when decrypting streams.
|
||||
obj.StmF = this.StmF;
|
||||
// The name of the crypt filter that shall be used when decrypting all strings in the document.
|
||||
obj.StrF = this.StrF;
|
||||
}
|
||||
// Additional encryption dictionary entries for the standard security handler
|
||||
if(this.pubkeys){
|
||||
if(this.V < 4 && this.Recipients && this.Recipients.length > 0){
|
||||
/** @type {PDFLib.PDFArray} */
|
||||
var recps = new PDFLib.PDFArray(pdfcont);
|
||||
this.Recipients.forEach(function(a_ele){
|
||||
recps.push(PDFLib.PDFHexString.of(a_ele));
|
||||
});
|
||||
obj.Recipients = recps;
|
||||
}
|
||||
}else{
|
||||
if(this.V == 5){ // AES-256
|
||||
obj.R = 5;
|
||||
obj.OE = PDFLib.PDFString.of(z.Crypto._escape(this.OE));
|
||||
obj.UE = PDFLib.PDFString.of(z.Crypto._escape(this.UE));
|
||||
obj.Perms = PDFLib.PDFString.of(z.Crypto._escape(this.perms));
|
||||
}else if(this.V == 4){ // AES-128
|
||||
obj.R = 4;
|
||||
}else if(this.V < 2){ // RC-40
|
||||
obj.R = 2;
|
||||
}else{ // RC-128
|
||||
obj.R = 3;
|
||||
}
|
||||
obj.O = PDFLib.PDFString.of(z.Crypto._escape(this.O));
|
||||
obj.U = PDFLib.PDFString.of(z.Crypto._escape(this.U));
|
||||
obj.P = this.P;
|
||||
if(typeof this.EncryptMetadata == "boolean" && !this.EncryptMetadata){
|
||||
obj.EncryptMetadata = false;
|
||||
}else{
|
||||
obj.EncryptMetadata = true;
|
||||
}
|
||||
}
|
||||
return pdfcont.obj(obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {number} num
|
||||
* @param {Uint8Array} dat
|
||||
* @return {Uint8Array}
|
||||
*/
|
||||
encryptU8arr(num, dat){
|
||||
var str = z.u8arrToRaw(dat);
|
||||
var enc = this._encrypt_data(num, str);
|
||||
return z.rawToU8arr(enc);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {number} num
|
||||
* @param {string} dat
|
||||
* @return {string}
|
||||
*/
|
||||
encryptHexstr(num, dat){
|
||||
var str = forge.util.hexToBytes(dat);
|
||||
var enc = this._encrypt_data(num, str);
|
||||
return forge.util.createBuffer(enc).toHex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute encryption key depending on object number where the encrypted data is stored.
|
||||
* This is used for all strings and streams without crypt filter specifier.
|
||||
*
|
||||
* @private
|
||||
* @param {number} n object number
|
||||
* @return {string} object key
|
||||
*/
|
||||
_objectkey(n){
|
||||
var buff = forge.util.createBuffer(this.key);
|
||||
//pack('VXxx', $n)
|
||||
buff.putInt24Le(n);
|
||||
buff.putBytes(String.fromCharCode(0) + String.fromCharCode(0));
|
||||
if (this.mode == z.Crypto.Mode.AES_128) {
|
||||
// AES padding
|
||||
buff.putBytes("sAlT");
|
||||
}
|
||||
|
||||
var md = forge.md.md5.create();
|
||||
md.update(buff.getBytes());
|
||||
var ret = md.digest();
|
||||
return ret.getBytes().substr(0, Math.min(16, (this.Length / 8) + 5));
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt the input string.
|
||||
*
|
||||
* @private
|
||||
* @param {number} n object number
|
||||
* @param {string} s data string to encrypt
|
||||
* @return {string} encrypted string
|
||||
*/
|
||||
_encrypt_data(n, s){
|
||||
switch(this.mode){
|
||||
case z.Crypto.Mode.RC4_40:
|
||||
case z.Crypto.Mode.RC4_128:
|
||||
s = z.Crypto._RC4(this._objectkey(n), s, this.rc4inf);
|
||||
break;
|
||||
case z.Crypto.Mode.AES_128:
|
||||
s = z.Crypto._AES(this._objectkey(n), s);
|
||||
break;
|
||||
case z.Crypto.Mode.AES_256:
|
||||
s = z.Crypto._AES(this.key, s);
|
||||
break;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute U value (used for encryption)
|
||||
* @private
|
||||
* @return {string} U value
|
||||
*/
|
||||
_Uvalue(){
|
||||
if(this.mode == z.Crypto.Mode.RC4_40){
|
||||
return z.Crypto._RC4(this.key, z.Crypto.EncPadding, this.rc4inf);
|
||||
}else if(this.mode < z.Crypto.Mode.AES_256) { // RC4-128, AES-128
|
||||
var tmp = z.Crypto._md5_16(z.Crypto.EncPadding + this.fileid);
|
||||
var enc = z.Crypto._RC4(this.key, tmp, this.rc4inf);
|
||||
var len = tmp.length;
|
||||
for(var i=1; i<=19; i++){
|
||||
var ek = "";
|
||||
for(var j=0; j<len; j++){
|
||||
ek += String.fromCharCode(this.key.charCodeAt(j) ^ i);
|
||||
}
|
||||
enc = z.Crypto._RC4(ek, enc, this.rc4inf);
|
||||
}
|
||||
enc += String.fromCharCode(0).repeat(16);
|
||||
return enc.substr(0, 32);
|
||||
|
||||
}else if(this.mode == z.Crypto.Mode.AES_256){
|
||||
var seed = z.Crypto._md5_16(z.Crypto.getRandomSeed());
|
||||
// User Validation Salt
|
||||
this.UVS = seed.substr(0, 8);
|
||||
// User Key Salt
|
||||
this.UKS = seed.substr(8, 16);
|
||||
|
||||
var md = forge.md.sha256.create();
|
||||
md.update(this.userpwd + this.UVS);
|
||||
return md.digest().getBytes() + this.UVS + this.UKS;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute UE value (used for encryption)
|
||||
* @private
|
||||
* @return {string} UE value
|
||||
*/
|
||||
_UEvalue(){
|
||||
var md = forge.md.sha256.create();
|
||||
md.update(this.userpwd + this.UKS);
|
||||
return z.Crypto._AESnopad(md.digest().getBytes(), this.key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute O value (used for encryption)
|
||||
* @private
|
||||
* @return {string} O value
|
||||
*/
|
||||
_Ovalue(){
|
||||
if(this.mode < z.Crypto.Mode.AES_256){ // RC4-40, RC4-128, AES-128
|
||||
var tmp = z.Crypto._md5_16(this.ownerpwd);
|
||||
if(this.mode > z.Crypto.Mode.RC4_40){
|
||||
for(var i=0; i<50; i++){
|
||||
tmp = z.Crypto._md5_16(tmp);
|
||||
}
|
||||
}
|
||||
var owner_key = tmp.substr(0, this.Length / 8);
|
||||
var enc = z.Crypto._RC4(owner_key, this.userpwd, this.rc4inf);
|
||||
if(this.mode > z.Crypto.Mode.RC4_40){
|
||||
var len = owner_key.length;
|
||||
for(var i=1; i<=19; i++){
|
||||
var ek = "";
|
||||
for(var j=0; j<len; j++){
|
||||
ek += String.fromCharCode(owner_key.charCodeAt(j) ^ i);
|
||||
}
|
||||
enc = z.Crypto._RC4(ek, enc, this.rc4inf);
|
||||
}
|
||||
}
|
||||
return enc;
|
||||
}else if(this.mode == z.Crypto.Mode.AES_256){
|
||||
var seed = z.Crypto._md5_16(z.Crypto.getRandomSeed());
|
||||
// Owner Validation Salt
|
||||
this.OVS = seed.substr(0, 8);
|
||||
// Owner Key Salt
|
||||
this.OKS = seed.substr(8, 16);
|
||||
|
||||
var md = forge.md.sha256.create();
|
||||
md.update(this.ownerpwd + this.OVS + this.U);
|
||||
return md.digest().getBytes() + this.OVS + this.OKS;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute OE value (used for encryption)
|
||||
* @private
|
||||
* @return {string} OE value
|
||||
*/
|
||||
_OEvalue(){
|
||||
var md = forge.md.sha256.create();
|
||||
md.update(this.ownerpwd + this.OKS + this.U);
|
||||
return z.Crypto._AESnopad(md.digest().getBytes(), this.key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert password for AES-256 encryption mode
|
||||
* @private
|
||||
* @param {string} pwd password
|
||||
* @return {string} password
|
||||
*/
|
||||
_fixAES256Password(pwd) {
|
||||
return pwd.substr(0, 127);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute encryption key
|
||||
* @private
|
||||
*/
|
||||
_generateencryptionkey(){
|
||||
var keybytelen = this.Length / 8;
|
||||
// standard mode
|
||||
if(!this.pubkeys){
|
||||
if(this.mode == z.Crypto.Mode.AES_256){
|
||||
// generate 256 bit random key
|
||||
var md = forge.md.sha256.create();
|
||||
md.update(z.Crypto.getRandomSeed());
|
||||
this.key = md.digest().getBytes().substr(0, keybytelen);
|
||||
// truncate passwords
|
||||
this.userpwd = this._fixAES256Password(this.userpwd);
|
||||
this.ownerpwd = this._fixAES256Password(this.ownerpwd);
|
||||
// Compute U value
|
||||
this.U = this._Uvalue();
|
||||
// Compute UE value
|
||||
this.UE = this._UEvalue();
|
||||
// Compute O value
|
||||
this.O = this._Ovalue();
|
||||
// Compute OE value
|
||||
this.OE = this._OEvalue();
|
||||
// Compute P value
|
||||
this.P = this.protection;
|
||||
// Computing the encryption dictionary's Perms (permissions) value
|
||||
var perms = z.Crypto.getEncPermissionsString(this.protection); // bytes 0-3
|
||||
perms += String.fromCharCode(255).repeat(4); // bytes 4-7
|
||||
if(typeof this.CF.EncryptMetadata == "boolean" && !this.CF.EncryptMetadata){ // byte 8
|
||||
perms += "F";
|
||||
}else{
|
||||
perms += "T";
|
||||
}
|
||||
perms += "adb"; // bytes 9-11
|
||||
perms += "nick"; // bytes 12-15
|
||||
this.perms = z.Crypto._AESnopad(this.key, perms);
|
||||
}else{ // RC4-40, RC4-128, AES-128
|
||||
// Pad passwords
|
||||
this.userpwd = (this.userpwd + z.Crypto.EncPadding).substr(0, 32);
|
||||
this.ownerpwd = (this.ownerpwd + z.Crypto.EncPadding).substr(0, 32);
|
||||
// Compute O value
|
||||
this.O = this._Ovalue();
|
||||
// get default permissions (reverse byte order)
|
||||
var permissions = z.Crypto.getEncPermissionsString(this.protection);
|
||||
// Compute encryption key
|
||||
var tmp = z.Crypto._md5_16(this.userpwd + this.O + permissions + this.fileid);
|
||||
if(this.mode > z.Crypto.Mode.RC4_40) {
|
||||
for(var i=0; i<50; i++){
|
||||
tmp = z.Crypto._md5_16(tmp.substr(0, keybytelen));
|
||||
}
|
||||
}
|
||||
this.key = tmp.substr(0, keybytelen);
|
||||
// Compute U value
|
||||
this.U = this._Uvalue();
|
||||
// Compute P value
|
||||
this.P = this.protection;
|
||||
}
|
||||
}else{ // Public-Key mode
|
||||
//TODO
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
if(!globalThis.Zga){
|
||||
globalThis.Zga = {};
|
||||
}
|
||||
supplyZgaCryptor(globalThis.Zga);
|
210
zgapdfsigner.js
210
zgapdfsigner.js
|
@ -1,28 +1,37 @@
|
|||
'use strict';
|
||||
|
||||
globalThis.Zga = {
|
||||
/**
|
||||
* @param {Object<string, *>} z
|
||||
*/
|
||||
function supplyZgaSigner(z){
|
||||
|
||||
/** @type {Object<string, TsaServiceInfo>} */
|
||||
TSAURLS: {
|
||||
"1": {url: "http://ts.ssl.com", len: 15100},
|
||||
"2": {url: "http://timestamp.digicert.com", len: 14900},
|
||||
"3": {url: "http://timestamp.sectigo.com", len: 12900},
|
||||
"4": {url: "http://timestamp.entrust.net/TSS/RFC3161sha2TS", len: 13900},
|
||||
"5": {url: "http://timestamp.apple.com/ts01", len: 11600},
|
||||
"6": {url: "http://www.langedge.jp/tsa", len: 8700},
|
||||
"7": {url: "https://freetsa.org/tsr", len: 14000},
|
||||
},
|
||||
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},
|
||||
};
|
||||
|
||||
PdfSigner: class {
|
||||
z.PdfSigner = class{
|
||||
/**
|
||||
* @constructor
|
||||
* @param {SignOption} signopt
|
||||
*/
|
||||
constructor(signopt){
|
||||
/** @private @const {string} */
|
||||
this.DEFAULT_BYTE_RANGE_PLACEHOLDER = "**********";
|
||||
/** @private @type {SignOption} */
|
||||
this.opt = signopt;
|
||||
/** @private @type {TsaServiceInfo} */
|
||||
this.tsainf = null;
|
||||
/** @private @type {number} */
|
||||
this.siglen = 0;
|
||||
/** @private @type {PDFLib.PDFHexString} */
|
||||
this.sigContents = null;
|
||||
/** @private @type {boolean} */
|
||||
this.debug = false;
|
||||
|
||||
|
@ -45,8 +54,8 @@ PdfSigner: class {
|
|||
if(!globalThis.UrlFetchApp){
|
||||
throw new Error("Because of the CORS security restrictions, signing with TSA is not supported in web browser.");
|
||||
}
|
||||
if(Zga.TSAURLS[this.tsainf.url]){
|
||||
Object.assign(this.tsainf, Zga.TSAURLS[this.tsainf.url]);
|
||||
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));
|
||||
}
|
||||
|
@ -64,19 +73,17 @@ PdfSigner: class {
|
|||
/**
|
||||
* @public
|
||||
* @param {PDFLib.PDFDocument|Array<number>|Uint8Array|ArrayBuffer|string} pdf
|
||||
* @param {EncryptOption=} cypopt
|
||||
* @return {Promise<Uint8Array>}
|
||||
*/
|
||||
async sign(pdf){
|
||||
/** @type {PDFLib.PDFDocument} */
|
||||
var pdfdoc = null;
|
||||
if(pdf.addPage){
|
||||
pdfdoc = pdf;
|
||||
}else if(Array.isArray(pdf)){
|
||||
pdfdoc = await PDFLib.PDFDocument.load(new Uint8Array(pdf));
|
||||
}else{
|
||||
pdfdoc = await PDFLib.PDFDocument.load(pdf);
|
||||
async sign(pdf, cypopt){
|
||||
if(cypopt && !z.PdfCryptor){
|
||||
throw new Error("ZgaPdfCryptor is not imported.");
|
||||
}
|
||||
|
||||
/** @type {PDFLib.PDFDocument} */
|
||||
var pdfdoc = await z.loadPdf(pdf);
|
||||
|
||||
if(this.opt.drawinf && this.opt.drawinf.imgData && !this.opt.drawinf.img){
|
||||
/** @type {Uint8Array|ArrayBuffer|string} */
|
||||
var imgData2 = null;
|
||||
|
@ -95,29 +102,56 @@ PdfSigner: class {
|
|||
}
|
||||
|
||||
this.addSignHolder(pdfdoc);
|
||||
var uarr = await pdfdoc.save({"useObjectStreams": false});
|
||||
var pdfstr = Zga.u8arrToRaw(uarr);
|
||||
|
||||
this.log("A signature holder has been added to the pdf.");
|
||||
|
||||
if(cypopt){
|
||||
/** @type {Zga.PdfCryptor} */
|
||||
var cypt = new z.PdfCryptor(cypopt);
|
||||
pdfdoc = await cypt.encryptPdf(pdfdoc, true);
|
||||
// Because pdfdoc has been changed, so this.sigContents need to be found again.
|
||||
this.sigContents = null;
|
||||
this.log("Pdf data has been encrypted.");
|
||||
}
|
||||
|
||||
/** @type {Uint8Array} */
|
||||
var ret = this.signPdf(pdfstr);
|
||||
this.log("Signing pdf accomplished.");
|
||||
var ret = await this.saveAndSign(pdfdoc);
|
||||
if(!ret){
|
||||
this.log("Change size of signature's placeholder and retry.");
|
||||
if(!this.sigContents){
|
||||
this.sigContents = this.findSigContents(pdfdoc);
|
||||
}
|
||||
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.");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {PDFLib.PDFDocument} pdfdoc
|
||||
* @return {Promise<Uint8Array>}
|
||||
*/
|
||||
async saveAndSign(pdfdoc){
|
||||
/** @type {Uint8Array} */
|
||||
var uarr = await pdfdoc.save({"useObjectStreams": false});
|
||||
/** @type {string} */
|
||||
var pdfstr = z.u8arrToRaw(uarr);
|
||||
return this.signPdf(pdfstr);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {PDFLib.PDFDocument} pdfdoc
|
||||
*/
|
||||
addSignHolder(pdfdoc){
|
||||
/** @const {string} */
|
||||
const DEFAULT_BYTE_RANGE_PLACEHOLDER = "**********";
|
||||
/** @const {number} */
|
||||
const SIGNATURE_LENGTH = this.tsainf ? this.tsainf.len : 3322;
|
||||
|
||||
/** @const {VisualSignature} */
|
||||
const visign = new Zga.VisualSignature(this.opt.drawinf);
|
||||
const visign = new z.VisualSignature(this.opt.drawinf);
|
||||
/** @const {PDFLib.PDFRef} */
|
||||
const strmRef = visign.createStream(pdfdoc, this.opt.signame);
|
||||
/** @const {PDFLib.PDFPage} */
|
||||
|
@ -132,9 +166,12 @@ PdfSigner: class {
|
|||
/** @type {PDFLib.PDFArray} */
|
||||
var bytrng = new PDFLib.PDFArray(pdfdoc.context);
|
||||
bytrng.push(PDFLib.PDFNumber.of(0));
|
||||
bytrng.push(PDFLib.PDFName.of(DEFAULT_BYTE_RANGE_PLACEHOLDER));
|
||||
bytrng.push(PDFLib.PDFName.of(DEFAULT_BYTE_RANGE_PLACEHOLDER));
|
||||
bytrng.push(PDFLib.PDFName.of(DEFAULT_BYTE_RANGE_PLACEHOLDER));
|
||||
bytrng.push(PDFLib.PDFName.of(this.DEFAULT_BYTE_RANGE_PLACEHOLDER));
|
||||
bytrng.push(PDFLib.PDFName.of(this.DEFAULT_BYTE_RANGE_PLACEHOLDER));
|
||||
bytrng.push(PDFLib.PDFName.of(this.DEFAULT_BYTE_RANGE_PLACEHOLDER));
|
||||
|
||||
this.siglen = this.tsainf ? this.tsainf.len : 3322;
|
||||
this.sigContents = PDFLib.PDFHexString.of("0".repeat(this.siglen));
|
||||
|
||||
/** @type {Object<string, *>} */
|
||||
var signObj = {
|
||||
|
@ -142,7 +179,7 @@ PdfSigner: class {
|
|||
"Filter": "Adobe.PPKLite",
|
||||
"SubFilter": "adbe.pkcs7.detached",
|
||||
"ByteRange": bytrng,
|
||||
"Contents": PDFLib.PDFHexString.of("0".repeat(SIGNATURE_LENGTH)),
|
||||
"Contents": this.sigContents,
|
||||
"M": PDFLib.PDFString.fromDate(signdate),
|
||||
"Prop_Build": pdfdoc.context.obj({
|
||||
"App": pdfdoc.context.obj({
|
||||
|
@ -192,6 +229,51 @@ PdfSigner: class {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {PDFLib.PDFDocument} pdfdoc
|
||||
* @return {PDFLib.PDFHexString}
|
||||
*/
|
||||
findSigContents(pdfdoc){
|
||||
/** @type {boolean} */
|
||||
var istgt = false;
|
||||
/** @type {PDFLib.PDFHexString} */
|
||||
var sigContents = null;
|
||||
/** @type {Array<*>} */
|
||||
var objarr = pdfdoc.context.enumerateIndirectObjects();
|
||||
for(var i=objarr.length - 1; i>= 0; i--){
|
||||
if(objarr[i][1].dict instanceof Map){
|
||||
/** @type {Iterator} */
|
||||
var es = objarr[i][1].dict.entries();
|
||||
/** @type {IteratorResult} */
|
||||
var res = es.next();
|
||||
istgt = false;
|
||||
sigContents = null;
|
||||
while(!res.done){
|
||||
if(res.value[0].encodedName == "/ByteRange"){
|
||||
if(res.value[1].array &&
|
||||
res.value[1].array.length == 4 &&
|
||||
res.value[1].array[0].numberValue == 0 &&
|
||||
res.value[1].array[1].encodedName == "/" + this.DEFAULT_BYTE_RANGE_PLACEHOLDER &&
|
||||
res.value[1].array[2].encodedName == res.value[1].array[1].encodedName &&
|
||||
res.value[1].array[3].encodedName == res.value[1].array[1].encodedName){
|
||||
istgt = true;
|
||||
}
|
||||
}else if(res.value[0].encodedName == "/Contents"){
|
||||
if(res.value[1] instanceof PDFLib.PDFHexString){
|
||||
sigContents = res.value[1];
|
||||
}
|
||||
}
|
||||
if(istgt && sigContents){
|
||||
return sigContents;
|
||||
}else{
|
||||
res = es.next();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {string} pdfstr
|
||||
|
@ -207,8 +289,8 @@ PdfSigner: class {
|
|||
// Finds ByteRange information within a given PDF Buffer if one exists
|
||||
var byteRangeStrings = pdfstr.match(/\/ByteRange\s*\[{1}\s*(?:(?:\d*|\/\*{10})\s+){3}(?:\d+|\/\*{10}){1}\s*]{1}/g);
|
||||
var byteRangePlaceholder = byteRangeStrings.find(function(a_str){
|
||||
return a_str.includes("/**********");
|
||||
});
|
||||
return a_str.includes("/"+this.DEFAULT_BYTE_RANGE_PLACEHOLDER);
|
||||
}.bind(this));
|
||||
if(!byteRangePlaceholder){
|
||||
throw new Error("no signature placeholder");
|
||||
}
|
||||
|
@ -231,7 +313,7 @@ PdfSigner: class {
|
|||
pdfstr = pdfstr.slice(0, byteRange[1]) + pdfstr.slice(byteRange[2], byteRange[2] + byteRange[3]);
|
||||
|
||||
if(typeof this.opt.p12cert !== "string"){
|
||||
this.opt.p12cert = Zga.u8arrToRaw(new Uint8Array(this.opt.p12cert));
|
||||
this.opt.p12cert = z.u8arrToRaw(new Uint8Array(this.opt.p12cert));
|
||||
}
|
||||
// Convert Buffer P12 to a forge implementation.
|
||||
var p12Asn1 = forge.asn1.fromDer(this.opt.p12cert);
|
||||
|
@ -323,7 +405,9 @@ PdfSigner: class {
|
|||
// checking the actual lengths.
|
||||
this.log("Size of signature is " + sighex.length + "/" + placeholderLength);
|
||||
if(sighex.length > placeholderLength){
|
||||
throw new Error("Signature is too big. Needs: " + sighex.length);
|
||||
// throw new Error("Signature is too big. Needs: " + sighex.length);
|
||||
this.siglen = sighex.length;
|
||||
return null;
|
||||
}else{
|
||||
// Pad the signature with zeroes so the it is the same length as the placeholder
|
||||
sighex += "0".repeat(placeholderLength - sighex.length);
|
||||
|
@ -331,7 +415,7 @@ PdfSigner: class {
|
|||
// Place it in the document.
|
||||
pdfstr = pdfstr.slice(0, byteRange[1]) + "<" + sighex + ">" + pdfstr.slice(byteRange[1]);
|
||||
|
||||
return Zga.rawToU8arr(pdfstr);
|
||||
return z.rawToU8arr(pdfstr);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -432,14 +516,14 @@ PdfSigner: class {
|
|||
*/
|
||||
queryTsa(signature){
|
||||
var tsr = this.genTsrData(signature);
|
||||
var tu8s = Zga.rawToU8arr(tsr);
|
||||
var tu8s = z.rawToU8arr(tsr);
|
||||
var options = {
|
||||
"method": "POST",
|
||||
"headers": {"Content-Type": "application/timestamp-query"},
|
||||
"payload": tu8s,
|
||||
};
|
||||
var tblob = UrlFetchApp.fetch(this.tsainf.url, options).getBlob();
|
||||
var tstr = Zga.u8arrToRaw(new Uint8Array(tblob.getBytes()));
|
||||
var tstr = z.u8arrToRaw(new Uint8Array(tblob.getBytes()));
|
||||
var token = forge.asn1.fromDer(tstr).value[1];
|
||||
return token;
|
||||
}
|
||||
|
@ -453,9 +537,9 @@ PdfSigner: class {
|
|||
console.log(msg);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
VisualSignature: class {
|
||||
z.VisualSignature = class{
|
||||
/**
|
||||
* @constructor
|
||||
* @param {SignDrawInfo=} drawinf
|
||||
|
@ -667,31 +751,11 @@ VisualSignature: class {
|
|||
}
|
||||
return ret;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} uarr
|
||||
* @return {string}
|
||||
*/
|
||||
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}
|
||||
*/
|
||||
rawToU8arr: function(raw){
|
||||
var arr = new Uint8Array(raw.length);
|
||||
for(var i=0; i<raw.length; i++){
|
||||
arr[i] = raw.charCodeAt(i);
|
||||
}
|
||||
return arr;
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
if(!globalThis.Zga){
|
||||
globalThis.Zga = {};
|
||||
}
|
||||
supplyZgaSigner(globalThis.Zga);
|
||||
|
|
Loading…
Reference in New Issue