Added support to DocMDP and improved the way of appending TSA timestamp.
parent
adc260b794
commit
fe2fd4840d
36
README.md
36
README.md
|
@ -9,6 +9,7 @@ And I use this name to hope the merits from this application will be dedicated t
|
||||||
|
|
||||||
* Sign a pdf with an invisible pkcs#7 signature.
|
* Sign a pdf with an invisible pkcs#7 signature.
|
||||||
* Sign a pdf with a visible pkcs#7 signature by drawing an image.
|
* Sign a pdf with a visible pkcs#7 signature by drawing an image.
|
||||||
|
* Sign a pdf and set DocMDP(document modification detection and prevention).
|
||||||
* Sign a pdf with a timestamp from TSA(Time Stamp Authority). (Only in Google Apps Script)
|
* Sign a pdf with a timestamp from TSA(Time Stamp Authority). (Only in Google Apps Script)
|
||||||
* Set password protection to a pdf. Supported algorithms:
|
* Set password protection to a pdf. Supported algorithms:
|
||||||
* 40bit RC4 Encryption
|
* 40bit RC4 Encryption
|
||||||
|
@ -22,8 +23,6 @@ And I use this name to hope the merits from this application will be dedicated t
|
||||||
|
|
||||||
Because of the CORS security restrictions in web browser,
|
Because of the CORS security restrictions in web browser,
|
||||||
signing with a timestamp from TSA can only be used in Google Apps Script.
|
signing with a timestamp from TSA can only be used in Google Apps Script.
|
||||||
And because [node-forge](https://github.com/digitalbazaar/forge) hasn't supported unauthenticated attributes in pkcs#7 yet,
|
|
||||||
so when use this function, [the edited version](https://github.com/zboris12/zgapdfsigner/releases/download/1.2.0/forge.min.edited.js) needs to be imported.
|
|
||||||
|
|
||||||
## The Dependencies
|
## The Dependencies
|
||||||
|
|
||||||
|
@ -34,9 +33,9 @@ so when use this function, [the edited version](https://github.com/zboris12/zgap
|
||||||
|
|
||||||
Just import the dependencies and this tool.
|
Just import the dependencies and this tool.
|
||||||
```html
|
```html
|
||||||
<script src="https://unpkg.com/pdf-lib/dist/pdf-lib.min.js" type="text/javascript"></script>
|
<script src="https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js" type="text/javascript"></script>
|
||||||
<script src="https://unpkg.com/node-forge/dist/forge.min.js" type="text/javascript"></script>
|
<script src="https://unpkg.com/node-forge@1.3.1/dist/forge.min.js" type="text/javascript"></script>
|
||||||
<script src="https://github.com/zboris12/zgapdfsigner/releases/download/2.0.0/zgapdfsigner.min.js" type="text/javascript"></script>
|
<script src="https://github.com/zboris12/zgapdfsigner/releases/download/2.2.0/zgapdfsigner.min.js" type="text/javascript"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Let's sign
|
## Let's sign
|
||||||
|
@ -55,6 +54,7 @@ async function sign1(pdf, cert, pwd){
|
||||||
var sopt = {
|
var sopt = {
|
||||||
p12cert: cert,
|
p12cert: cert,
|
||||||
pwd: pwd,
|
pwd: pwd,
|
||||||
|
permission: 1,
|
||||||
};
|
};
|
||||||
var signer = new Zga.PdfSigner(sopt);
|
var signer = new Zga.PdfSigner(sopt);
|
||||||
var u8arr = await signer.sign(pdf);
|
var u8arr = await signer.sign(pdf);
|
||||||
|
@ -114,9 +114,9 @@ var window = globalThis;
|
||||||
// Load pdf-lib
|
// Load pdf-lib
|
||||||
eval(UrlFetchApp.fetch("https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js").getContentText());
|
eval(UrlFetchApp.fetch("https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js").getContentText());
|
||||||
// Load node-forge
|
// Load node-forge
|
||||||
eval(UrlFetchApp.fetch("https://github.com/zboris12/zgapdfsigner/releases/download/1.2.0/forge.min.edited.js").getContentText());
|
eval(UrlFetchApp.fetch("https://unpkg.com/node-forge@1.3.1/dist/forge.min.js").getContentText());
|
||||||
// Load ZgaPdfSigner
|
// Load ZgaPdfSigner
|
||||||
eval(UrlFetchApp.fetch("https://github.com/zboris12/zgapdfsigner/releases/download/2.0.0/zgapdfsigner.min.js").getContentText());
|
eval(UrlFetchApp.fetch("https://github.com/zboris12/zgapdfsigner/releases/download/2.2.0/zgapdfsigner.min.js").getContentText());
|
||||||
|
|
||||||
// Load pdf, certificate
|
// Load pdf, certificate
|
||||||
var pdfBlob = DriveApp.getFilesByName("_test.pdf").next().getBlob();
|
var pdfBlob = DriveApp.getFilesByName("_test.pdf").next().getBlob();
|
||||||
|
@ -139,12 +139,17 @@ fld.createFile(Utilities.newBlob(u8arr, "application/pdf").setName("signed_test.
|
||||||
|
|
||||||
* __p12cert__: Array<number>|Uint8Array|ArrayBuffer|string :point_right: Certificate's data
|
* __p12cert__: Array<number>|Uint8Array|ArrayBuffer|string :point_right: Certificate's data
|
||||||
* __pwd__: string :point_right: The passphrase of the certificate
|
* __pwd__: string :point_right: The passphrase of the certificate
|
||||||
|
* __permission__: number :point_right: (Optional) The modification permissions granted for this document.
|
||||||
|
This is a setting of DocMDP(document modification detection and prevention). 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.
|
||||||
* __reason__: string :point_right: (Optional) The reason for signing
|
* __reason__: string :point_right: (Optional) The reason for signing
|
||||||
* __location__: string :point_right: (Optional) Your location
|
* __location__: string :point_right: (Optional) Your location
|
||||||
* __contact__: string :point_right: (Optional) Your contact information
|
* __contact__: string :point_right: (Optional) Your contact information
|
||||||
* __signdate__: Date|string|_TsaServiceInfo_ :point_right: (Optional)
|
* __signdate__: Date|string|_TsaServiceInfo_ :point_right: (Optional)
|
||||||
* When it is a Date, it means the date and time for signing.
|
* When it is a Date, it means the date and time of signing.
|
||||||
* When it is a string, it can be an url of TSA or an index of the preset TSA as below:
|
* When it is a string, it can be an url of TSA or an index of the preset TSAs as below:
|
||||||
* "1": http://ts.ssl.com
|
* "1": http://ts.ssl.com
|
||||||
* "2": http://timestamp.digicert.com
|
* "2": http://timestamp.digicert.com
|
||||||
* "3": http://timestamp.sectigo.com
|
* "3": http://timestamp.sectigo.com
|
||||||
|
@ -152,20 +157,21 @@ fld.createFile(Utilities.newBlob(u8arr, "application/pdf").setName("signed_test.
|
||||||
* "5": http://timestamp.apple.com/ts01
|
* "5": http://timestamp.apple.com/ts01
|
||||||
* "6": http://www.langedge.jp/tsa
|
* "6": http://www.langedge.jp/tsa
|
||||||
* "7": https://freetsa.org/tsr
|
* "7": https://freetsa.org/tsr
|
||||||
* When it is a _TsaServiceInfo_, it means a full customized information of TSA.
|
* When it is a _TsaServiceInfo_, it means a full customized information of a TSA.
|
||||||
* __url__: string :point_right: The url of TSA
|
* __url__: string :point_right: The url of TSA
|
||||||
* __len__: number :point_right: (Optional) The length of signature's placeholder
|
* __len__: number :point_right: (Optional) The length of signature's placeholder
|
||||||
|
* When it is omitted, the system timestamp will be used.
|
||||||
* __signame__: string :point_right: (Optional) The name of the signature
|
* __signame__: string :point_right: (Optional) The name of the signature
|
||||||
* __drawinf__: _SignDrawInfo_ :point_right: (Optional) Visible signature's information
|
* __drawinf__: _SignDrawInfo_ :point_right: (Optional) Visible signature's information
|
||||||
* __area__: _SignAreaInfo_ :point_right: The signature's drawing area
|
* __area__: _SignAreaInfo_ :point_right: The signature's drawing area, these numbers are dots on 72dpi.
|
||||||
* __x__: number :point_right: Distance from left
|
* __x__: number :point_right: Distance from left
|
||||||
* __y__: number :point_right: Distance from top
|
* __y__: number :point_right: Distance from top
|
||||||
* __w__: number :point_right: Width
|
* __w__: number :point_right: Width
|
||||||
* __h__: number :point_right: Height
|
* __h__: number :point_right: Height
|
||||||
* __pageidx__: number :point_right: (Optional) The page index for drawing the signature
|
* __pageidx__: number :point_right: (Optional) The index of a page where the signature will be drawn.
|
||||||
* __imgData__: Array<number>|Uint8Array|ArrayBuffer|string :point_right: (Optional) The image's data
|
* __imgData__: Array<number>|Uint8Array|ArrayBuffer|string :point_right: (Optional) The image's data
|
||||||
* __imgType__: string :point_right: (Optional) The image's type, <ins>only support jpg and png</ins>
|
* __imgType__: string :point_right: (Optional) The image's type, <ins>only support jpg and png</ins>
|
||||||
* __text__: string :point_right: (Optional) A text drawing on signature, <ins>not implemented yet</ins>
|
* __text__: string :point_right: (Optional) A text drawing for the signature, <ins>not implemented yet</ins>
|
||||||
* __fontData__: PDFLib.StandardFonts|Array<number>|Uint8Array|ArrayBuffer|string :point_right: (Optional) The font's data for drawing text, <ins>not implemented yet</ins>
|
* __fontData__: PDFLib.StandardFonts|Array<number>|Uint8Array|ArrayBuffer|string :point_right: (Optional) The font's data for drawing text, <ins>not implemented yet</ins>
|
||||||
|
|
||||||
## Let's protect the pdf
|
## Let's protect the pdf
|
||||||
|
@ -293,8 +299,8 @@ async function signAndProtect2(pdf, cert, pwd){
|
||||||
* __userpwd__: string :point_right: (Optional) User password. Used when opening the pdf.
|
* __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.
|
* __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').
|
* __pubkeys__: Array<_PubKeyInfo_> :point_right: (Optional) Array of recipients containing public-key certificates ('c') and permissions ('p').
|
||||||
* __c__: string|forge_cert :point_right: (Optional) A public-key certificate.
|
* __c__: Array<number>|Uint8Array|ArrayBuffer|string|forge_cert :point_right: (Optional) A public-key certificate.
|
||||||
Only if you want to encrypt the pdf by the certificate for signing, the c can be omitted.
|
Only when you want to encrypt the pdf by the certificate used in signing, the c can be omitted.
|
||||||
* __p__: Array<string> :point_right: (Optional) Permissions
|
* __p__: Array<string> :point_right: (Optional) Permissions
|
||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
|
|
|
@ -118,13 +118,15 @@ forge.asn1.Type.NULL;
|
||||||
/** @type {number} */
|
/** @type {number} */
|
||||||
forge.asn1.Type.OCTETSTRING;
|
forge.asn1.Type.OCTETSTRING;
|
||||||
forge.asn1.Class = {};
|
forge.asn1.Class = {};
|
||||||
/** @type {string} */
|
/** @type {number} */
|
||||||
forge.asn1.Class.UNIVERSAL;
|
forge.asn1.Class.UNIVERSAL;
|
||||||
|
/** @type {number} */
|
||||||
|
forge.asn1.Class.CONTEXT_SPECIFIC;
|
||||||
/**
|
/**
|
||||||
* @param {string} tagClass
|
* @param {number} tagClass
|
||||||
* @param {number} type
|
* @param {number} type
|
||||||
* @param {boolean} constructed
|
* @param {boolean} constructed
|
||||||
* @param {Array<string>} value
|
* @param {Array<string>|string} value
|
||||||
* @param {Object=} options
|
* @param {Object=} options
|
||||||
* @return {forge.asn1}
|
* @return {forge.asn1}
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -47,6 +47,10 @@ PDFLib.PDFDocument.prototype.embedPng = function(png){};
|
||||||
* @returns {Promise<PDFLib.PDFImage>}
|
* @returns {Promise<PDFLib.PDFImage>}
|
||||||
*/
|
*/
|
||||||
PDFLib.PDFDocument.prototype.embedJpg = function(jpg){};
|
PDFLib.PDFDocument.prototype.embedJpg = function(jpg){};
|
||||||
|
/**
|
||||||
|
* @returns {Promise<number>}
|
||||||
|
*/
|
||||||
|
PDFLib.PDFDocument.prototype.flush = function(){};
|
||||||
/** @type {PDFLib.PDFCatalog} */
|
/** @type {PDFLib.PDFCatalog} */
|
||||||
PDFLib.PDFDocument.prototype.catalog;
|
PDFLib.PDFDocument.prototype.catalog;
|
||||||
/** @type {PDFLib.PDFContext} */
|
/** @type {PDFLib.PDFContext} */
|
||||||
|
@ -95,6 +99,14 @@ PDFLib.PDFPageLeaf.prototype.set = function(name, object){};
|
||||||
PDFLib.PDFRef = function(){};
|
PDFLib.PDFRef = function(){};
|
||||||
/** @type {number} */
|
/** @type {number} */
|
||||||
PDFLib.PDFRef.prototype.objectNumber;
|
PDFLib.PDFRef.prototype.objectNumber;
|
||||||
|
/** @type {number} */
|
||||||
|
PDFLib.PDFRef.prototype.generationNumber;
|
||||||
|
/**
|
||||||
|
* @param {number} objectNumber
|
||||||
|
* @param {number=} generationNumber
|
||||||
|
* @return {PDFLib.PDFRef}
|
||||||
|
*/
|
||||||
|
PDFLib.PDFRef.of = function(objectNumber, generationNumber){};
|
||||||
|
|
||||||
/** @constructor */
|
/** @constructor */
|
||||||
PDFLib.PDFContext = function(){};
|
PDFLib.PDFContext = function(){};
|
||||||
|
@ -108,18 +120,40 @@ PDFLib.PDFContext = function(){};
|
||||||
var PdfObjEntry;
|
var PdfObjEntry;
|
||||||
/** @return {Array<PdfObjEntry>} */
|
/** @return {Array<PdfObjEntry>} */
|
||||||
PDFLib.PDFContext.prototype.enumerateIndirectObjects = function(){};
|
PDFLib.PDFContext.prototype.enumerateIndirectObjects = function(){};
|
||||||
/** @type {Object<string, *>} */
|
/**
|
||||||
|
* @typedef
|
||||||
|
* {{
|
||||||
|
* Root: PDFLib.PDFRef,
|
||||||
|
* ID: (PDFLib.PDFArray|undefined),
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
var PdfTrailerInfo;
|
||||||
|
/** @type {PdfTrailerInfo} */
|
||||||
PDFLib.PDFContext.prototype.trailerInfo;
|
PDFLib.PDFContext.prototype.trailerInfo;
|
||||||
|
/**
|
||||||
|
* @param {PDFLib.PDFRef} ref
|
||||||
|
* @param {PDFLib.PDFObject} object
|
||||||
|
*/
|
||||||
|
PDFLib.PDFContext.prototype.assign = function(ref, object){};
|
||||||
/**
|
/**
|
||||||
* @param {PDFLib.PDFObject} object
|
* @param {PDFLib.PDFObject} object
|
||||||
* @return {PDFLib.PDFRef}
|
* @return {PDFLib.PDFRef}
|
||||||
*/
|
*/
|
||||||
PDFLib.PDFContext.prototype.register = function(object){};
|
PDFLib.PDFContext.prototype.register = function(object){};
|
||||||
|
/**
|
||||||
|
* @return {PDFLib.PDFRef}
|
||||||
|
*/
|
||||||
|
PDFLib.PDFContext.prototype.nextRef = function(){};
|
||||||
/**
|
/**
|
||||||
* @param {*} literal
|
* @param {*} literal
|
||||||
* @return {PDFLib.PDFObject}
|
* @return {PDFLib.PDFObject}
|
||||||
*/
|
*/
|
||||||
PDFLib.PDFContext.prototype.obj = function(literal){};
|
PDFLib.PDFContext.prototype.obj = function(literal){};
|
||||||
|
/**
|
||||||
|
* @param {PDFLib.PDFRef} ref
|
||||||
|
* @return {PDFLib.PDFObject}
|
||||||
|
*/
|
||||||
|
PDFLib.PDFContext.prototype.lookup = function(ref){};
|
||||||
|
|
||||||
/** @constructor */
|
/** @constructor */
|
||||||
PDFLib.PDFObject = function(){};
|
PDFLib.PDFObject = function(){};
|
||||||
|
@ -152,6 +186,11 @@ PDFLib.PDFArray = function(context){};
|
||||||
* @param {PDFLib.PDFObject} object
|
* @param {PDFLib.PDFObject} object
|
||||||
*/
|
*/
|
||||||
PDFLib.PDFArray.prototype.push = function(object){};
|
PDFLib.PDFArray.prototype.push = function(object){};
|
||||||
|
/**
|
||||||
|
* @param {number} idx
|
||||||
|
* @return {PDFLib.PDFObject}
|
||||||
|
*/
|
||||||
|
PDFLib.PDFArray.prototype.get = function(idx){};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @constructor
|
* @constructor
|
||||||
|
@ -251,10 +290,32 @@ var PdfDrawimgOption;
|
||||||
*/
|
*/
|
||||||
PDFLib.drawImage = function(name, options){};
|
PDFLib.drawImage = function(name, options){};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
PDFLib.Cache = function(){};
|
||||||
|
/**
|
||||||
|
* @return {Uint8Array}
|
||||||
|
*/
|
||||||
|
PDFLib.Cache.prototype.access = function(){};
|
||||||
|
/** @type {Uint8Array} */
|
||||||
|
PDFLib.Cache.prototype.value;
|
||||||
/**
|
/**
|
||||||
* @constructor
|
* @constructor
|
||||||
* @extends {PDFLib.PDFObject}
|
* @extends {PDFLib.PDFObject}
|
||||||
*/
|
*/
|
||||||
|
PDFLib.PDFStream = function(){};
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
* @extends {PDFLib.PDFStream}
|
||||||
|
*/
|
||||||
|
PDFLib.PDFFlateStream = function(){};
|
||||||
|
/** @type {PDFLib.Cache} */
|
||||||
|
PDFLib.PDFFlateStream.prototype.contentsCache;
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
* @extends {PDFLib.PDFFlateStream}
|
||||||
|
*/
|
||||||
PDFLib.PDFContentStream = function(){};
|
PDFLib.PDFContentStream = function(){};
|
||||||
/**
|
/**
|
||||||
* @param {PDFLib.PDFObject} dict
|
* @param {PDFLib.PDFObject} dict
|
||||||
|
|
|
@ -32,10 +32,16 @@ var SignAreaInfo;
|
||||||
*/
|
*/
|
||||||
var SignDrawInfo;
|
var SignDrawInfo;
|
||||||
/**
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
* @typedef
|
* @typedef
|
||||||
* {{
|
* {{
|
||||||
* p12cert: (Array<number>|Uint8Array|ArrayBuffer|string),
|
* p12cert: (Array<number>|Uint8Array|ArrayBuffer|string),
|
||||||
* pwd: string,
|
* pwd: string,
|
||||||
|
* permission: (number|undefined),
|
||||||
* reason: (string|undefined),
|
* reason: (string|undefined),
|
||||||
* location: (string|undefined),
|
* location: (string|undefined),
|
||||||
* contact: (string|undefined),
|
* contact: (string|undefined),
|
||||||
|
@ -50,7 +56,7 @@ var SignOption;
|
||||||
/**
|
/**
|
||||||
* @typedef
|
* @typedef
|
||||||
* {{
|
* {{
|
||||||
* c: (string|forge_cert|undefined),
|
* c: (Array<number>|Uint8Array|ArrayBuffer|string|forge_cert|undefined),
|
||||||
* p: (Array<string>|undefined),
|
* p: (Array<string>|undefined),
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
|
@ -103,6 +109,17 @@ var CFType;
|
||||||
var RC4LastInfo;
|
var RC4LastInfo;
|
||||||
|
|
||||||
var Zga = {};
|
var Zga = {};
|
||||||
|
/**
|
||||||
|
* @param {Uint8Array} uarr
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
Zga.u8arrToRaw = function(uarr){};
|
||||||
|
/**
|
||||||
|
* @param {string} raw
|
||||||
|
* @return {Uint8Array}
|
||||||
|
*/
|
||||||
|
Zga.rawToU8arr = function(raw){};
|
||||||
|
|
||||||
Zga.Crypto = {};
|
Zga.Crypto = {};
|
||||||
/** @enum {number} */
|
/** @enum {number} */
|
||||||
Zga.Crypto.Mode = {
|
Zga.Crypto.Mode = {
|
||||||
|
@ -118,8 +135,19 @@ Zga.Crypto.Mode = {
|
||||||
Zga.PdfCryptor = function(encopt){};
|
Zga.PdfCryptor = function(encopt){};
|
||||||
/**
|
/**
|
||||||
* @param {PDFLib.PDFDocument|Array<number>|Uint8Array|ArrayBuffer|string} pdf
|
* @param {PDFLib.PDFDocument|Array<number>|Uint8Array|ArrayBuffer|string} pdf
|
||||||
* @param {boolean=} reload
|
* @param {PDFLib.PDFRef=} ref
|
||||||
* @return {Promise<PDFLib.PDFDocument>}
|
* @return {Promise<PDFLib.PDFDocument>}
|
||||||
*/
|
*/
|
||||||
Zga.PdfCryptor.prototype.encryptPdf = function(pdf, reload){};
|
Zga.PdfCryptor.prototype.encryptPdf = function(pdf, ref){};
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
* @param {SignOption} signopt
|
||||||
|
*/
|
||||||
|
Zga.PdfSigner = function(signopt){};
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
* @param {PDFLib.PDFDocument|Array<number>|Uint8Array|ArrayBuffer|string} pdf
|
||||||
|
* @param {EncryptOption=} cypopt
|
||||||
|
* @return {Promise<Uint8Array>}
|
||||||
|
*/
|
||||||
|
Zga.PdfSigner.prototype.sign = function(pdf, cypopt){};
|
||||||
|
|
File diff suppressed because one or more lines are too long
225
test.html
225
test.html
|
@ -4,22 +4,34 @@
|
||||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title> Test for ZgaPdfSigner </title>
|
<title> Test for ZgaPdfSigner </title>
|
||||||
<script src="https://unpkg.com/node-forge/dist/forge.min.js" type="text/javascript"></script>
|
<script src="https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js" type="text/javascript"></script>
|
||||||
<script src="https://unpkg.com/pdf-lib/dist/pdf-lib.min.js" type="text/javascript"></script>
|
<script src="https://unpkg.com/node-forge@1.3.1/dist/forge.min.js" type="text/javascript"></script>
|
||||||
<script src="zgapdfsigner.js" type="text/javascript"></script>
|
<script src="dist/zgapdfsigner.min.js" type="text/javascript"></script>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
function test(){
|
/**
|
||||||
var img = document.getElementById("img").files[0];
|
* @param {string} fid
|
||||||
if(img){
|
* @return {Promise<ArrayBuffer>}
|
||||||
readAsArrayBuffer(img, function(a_buff){
|
*/
|
||||||
testSign(a_buff, getFilExt(img));
|
function readFile(fid){
|
||||||
});
|
return new Promise((resolve, reject) => {
|
||||||
|
var f = document.getElementById(fid).files[0];
|
||||||
|
if(f){
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.onload = function(a_evt){
|
||||||
|
resolve(a_evt.target.result);
|
||||||
|
};
|
||||||
|
reader.readAsArrayBuffer(f);
|
||||||
}else{
|
}else{
|
||||||
testSign();
|
resolve(null);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
function getFilExt(f){
|
/**
|
||||||
var n = f.name;
|
* @param {string} fid
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
function getFilExt(fid){
|
||||||
|
var n = document.getElementById(fid).files[0].name;
|
||||||
var i = n.lastIndexOf(".");
|
var i = n.lastIndexOf(".");
|
||||||
if(i >= 0){
|
if(i >= 0){
|
||||||
return n.slice(i + 1);
|
return n.slice(i + 1);
|
||||||
|
@ -27,50 +39,189 @@ function getFilExt(f){
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function readAsArrayBuffer(f, func){
|
async function testMe(){
|
||||||
var reader = new FileReader();
|
/** @type {ArrayBuffer} */
|
||||||
reader.onload = function(a_evt){
|
var pdf = await readFile("fff");
|
||||||
func(a_evt.target.result);
|
if(!pdf){
|
||||||
|
alert("The target pdf is not specified.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {ArrayBuffer} */
|
||||||
|
var pfx = await readFile("kkk");
|
||||||
|
/** @type {string} */
|
||||||
|
var ps = document.getElementById("pwd").value;
|
||||||
|
if(pfx && !ps){
|
||||||
|
alert("The passphrase is not specified.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {ArrayBuffer} */
|
||||||
|
var img = await readFile("img");
|
||||||
|
/** @type {string} */
|
||||||
|
var imgType = "";
|
||||||
|
if(img){
|
||||||
|
imgType = getFilExt("img");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {ArrayBuffer} */
|
||||||
|
var pubcert = await readFile("pubcert");
|
||||||
|
/** @type {string} */
|
||||||
|
var upwd = document.getElementById("upwd").value;
|
||||||
|
|
||||||
|
/** @type {SignOption} */
|
||||||
|
var sopt = null;
|
||||||
|
if(pfx){
|
||||||
|
sopt = {
|
||||||
|
p12cert: pfx,
|
||||||
|
pwd: ps,
|
||||||
|
permission: parseInt(document.getElementById("sperm").value),
|
||||||
|
reason: document.getElementById("tReason").value,
|
||||||
|
location: document.getElementById("tLocation").value,
|
||||||
|
contact: document.getElementById("tContact").value,
|
||||||
|
debug: true,
|
||||||
};
|
};
|
||||||
reader.readAsArrayBuffer(f);
|
if(img){
|
||||||
}
|
sopt.drawinf = {
|
||||||
function testSign(imgdat, imgtyp){
|
|
||||||
var fil = document.getElementById("fff").files[0];
|
|
||||||
var kf = document.getElementById("kkk").files[0];
|
|
||||||
readAsArrayBuffer(fil, function(a_pdf){
|
|
||||||
readAsArrayBuffer(kf, async function(b_kfbuf){
|
|
||||||
var b_opt = {
|
|
||||||
p12cert: b_kfbuf,
|
|
||||||
pwd: document.getElementById("pwd").value,
|
|
||||||
};
|
|
||||||
if(imgdat){
|
|
||||||
b_opt.drawinf = {
|
|
||||||
area: {
|
area: {
|
||||||
x: 25, // left
|
x: 25, // left
|
||||||
y: 150, // top
|
y: 150, // top
|
||||||
w: 60,
|
w: 60,
|
||||||
h: 60,
|
h: 60,
|
||||||
},
|
},
|
||||||
imgData: imgdat,
|
// pageidx: 2,
|
||||||
imgType: imgtyp,
|
imgData: img,
|
||||||
|
imgType: imgType,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
var b_signer = new Zga.PdfSigner(b_opt);
|
}
|
||||||
var b_u8s = await b_signer.sign(a_pdf);
|
|
||||||
document.getElementById("download").download = "zzzs3.pdf";
|
/** @type {EncryptOption} */
|
||||||
document.getElementById("download").href = window.URL.createObjectURL(new Blob([b_u8s], {"type" : "application/pdf"}));
|
var eopt = undefined;
|
||||||
document.getElementById("download").click();
|
if(pubcert || upwd){
|
||||||
|
eopt = {
|
||||||
|
mode: parseInt(document.getElementById("mode").value),
|
||||||
|
permissions: [],
|
||||||
|
};
|
||||||
|
Array.from(document.getElementsByName("perms")).forEach((a_chk) => {
|
||||||
|
if(!a_chk.checked){
|
||||||
|
eopt.permissions.push(a_chk.value);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
if(pubcert){
|
||||||
|
eopt.pubkeys = [{
|
||||||
|
c: pubcert,
|
||||||
|
}];
|
||||||
|
}else if(upwd){
|
||||||
|
eopt.userpwd = upwd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {Uint8Array} */
|
||||||
|
var u8dat = null;
|
||||||
|
if(sopt){
|
||||||
|
/** @type {Zga.PdfSigner} */
|
||||||
|
var ser = new Zga.PdfSigner(sopt);
|
||||||
|
u8dat = await ser.sign(pdf, eopt);
|
||||||
|
}else if(eopt){
|
||||||
|
/** @type {Zga.PdfCryptor} */
|
||||||
|
var crypr = new Zga.PdfCryptor(eopt);
|
||||||
|
/** @type {PDFLib.PDFDocument} */
|
||||||
|
var pdfdoc = await crypr.encryptPdf(pdf);
|
||||||
|
u8dat = await pdfdoc.save({"useObjectStreams": false});
|
||||||
|
}else{
|
||||||
|
alert("Nothing to do.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("download").download = "test_test.pdf";
|
||||||
|
document.getElementById("download").href = window.URL.createObjectURL(new Blob([u8dat], {"type" : "application/pdf"}));
|
||||||
|
document.getElementById("download").click();
|
||||||
|
}
|
||||||
|
function test(){
|
||||||
|
testMe().catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
alert(err.message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
function clearFiles(){
|
||||||
|
["fff","kkk","img","pubcert"].forEach((a_id) => {
|
||||||
|
document.getElementById(a_id).value = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function changeSperm(){
|
||||||
|
/** @type {Element} */
|
||||||
|
var sel = window.event.currentTarget || window.event.srcElement || window.event.target;
|
||||||
|
/** @type {Element} */
|
||||||
|
var spn = document.getElementById("spcmt");
|
||||||
|
switch(sel.value){
|
||||||
|
case "1":
|
||||||
|
spn.innerText = "No changes to the document are permitted; any change to the document invalidates the signature.";
|
||||||
|
break;
|
||||||
|
case "2":
|
||||||
|
spn.innerText = "Permitted changes are filling in forms, instantiating page templates, and signing; other changes invalidate the signature.";
|
||||||
|
break;
|
||||||
|
case "3":
|
||||||
|
spn.innerText = "Permitted changes are the same as for 2, as well as annotation creation, deletion, and modification; other changes invalidate the signature.";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
spn.innerText = "";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
span.header {
|
||||||
|
font-weight: bold;
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<label>pdf </label><input type="file" id="fff" /><br />
|
<label>pdf </label><input type="file" id="fff" /><br />
|
||||||
|
<hr />
|
||||||
|
<span class="header">Sign the PDF:</span><br />
|
||||||
<label>certificate </label><input type="file" id="kkk" /><br />
|
<label>certificate </label><input type="file" id="kkk" /><br />
|
||||||
<label>passphrase </label><input type="password" id="pwd" /><br />
|
<label>passphrase </label><input type="password" id="pwd" /><br />
|
||||||
<label>signature image </label><input type="file" id="img" /><br />
|
<label>signature image </label><input type="file" id="img" /><br />
|
||||||
<a id="download" href="#" download="" style="display: none;" target="_blank">dummy</a>
|
<label>permission </label>
|
||||||
|
<select id="sperm" onchange="changeSperm()">
|
||||||
|
<option value="0">No DocMDP</option>
|
||||||
|
<option value="1">DocMDP pattern 1</option>
|
||||||
|
<option value="2">DocMDP pattern 2</option>
|
||||||
|
<option value="3">DocMDP pattern 3</option>
|
||||||
|
</select>
|
||||||
|
<span id="spcmt"></span><br />
|
||||||
|
<label>reason </label><input type="text" id="tReason" value="I have a test reason." /><br />
|
||||||
|
<label>location </label><input type="text" id="tLocation" value="I am on the earth." /><br />
|
||||||
|
<label>contact </label><input type="text" id="tContact" value="zga@zga.com" /><br />
|
||||||
|
<hr />
|
||||||
|
<span class="header">Encrypt the PDF:</span><br />
|
||||||
|
<label>encryption </label>
|
||||||
|
<select id="mode">
|
||||||
|
<option value="0">RC4-40</option>
|
||||||
|
<option value="1">RC4-128</option>
|
||||||
|
<option value="2">AES-128</option>
|
||||||
|
<option value="3">AES-256</option>
|
||||||
|
</select><br />
|
||||||
|
<label>user password </label><input type="password" id="upwd" /><br />
|
||||||
|
<label>public certificate </label><input type="file" id="pubcert" /><br />
|
||||||
|
<label>permissions: </label><br />
|
||||||
|
<input type="checkbox" id="pCopy" name="perms" value="copy" checked><label for="pCopy">"copy": Copy text and graphics from the document. (Only valid on public-key mode)</label><br />
|
||||||
|
<input type="checkbox" id="pPrint" name="perms" value="print" checked><label for="pPrint">"print": Print the document.</label><br />
|
||||||
|
<input type="checkbox" id="pModify" name="perms" value="modify" checked><label for="pModify">"modify": Modify the contents of the document by operations other than those controlled by 'fill-forms', 'extract' and 'assemble'.</label><br />
|
||||||
|
<input type="checkbox" id="pCopyExtract" name="perms" value="copy-extract" checked><label for="pCopyExtract">"copy-extract": Copy or otherwise extract text and graphics from the document.</label><br />
|
||||||
|
<input type="checkbox" id="pAnnotForms" name="perms" value="annot-forms" checked><label for="pAnnotForms">"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).</label><br />
|
||||||
|
<input type="checkbox" id="pFillForms" name="perms" value="fill-forms" checked><label for="pFillForms">"fill-forms": Fill in existing interactive form fields (including signature fields), even if 'annot-forms' is not specified.</label><br />
|
||||||
|
<input type="checkbox" id="pExtract" name="perms" value="extract" checked><label for="pExtract">"extract": Extract text and graphics (in support of accessibility to users with disabilities or for other purposes).</label><br />
|
||||||
|
<input type="checkbox" id="pAssemble" name="perms" value="assemble" checked><label for="pAssemble">"assemble": Assemble the document (insert, rotate, or delete pages and create bookmarks or thumbnail images), even if 'modify' is not set.</label><br />
|
||||||
|
<input type="checkbox" id="pPrintHigh" name="perms" value="print-high" checked><label for="pPrintHigh">"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.</label><br />
|
||||||
|
<hr />
|
||||||
<input type="button" value="test" onclick="test()" />
|
<input type="button" value="test" onclick="test()" />
|
||||||
|
<input type="button" value="clear files" onclick="clearFiles()" />
|
||||||
|
<a id="download" href="#" download="" style="display: none;" target="_blank">dummy</a>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -458,21 +458,14 @@ z.PdfCryptor = class{
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
* @param {PDFLib.PDFDocument|Array<number>|Uint8Array|ArrayBuffer|string} pdf
|
* @param {PDFLib.PDFDocument|Array<number>|Uint8Array|ArrayBuffer|string} pdf
|
||||||
* @param {boolean=} reload
|
* @param {PDFLib.PDFRef=} ref The unique reference will be assigned to the encryption information.
|
||||||
* @return {Promise<PDFLib.PDFDocument>}
|
* @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){
|
async encryptPdf(pdf, ref){
|
||||||
/** @type {PDFLib.PDFDocument} */
|
/** @type {PDFLib.PDFDocument} */
|
||||||
var pdfdoc = await z.loadPdf(pdf);
|
var pdfdoc = await z.loadPdf(pdf);
|
||||||
if(pdfdoc === pdf && reload){
|
if(pdfdoc === pdf && !ref){
|
||||||
// Temporaryly save the pdf and reload it to apply all changes.
|
await pdfdoc.flush();
|
||||||
/** @type {Uint8Array} */
|
|
||||||
var newpdf = await pdfdoc.save({"useObjectStreams": false});
|
|
||||||
pdfdoc = await PDFLib.PDFDocument.load(newpdf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {PDFLib.PDFContext} */
|
/** @type {PDFLib.PDFContext} */
|
||||||
|
@ -485,7 +478,13 @@ z.PdfCryptor = class{
|
||||||
* @param {PDFLib.PDFObject} a_val
|
* @param {PDFLib.PDFObject} a_val
|
||||||
*/
|
*/
|
||||||
var func = function(a_num, a_val){
|
var func = function(a_num, a_val){
|
||||||
if(a_val instanceof PDFLib.PDFStream){
|
if(a_val instanceof PDFLib.PDFContentStream){
|
||||||
|
/** @type {Uint8Array} */
|
||||||
|
var a_dat = a_val.contentsCache.access();
|
||||||
|
if(a_dat){
|
||||||
|
a_val.contentsCache.value = this.encryptU8arr(a_num, a_dat);
|
||||||
|
}
|
||||||
|
}else if(a_val instanceof PDFLib.PDFStream){
|
||||||
if(a_val.contents){
|
if(a_val.contents){
|
||||||
a_val.contents = this.encryptU8arr(a_num, a_val.contents);
|
a_val.contents = this.encryptU8arr(a_num, a_val.contents);
|
||||||
}
|
}
|
||||||
|
@ -513,7 +512,12 @@ z.PdfCryptor = class{
|
||||||
func(a_arr[0].objectNumber, a_arr[1]);
|
func(a_arr[0].objectNumber, a_arr[1]);
|
||||||
});
|
});
|
||||||
|
|
||||||
pdfcont.trailerInfo.Encrypt = pdfcont.register(trobj);
|
if(ref){
|
||||||
|
pdfcont.assign(ref, trobj);
|
||||||
|
}else{
|
||||||
|
ref = pdfcont.register(trobj);
|
||||||
|
}
|
||||||
|
pdfcont.trailerInfo.Encrypt = ref;
|
||||||
|
|
||||||
return pdfdoc;
|
return pdfdoc;
|
||||||
}
|
}
|
||||||
|
@ -938,13 +942,22 @@ z.PdfCryptor = class{
|
||||||
var a_envelope = seed + a_pkpermissionstr;
|
var a_envelope = seed + a_pkpermissionstr;
|
||||||
/** @type {forge_cert} */
|
/** @type {forge_cert} */
|
||||||
var a_cert = null;
|
var a_cert = null;
|
||||||
|
if(a_pubkey.c){
|
||||||
|
if(a_pubkey.c.issuer){
|
||||||
|
a_cert = /** @type {forge_cert} */(a_pubkey.c);
|
||||||
|
}else{
|
||||||
|
/** @type {string} */
|
||||||
|
var a_cerstr = "";
|
||||||
if(typeof a_pubkey.c == "string"){
|
if(typeof a_pubkey.c == "string"){
|
||||||
|
a_cerstr = a_pubkey.c;
|
||||||
|
}else{
|
||||||
|
a_cerstr = z.u8arrToRaw(new Uint8Array(/** @type {Array<number>|ArrayBuffer|Uint8Array} */(a_pubkey.c)));
|
||||||
|
}
|
||||||
/** @type {forge.asn1} */
|
/** @type {forge.asn1} */
|
||||||
var a_asn1 = forge.asn1.fromDer(a_pubkey.c);
|
var a_asn1 = forge.asn1.fromDer(a_cerstr);
|
||||||
a_cert = forge.pki.certificateFromAsn1(a_asn1);
|
a_cert = forge.pki.certificateFromAsn1(a_asn1);
|
||||||
z.fixCertAttributes(a_cert);
|
z.fixCertAttributes(a_cert);
|
||||||
}else if(a_pubkey.c){
|
}
|
||||||
a_cert = a_pubkey.c;
|
|
||||||
}else{
|
}else{
|
||||||
throw new Error("We need a certificate.");
|
throw new Error("We need a certificate.");
|
||||||
}
|
}
|
||||||
|
|
347
zgapdfsigner.js
347
zgapdfsigner.js
|
@ -16,6 +16,177 @@ z.TSAURLS = {
|
||||||
"7": {url: "https://freetsa.org/tsr", len: 14500},
|
"7": {url: "https://freetsa.org/tsr", len: 14500},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
a_new.changeNumber();
|
||||||
|
}.bind(this));
|
||||||
|
if(encref){
|
||||||
|
this.get(encref.tag).changeNumber();
|
||||||
|
}
|
||||||
|
return encref;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
restoreAll(){
|
||||||
|
/** @type {Iterator} */
|
||||||
|
var es = this.entries();
|
||||||
|
/** @type {IIterableResult} */
|
||||||
|
var result = es.next();
|
||||||
|
while(!result.done){
|
||||||
|
result.value[1].changeNumber(true);
|
||||||
|
result = es.next();
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @type {z.NewRefMap<string, z.NewRef>} */
|
||||||
|
z.newRefs = new z.NewRefMap();
|
||||||
|
|
||||||
z.PdfSigner = class{
|
z.PdfSigner = class{
|
||||||
/**
|
/**
|
||||||
* @param {SignOption} signopt
|
* @param {SignOption} signopt
|
||||||
|
@ -106,7 +277,14 @@ z.PdfSigner = class{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.addSignHolder(pdfdoc);
|
/** @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);
|
||||||
|
}
|
||||||
this.log("A signature holder has been added to the pdf.");
|
this.log("A signature holder has been added to the pdf.");
|
||||||
|
|
||||||
/** @type {forge_cert} */
|
/** @type {forge_cert} */
|
||||||
|
@ -132,9 +310,7 @@ z.PdfSigner = class{
|
||||||
}
|
}
|
||||||
/** @type {Zga.PdfCryptor} */
|
/** @type {Zga.PdfCryptor} */
|
||||||
var cypt = new z.PdfCryptor(cypopt);
|
var cypt = new z.PdfCryptor(cypopt);
|
||||||
pdfdoc = await cypt.encryptPdf(pdfdoc, true);
|
await cypt.encryptPdf(pdfdoc, encref);
|
||||||
// Because pdfdoc has been changed, so this.sigContents need to be found again.
|
|
||||||
this.sigContents = null;
|
|
||||||
this.log("Pdf data has been encrypted.");
|
this.log("Pdf data has been encrypted.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,9 +318,6 @@ z.PdfSigner = class{
|
||||||
var ret = await this.saveAndSign(pdfdoc);
|
var ret = await this.saveAndSign(pdfdoc);
|
||||||
if(!ret){
|
if(!ret){
|
||||||
this.log("Change size of signature's placeholder and retry.");
|
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);
|
this.sigContents.value = "0".repeat(this.siglen);
|
||||||
ret = await this.saveAndSign(pdfdoc);
|
ret = await this.saveAndSign(pdfdoc);
|
||||||
}
|
}
|
||||||
|
@ -154,6 +327,13 @@ z.PdfSigner = class{
|
||||||
throw new Error("Failed to sign the pdf.");
|
throw new Error("Failed to sign the pdf.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,14 +353,23 @@ z.PdfSigner = class{
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @param {PDFLib.PDFDocument} pdfdoc
|
* @param {PDFLib.PDFDocument} pdfdoc
|
||||||
|
* @return {boolean} DocMDP mode or not.
|
||||||
*/
|
*/
|
||||||
addSignHolder(pdfdoc){
|
addSignHolder(pdfdoc){
|
||||||
/** @const {z.VisualSignature} */
|
/** @const {number} */
|
||||||
const visign = new z.VisualSignature(this.opt.drawinf);
|
const docMdp = (this.opt.permission >= 1 && this.opt.permission <= 3) ? this.opt.permission : 0;
|
||||||
/** @const {PDFLib.PDFRef} */
|
/** @const {PDFLib.PDFContext} */
|
||||||
const strmRef = visign.createStream(pdfdoc, this.opt.signame);
|
const pdfcont = pdfdoc.context;
|
||||||
|
/** @const {z.SignatureCreator} */
|
||||||
|
const signcrt = new z.SignatureCreator(this.opt.drawinf);
|
||||||
/** @const {PDFLib.PDFPage} */
|
/** @const {PDFLib.PDFPage} */
|
||||||
const page = pdfdoc.getPages()[visign.getPageIndex()];
|
const page = pdfdoc.getPages()[signcrt.getPageIndex()];
|
||||||
|
/** @type {PDFLib.PDFRef} */
|
||||||
|
var strmRef = signcrt.createStream(pdfdoc, this.opt.signame);
|
||||||
|
|
||||||
|
if(docMdp && !strmRef){
|
||||||
|
strmRef = signcrt.createEmptyField(pdfcont);
|
||||||
|
}
|
||||||
|
|
||||||
/** @type {Date} */
|
/** @type {Date} */
|
||||||
var signdate = new Date();
|
var signdate = new Date();
|
||||||
|
@ -189,7 +378,7 @@ z.PdfSigner = class{
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {PDFLib.PDFArray} */
|
/** @type {PDFLib.PDFArray} */
|
||||||
var bytrng = new PDFLib.PDFArray(pdfdoc.context);
|
var bytrng = new PDFLib.PDFArray(pdfcont);
|
||||||
bytrng.push(PDFLib.PDFNumber.of(0));
|
bytrng.push(PDFLib.PDFNumber.of(0));
|
||||||
bytrng.push(PDFLib.PDFName.of(this.DEFAULT_BYTE_RANGE_PLACEHOLDER));
|
bytrng.push(PDFLib.PDFName.of(this.DEFAULT_BYTE_RANGE_PLACEHOLDER));
|
||||||
bytrng.push(PDFLib.PDFName.of(this.DEFAULT_BYTE_RANGE_PLACEHOLDER));
|
bytrng.push(PDFLib.PDFName.of(this.DEFAULT_BYTE_RANGE_PLACEHOLDER));
|
||||||
|
@ -206,12 +395,26 @@ z.PdfSigner = class{
|
||||||
"ByteRange": bytrng,
|
"ByteRange": bytrng,
|
||||||
"Contents": this.sigContents,
|
"Contents": this.sigContents,
|
||||||
"M": PDFLib.PDFString.fromDate(signdate),
|
"M": PDFLib.PDFString.fromDate(signdate),
|
||||||
"Prop_Build": pdfdoc.context.obj({
|
"Prop_Build": pdfcont.obj({
|
||||||
"App": pdfdoc.context.obj({
|
"App": pdfcont.obj({
|
||||||
"Name": "ZgaPdfSinger",
|
"Name": "ZgaPdfSinger",
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
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;
|
||||||
|
}
|
||||||
if(this.opt.reason){
|
if(this.opt.reason){
|
||||||
signObj["Reason"] = this.convToPDFString(this.opt.reason);
|
signObj["Reason"] = this.convToPDFString(this.opt.reason);
|
||||||
}
|
}
|
||||||
|
@ -221,85 +424,52 @@ z.PdfSigner = class{
|
||||||
if(this.opt.contact){
|
if(this.opt.contact){
|
||||||
signObj["ContactInfo"] = this.convToPDFString(this.opt.contact);
|
signObj["ContactInfo"] = this.convToPDFString(this.opt.contact);
|
||||||
}
|
}
|
||||||
var signatureDictRef = pdfdoc.context.register(pdfdoc.context.obj(signObj));
|
/** @type {PDFLib.PDFRef} */
|
||||||
|
var signatureDictRef = pdfcont.register(pdfcont.obj(signObj));
|
||||||
|
|
||||||
/** @type {Object<string, *>} */
|
/** @type {Object<string, *>} */
|
||||||
var widgetObj = {
|
var widgetObj = {
|
||||||
"Type": "Annot",
|
"Type": "Annot",
|
||||||
"Subtype": "Widget",
|
"Subtype": "Widget",
|
||||||
"FT": "Sig",
|
"FT": "Sig",
|
||||||
"Rect": visign.getSignRect(),
|
"Rect": signcrt.getSignRect(),
|
||||||
"V": signatureDictRef,
|
"V": signatureDictRef,
|
||||||
"T": this.convToPDFString(this.opt.signame ? this.opt.signame : "Signature1"),
|
"T": this.convToPDFString(this.opt.signame ? this.opt.signame : "Signature1"),
|
||||||
"F": 132,
|
"F": 132,
|
||||||
"P": page.ref,
|
"P": page.ref,
|
||||||
};
|
};
|
||||||
if(strmRef){
|
if(strmRef){
|
||||||
widgetObj["AP"] = pdfdoc.context.obj({
|
widgetObj["AP"] = pdfcont.obj({
|
||||||
"N": strmRef,
|
"N": strmRef,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
var widgetDictRef = pdfdoc.context.register(pdfdoc.context.obj(widgetObj));
|
/** @type {PDFLib.PDFRef} */
|
||||||
|
var widgetDictRef = pdfcont.register(pdfcont.obj(widgetObj));
|
||||||
|
|
||||||
// Add our signature widget to the page
|
// Add our signature widget to the page
|
||||||
page.node.set(PDFLib.PDFName.of("Annots"), pdfdoc.context.obj([widgetDictRef]));
|
page.node.set(PDFLib.PDFName.of("Annots"), pdfcont.obj([widgetDictRef]));
|
||||||
|
|
||||||
// Create an AcroForm object containing our signature widget
|
// Create an AcroForm object containing our signature widget
|
||||||
pdfdoc.catalog.set(
|
pdfdoc.catalog.set(
|
||||||
PDFLib.PDFName.of("AcroForm"),
|
PDFLib.PDFName.of("AcroForm"),
|
||||||
pdfdoc.context.obj({
|
pdfcont.obj({
|
||||||
"SigFlags": 3,
|
"SigFlags": 3,
|
||||||
"Fields": [widgetDictRef],
|
"Fields": [widgetDictRef],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
if(docMdp){
|
||||||
|
pdfdoc.catalog.set(
|
||||||
|
PDFLib.PDFName.of("Perms"),
|
||||||
|
pdfcont.obj({
|
||||||
|
"DocMDP": signatureDictRef,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @param {PDFLib.PDFDocument} pdfdoc
|
|
||||||
* @return {PDFLib.PDFHexString}
|
|
||||||
*/
|
|
||||||
findSigContents(pdfdoc){
|
|
||||||
/** @type {boolean} */
|
|
||||||
var istgt = false;
|
|
||||||
/** @type {PDFLib.PDFHexString} */
|
|
||||||
var sigContents = null;
|
|
||||||
/** @type {Array<PdfObjEntry>} */
|
|
||||||
var objarr = pdfdoc.context.enumerateIndirectObjects();
|
|
||||||
for(var i=objarr.length - 1; i>= 0; i--){
|
|
||||||
if(objarr[i][1].dict instanceof Map){
|
|
||||||
/** @type {Iterator<PdfObjEntry>} */
|
|
||||||
var es = objarr[i][1].dict.entries();
|
|
||||||
/** @type {IIterableResult<PdfObjEntry>} */
|
|
||||||
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{
|
}else{
|
||||||
res = es.next();
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
|
@ -431,23 +601,13 @@ z.PdfSigner = class{
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
if(this.tsainf){
|
|
||||||
//p7.signers[0].unauthenticatedAttributes.push({type: forge.pki.oids.timeStampToken, value: ""})
|
|
||||||
p7.signers[0].unauthenticatedAttributes.push({type: "1.2.840.113549.1.9.16.2.14", value: ""});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sign in detached mode.
|
// Sign in detached mode.
|
||||||
p7.sign({"detached": true});
|
p7.sign({"detached": true});
|
||||||
|
|
||||||
if(this.tsainf){
|
if(this.tsainf){
|
||||||
/** @type {forge.asn1} */
|
/** @type {forge.asn1} */
|
||||||
var tsatoken = this.queryTsa(p7.signers[0].signature);
|
var tsatoken = this.queryTsa(p7.signers[0].signature);
|
||||||
p7.signerInfos[0].value[6].value[0].value[1] = forge.asn1.create(
|
p7.signerInfos[0].value.push(tsatoken);
|
||||||
forge.asn1.Class.UNIVERSAL,
|
|
||||||
forge.asn1.Type.SET,
|
|
||||||
true,
|
|
||||||
[tsatoken]
|
|
||||||
);
|
|
||||||
this.log("Timestamp from " + this.tsainf.url + " has been added to the signature.");
|
this.log("Timestamp from " + this.tsainf.url + " has been added to the signature.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -571,6 +731,8 @@ z.PdfSigner = class{
|
||||||
* @return {forge.asn1}
|
* @return {forge.asn1}
|
||||||
*/
|
*/
|
||||||
queryTsa(signature){
|
queryTsa(signature){
|
||||||
|
/** @lends {forge.asn1} */
|
||||||
|
var asn1 = forge.asn1;
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
var tsr = this.genTsrData(signature);
|
var tsr = this.genTsrData(signature);
|
||||||
/** @type {Uint8Array} */
|
/** @type {Uint8Array} */
|
||||||
|
@ -586,8 +748,19 @@ z.PdfSigner = class{
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
var tstr = z.u8arrToRaw(new Uint8Array(tblob.getBytes()));
|
var tstr = z.u8arrToRaw(new Uint8Array(tblob.getBytes()));
|
||||||
/** @type {forge.asn1} */
|
/** @type {forge.asn1} */
|
||||||
var token = forge.asn1.fromDer(tstr).value[1];
|
var token = asn1.fromDer(tstr).value[1];
|
||||||
return token;
|
|
||||||
|
// 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]),
|
||||||
|
]),
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -601,7 +774,7 @@ z.PdfSigner = class{
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
z.VisualSignature = class{
|
z.SignatureCreator = class{
|
||||||
/**
|
/**
|
||||||
* @param {SignDrawInfo=} drawinf
|
* @param {SignDrawInfo=} drawinf
|
||||||
*/
|
*/
|
||||||
|
@ -637,6 +810,20 @@ z.VisualSignature = class{
|
||||||
return this.rect;
|
return this.rect;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
* @param {PDFLib.PDFDocument} pdfdoc
|
* @param {PDFLib.PDFDocument} pdfdoc
|
||||||
|
@ -711,7 +898,7 @@ z.VisualSignature = class{
|
||||||
"Resources": rscObj,
|
"Resources": rscObj,
|
||||||
});
|
});
|
||||||
/** @type {PDFLib.PDFContentStream} */
|
/** @type {PDFLib.PDFContentStream} */
|
||||||
var strm = PDFLib.PDFContentStream.of(frmDict, sigOprs, false);
|
var strm = PDFLib.PDFContentStream.of(frmDict, sigOprs, true);
|
||||||
return pdfdoc.context.register(strm);
|
return pdfdoc.context.register(strm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue