2022-09-17 14:55:09 +02:00
# ZgaPdfSigner
2022-10-02 14:45:06 +02:00
A javascript tool to sign a pdf or set protection of a pdf in web browser.
2022-09-18 09:49:56 +02:00
And it also can be used in Google Apps Script.
2022-09-17 14:55:09 +02:00
PS: __ZGA__ is the abbreviation of my father's name.
And I use this name to hope the merits from this application will be dedicated to my parents.
2022-10-06 15:02:11 +02:00
## Main features
* 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 timestamp from TSA(Time Stamp Authority). (Only in Google Apps Script)
* Set password protection to a pdf. Supported algorithms:
* 40bit RC4 Encryption
* 128bit RC4 Encryption
* 128bit AES Encryption
* 256bit AES Encryption
* Set public-key certificate protection to a pdf.
Supported algorithms are same as the password protection.
2022-09-24 06:59:30 +02:00
2022-10-06 15:02:11 +02:00
## About signing with TSA
2022-09-24 06:59:30 +02:00
2022-10-06 15:02:11 +02:00
Because of the CORS security restrictions in web browser,
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,
2022-09-24 06:59:30 +02:00
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.
2022-09-17 14:55:09 +02:00
## The Dependencies
* [pdf-lib ](https://pdf-lib.js.org/ )
* [node-forge ](https://github.com/digitalbazaar/forge )
## How to use this tool
Just import the dependencies and this tool.
```html
< script src = "https://unpkg.com/pdf-lib/dist/pdf-lib.min.js" type = "text/javascript" > < / script >
< script src = "https://unpkg.com/node-forge/dist/forge.min.js" type = "text/javascript" > < / script >
2022-10-04 14:50:22 +02:00
< script src = "https://github.com/zboris12/zgapdfsigner/releases/download/2.0.0/zgapdfsigner.min.js" type = "text/javascript" > < / script >
2022-09-17 14:55:09 +02:00
```
## Let's sign
Sign with an invisible signature.
```js
/**
* @param {ArrayBuffer} pdf
* @param {ArrayBuffer} cert
* @param {string} pwd
2022-09-17 15:08:29 +02:00
* @return {Promise< Blob > }
2022-09-17 14:55:09 +02:00
*/
async function sign1(pdf, cert, pwd){
2022-09-17 15:08:29 +02:00
/** @type {SignOption} */
var sopt = {
p12cert: cert,
pwd: pwd,
};
2022-09-18 09:49:56 +02:00
var signer = new Zga.PdfSigner(sopt);
2022-09-18 10:00:11 +02:00
var u8arr = await signer.sign(pdf);
return new Blob([u8arr], {"type" : "application/pdf"});
2022-09-17 14:55:09 +02:00
}
```
2022-10-06 15:02:11 +02:00
Sign with a visible signature of an image.
2022-09-17 14:55:09 +02:00
```js
/**
* @param {ArrayBuffer} pdf
* @param {ArrayBuffer} cert
* @param {string} pwd
* @param {ArrayBuffer} imgdat
* @param {string} imgtyp
2022-09-17 15:08:29 +02:00
* @return {Promise< Blob > }
2022-09-17 14:55:09 +02:00
*/
async function sign2(pdf, cert, pwd, imgdat, imgtyp){
2022-09-17 15:08:29 +02:00
/** @type {SignOption} */
var sopt = {
p12cert: cert,
pwd: pwd,
drawinf: {
area: {
x: 25, // left
y: 150, // top
w: 60, // width
h: 60, // height
},
imgData: imgdat,
imgType: imgtyp,
},
};
2022-09-18 09:49:56 +02:00
var signer = new Zga.PdfSigner(sopt);
2022-09-18 10:00:11 +02:00
var u8arr = await signer.sign(pdf);
return new Blob([u8arr], {"type" : "application/pdf"});
2022-09-17 14:55:09 +02:00
}
```
Sign with a visible signature of drawing a text.
```js
//TODO
```
2022-09-18 09:49:56 +02:00
Use it in Google Apps Script
```js
// Simulate setTimeout function for pdf-lib
function setTimeout(func, sleep){
Utilities.sleep(sleep);
func();
}
// Simulate window for node-forge
var window = globalThis;
// Load pdf-lib
eval(UrlFetchApp.fetch("https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js").getContentText());
// Load node-forge
2022-09-24 06:59:30 +02:00
eval(UrlFetchApp.fetch("https://github.com/zboris12/zgapdfsigner/releases/download/1.2.0/forge.min.edited.js").getContentText());
2022-09-18 09:49:56 +02:00
// Load ZgaPdfSigner
2022-10-04 14:50:22 +02:00
eval(UrlFetchApp.fetch("https://github.com/zboris12/zgapdfsigner/releases/download/2.0.0/zgapdfsigner.min.js").getContentText());
2022-09-18 09:49:56 +02:00
// Load pdf, certificate
var pdfBlob = DriveApp.getFilesByName("_test.pdf").next().getBlob();
var certBlob = DriveApp.getFilesByName("_test.pfx").next().getBlob();
// Sign the pdf
var sopt = {
2022-09-24 06:59:30 +02:00
p12cert: certBlob.getBytes(),
2022-09-18 09:49:56 +02:00
pwd: "some passphrase",
2022-09-24 06:59:30 +02:00
signdate: "1",
2022-09-18 09:49:56 +02:00
};
var signer = new Zga.PdfSigner(sopt);
2022-09-24 06:59:30 +02:00
var u8arr = await signer.sign(pdfBlob.getBytes());
2022-09-18 09:49:56 +02:00
// Save the result pdf to some folder
var fld = DriveApp.getFolderById("a folder's id");
fld.createFile(Utilities.newBlob(u8arr, "application/pdf").setName("signed_test.pdf"));
```
2022-09-17 14:55:09 +02:00
## Detail of SignOption
2022-09-24 06:59:30 +02:00
* __p12cert__: Array< number > |Uint8Array|ArrayBuffer|string :point_right: Certificate's data
2022-09-17 14:55:09 +02:00
* __pwd__: string :point_right: The passphrase of the certificate
* __reason__: string :point_right: (Optional) The reason for signing
* __location__: string :point_right: (Optional) Your location
* __contact__: string :point_right: (Optional) Your contact information
2022-09-24 06:59:30 +02:00
* __signdate__: Date|string|_TsaServiceInfo_ :point_right: (Optional)
* When it is a Date, it means the date and time for signing.
* When it is a string, it can be an url of TSA or an index of the preset TSA as below:
* "1": http://ts.ssl.com
* "2": http://timestamp.digicert.com
* "3": http://timestamp.sectigo.com
* "4": http://timestamp.entrust.net/TSS/RFC3161sha2TS
* "5": http://timestamp.apple.com/ts01
* "6": http://www.langedge.jp/tsa
* "7": https://freetsa.org/tsr
* When it is a _TsaServiceInfo_ , it means a full customized information of TSA.
* __url__: string :point_right: The url of TSA
* __len__: number :point_right: (Optional) The length of signature's placeholder
* __signame__: string :point_right: (Optional) The name of the signature
* __drawinf__: _SignDrawInfo_ :point_right: (Optional) Visible signature's information
* __area__: _SignAreaInfo_ :point_right: The signature's drawing area
2022-09-17 14:55:09 +02:00
* __x__: number :point_right: Distance from left
* __y__: number :point_right: Distance from top
* __w__: number :point_right: Width
* __h__: number :point_right: Height
* __pageidx__: number :point_right: (Optional) The page index for drawing the signature
2022-09-24 06:59:30 +02:00
* __imgData__: Array< number > |Uint8Array|ArrayBuffer|string :point_right: (Optional) The image's data
2022-10-06 15:02:11 +02:00
* __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 >
* __fontData__: PDFLib.StandardFonts|Array< number > |Uint8Array|ArrayBuffer|string :point_right: (Optional) The font's data for drawing text, < ins > not implemented yet</ ins >
2022-09-17 14:55:09 +02:00
2022-10-02 14:45:06 +02:00
## Let's protect the pdf
2022-10-06 15:02:11 +02:00
Set password protection to the pdf.
2022-10-02 14:45:06 +02:00
```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"});
}
```
2022-10-06 15:02:11 +02:00
Set public-key certificate protection to the pdf.
```js
/**
* @param {ArrayBuffer} pdf
* @param {ArrayBuffer} cert
* @return {Promise< Blob > }
*/
async function protect1(pdf, cert){
/** @type {EncryptOption} */
var eopt = {
mode: Zga.Crypto.Mode.AES_128,
pubkeys: [{
c: Zga.u8arrToRaw(new Uint8Array(cert)),
p: ["copy", "modify", "copy-extract", "annot-forms", "fill-forms", "extract", "assemble"],
}],
};
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"});
}
```
2022-10-02 14:45:06 +02:00
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
2022-10-06 15:02:11 +02:00
* __permissions__: Array< string > :point_right: (Optional) The set of permissions you want to block
* "copy": (Only valid on public-key mode) Copy text and graphics from the document;
2022-10-02 14:45:06 +02:00
* "print": Print the document;
* "modify": Modify the contents of the document by operations other than those controlled by 'fill-forms', 'extract' and 'assemble';
2022-10-06 15:02:11 +02:00
* "copy-extract": Copy or otherwise extract text and graphics from the document;
2022-10-02 14:45:06 +02:00
* "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.
* __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.
2022-10-06 15:02:11 +02:00
* __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.
Only if you want to encrypt the pdf by the certificate for signing, the c can be omitted.
* __p__: Array< string > :point_right: (Optional) Permissions
2022-10-02 14:45:06 +02:00
## Thanks
2022-10-06 15:02:11 +02:00
* The module of setting protection was almost migrated from [TCPDF ](http://www.tcpdf.org ).
2022-09-17 14:55:09 +02:00
## License
This tool is available under the
[MIT license ](https://opensource.org/licenses/MIT ).