Added support to public-key certificate encryption.
parent
14ad61b5cf
commit
299aeb3419
70
README.md
70
README.md
|
@ -5,13 +5,24 @@ And it also can be used in Google Apps Script.
|
||||||
PS: __ZGA__ is the abbreviation of my father's name.
|
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.
|
And I use this name to hope the merits from this application will be dedicated to my parents.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
## About signing with TSA
|
## About signing with TSA
|
||||||
|
|
||||||
This tool supports signing with a timestamp from TSA(Time Stamp Authority),
|
Because of the CORS security restrictions in web browser,
|
||||||
but because of the CORS security restrictions in web browser,
|
signing with a timestamp from TSA can only be used in Google Apps Script.
|
||||||
this function 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,
|
||||||
|
|
||||||
And because node-forge hasn't supported unauthenticated attributes in pkcs7 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.
|
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
|
||||||
|
@ -51,7 +62,7 @@ async function sign1(pdf, cert, pwd){
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Sign with a visible signature of a picture.
|
Sign with a visible signature of an image.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
/**
|
/**
|
||||||
|
@ -152,13 +163,13 @@ fld.createFile(Utilities.newBlob(u8arr, "application/pdf").setName("signed_test.
|
||||||
* __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 page index for drawing the signature
|
||||||
* __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, __only support jpg and png__
|
* __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, __not implemented yet__
|
* __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, __not implemented yet__
|
* __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
|
||||||
|
|
||||||
Set protection to the pdf.
|
Set password protection to the pdf.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
/**
|
/**
|
||||||
|
@ -182,6 +193,30 @@ async function protect1(pdf, upwd, opwd){
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
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"});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Sign and set protection.
|
Sign and set protection.
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
@ -217,24 +252,25 @@ async function sign1(pdf, cert, pwd, opwd){
|
||||||
* RC4_128: 128bit RC4 Encryption
|
* RC4_128: 128bit RC4 Encryption
|
||||||
* AES_128: 128bit AES Encryption
|
* AES_128: 128bit AES Encryption
|
||||||
* AES_256: 256bit AES Encryption
|
* AES_256: 256bit AES Encryption
|
||||||
* __permissions__: Array<Zga.Crypto.Permission> :point_right: (Optional) The set of permissions you want to block
|
* __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;
|
||||||
* "print": Print the document;
|
* "print": Print the document;
|
||||||
* "modify": Modify the contents of the document by operations other than those controlled by 'fill-forms', 'extract' and 'assemble';
|
* "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;
|
* "copy-extract": 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);
|
* "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;
|
* "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);
|
* "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;
|
* "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.
|
* "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.
|
* __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'). <ins>Not supported yet.</ins>
|
* __pubkeys__: Array<_PubKeyInfo_> :point_right: (Optional) Array of recipients containing public-key certificates ('c') and permissions ('p').
|
||||||
* __c__: string :point_right: A public-key certificate
|
* __c__: string|forge_cert :point_right: (Optional) A public-key certificate.
|
||||||
* __p__: Array<Zga.Crypto.Permission> :point_right: (Optional) Permissions
|
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
|
||||||
|
|
||||||
## Thanks
|
## Thanks
|
||||||
* The module of setting protection was migrated from [TCPDF](http://www.tcpdf.org).
|
* The module of setting protection was almost migrated from [TCPDF](http://www.tcpdf.org).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,11 @@ forge.util.ByteStringBuffer.prototype.getBytes = function(){};
|
||||||
* @return {forge.util.ByteStringBuffer}
|
* @return {forge.util.ByteStringBuffer}
|
||||||
*/
|
*/
|
||||||
forge.util.ByteStringBuffer.prototype.putBytes = function(value){};
|
forge.util.ByteStringBuffer.prototype.putBytes = function(value){};
|
||||||
|
/**
|
||||||
|
* @param {number} i
|
||||||
|
* @return {forge.util.ByteStringBuffer}
|
||||||
|
*/
|
||||||
|
forge.util.ByteStringBuffer.prototype.putInt16 = function(i){};
|
||||||
/**
|
/**
|
||||||
* @param {number} i
|
* @param {number} i
|
||||||
* @return {forge.util.ByteStringBuffer}
|
* @return {forge.util.ByteStringBuffer}
|
||||||
|
@ -58,7 +63,7 @@ forge.util.createBuffer = function(input, encoding){};
|
||||||
*/
|
*/
|
||||||
forge.util.hexToBytes = function(hex){};
|
forge.util.hexToBytes = function(hex){};
|
||||||
/**
|
/**
|
||||||
* @param {string} value
|
* @param {string=} value
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
forge.util.decodeUtf8 = function(value){};
|
forge.util.decodeUtf8 = function(value){};
|
||||||
|
@ -153,7 +158,7 @@ forge_cert_issuer.prototype.attributes;
|
||||||
/**
|
/**
|
||||||
* @typedef
|
* @typedef
|
||||||
* {{
|
* {{
|
||||||
* valueTagClass: (string|undefined),
|
* valueTagClass: (number|undefined),
|
||||||
* type: (string|undefined),
|
* type: (string|undefined),
|
||||||
* value: (string|undefined),
|
* value: (string|undefined),
|
||||||
* }}
|
* }}
|
||||||
|
@ -178,6 +183,10 @@ forge.pkcs7 = function(){};
|
||||||
* @return {forge.pkcs7}
|
* @return {forge.pkcs7}
|
||||||
*/
|
*/
|
||||||
forge.pkcs7.createSignedData = function(){};
|
forge.pkcs7.createSignedData = function(){};
|
||||||
|
/**
|
||||||
|
* @return {forge.pkcs7}
|
||||||
|
*/
|
||||||
|
forge.pkcs7.createEnvelopedData = function(){};
|
||||||
/**
|
/**
|
||||||
* @param {forge_cert} cert
|
* @param {forge_cert} cert
|
||||||
*/
|
*/
|
||||||
|
@ -190,6 +199,17 @@ forge.pkcs7.prototype.addSigner = function(signer){};
|
||||||
forge.pkcs7.prototype.signers;
|
forge.pkcs7.prototype.signers;
|
||||||
/** @type {Array<forge.asn1>} */
|
/** @type {Array<forge.asn1>} */
|
||||||
forge.pkcs7.prototype.signerInfos;
|
forge.pkcs7.prototype.signerInfos;
|
||||||
|
/**
|
||||||
|
* @param {forge_cert} cert
|
||||||
|
*/
|
||||||
|
forge.pkcs7.prototype.addRecipient = function(cert){};
|
||||||
|
/** @type {forge.util.ByteStringBuffer} */
|
||||||
|
forge.pkcs7.prototype.content;
|
||||||
|
/**
|
||||||
|
* @param {forge.util.ByteStringBuffer=} key
|
||||||
|
* @param {string=} cipher
|
||||||
|
*/
|
||||||
|
forge.pkcs7.prototype.encrypt = function(key, cipher){};
|
||||||
/**
|
/**
|
||||||
* @typedef
|
* @typedef
|
||||||
* {{
|
* {{
|
||||||
|
@ -258,6 +278,12 @@ forge.pki.oids.data;
|
||||||
forge.pki.oids.messageDigest;
|
forge.pki.oids.messageDigest;
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
forge.pki.oids.signingTime;
|
forge.pki.oids.signingTime;
|
||||||
|
/**
|
||||||
|
* @param {forge.asn1} obj
|
||||||
|
* @param {boolean=} computeHash
|
||||||
|
* @return {forge_cert}
|
||||||
|
*/
|
||||||
|
forge.pki.certificateFromAsn1 = function(obj, computeHash){};
|
||||||
|
|
||||||
forge.md = {};
|
forge.md = {};
|
||||||
/** @constructor */
|
/** @constructor */
|
||||||
|
@ -273,11 +299,16 @@ forge.md.digest.prototype.update = function(msg, encoding){};
|
||||||
*/
|
*/
|
||||||
forge.md.digest.prototype.digest = function(){};
|
forge.md.digest.prototype.digest = function(){};
|
||||||
forge.md.md5 = {};
|
forge.md.md5 = {};
|
||||||
|
forge.md.sha1 = {};
|
||||||
forge.md.sha256 = {};
|
forge.md.sha256 = {};
|
||||||
/**
|
/**
|
||||||
* @return {forge.md.digest}
|
* @return {forge.md.digest}
|
||||||
*/
|
*/
|
||||||
forge.md.md5.create = function(){};
|
forge.md.md5.create = function(){};
|
||||||
|
/**
|
||||||
|
* @return {forge.md.digest}
|
||||||
|
*/
|
||||||
|
forge.md.sha1.create = function(){};
|
||||||
/**
|
/**
|
||||||
* @return {forge.md.digest}
|
* @return {forge.md.digest}
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -50,26 +50,26 @@ var SignOption;
|
||||||
/**
|
/**
|
||||||
* @typedef
|
* @typedef
|
||||||
* {{
|
* {{
|
||||||
* c: string,
|
* c: (string|forge_cert|undefined),
|
||||||
* p: (Array<string>|undefined),
|
* p: (Array<string>|undefined),
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
var PubKeyInfo;
|
var PubKeyInfo;
|
||||||
/**
|
/**
|
||||||
* permissions: The set of permissions (specify the ones you want to block):
|
* permissions: The set of permissions (specify the ones you want to block):
|
||||||
|
* copy : (Only valid on public-key mode) Copy text and graphics from the document;
|
||||||
* print : Print the document;
|
* print : Print the document;
|
||||||
* modify : Modify the contents of the document by operations other than those controlled by 'fill-forms', 'extract' and 'assemble';
|
* 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;
|
* copy-extract : 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);
|
* 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;
|
* 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);
|
* 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;
|
* 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.
|
* 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.
|
* ownerpwd: Owner password If not specified, a random value is used.
|
||||||
*
|
*
|
||||||
* pubkeys: Array of recipients containing public-key certificates ('c') and permissions ('p').
|
* pubkeys: Array of recipients containing public-key certificates ('c') and permissions ('p'). If want to encrypt the pdf by the certificate of signing, just apply a PubKeyInfo without c.
|
||||||
*
|
*
|
||||||
* @typedef
|
* @typedef
|
||||||
* {{
|
* {{
|
||||||
|
|
153
zgapdfcryptor.js
153
zgapdfcryptor.js
|
@ -49,6 +49,21 @@ z.rawToU8arr = function(raw){
|
||||||
return arr;
|
return arr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When converting to asn1, forge will encode the value of issuer to utf8 if the valueTagClass is UTF8.
|
||||||
|
* But the value load from a file which is DER format, is already utf8 encoded,
|
||||||
|
* so the encoding action will break the final data.
|
||||||
|
* To avoid the broken data issue, we decode the value before the later actions.
|
||||||
|
* @param {forge_cert} cert
|
||||||
|
*/
|
||||||
|
z.fixCertAttributes = function(cert){
|
||||||
|
cert.issuer.attributes.forEach(function(a_ele){
|
||||||
|
if(a_ele.valueTagClass === forge.asn1.Type.UTF8){
|
||||||
|
a_ele.value = forge.util.decodeUtf8(a_ele.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
z.Crypto = {
|
z.Crypto = {
|
||||||
/**
|
/**
|
||||||
* @enum {number}
|
* @enum {number}
|
||||||
|
@ -64,14 +79,15 @@ z.Crypto = {
|
||||||
* @enum {number}
|
* @enum {number}
|
||||||
*/
|
*/
|
||||||
Permission: {
|
Permission: {
|
||||||
"owner": 2, // bit 2 -- inverted logic: cleared by default
|
// "owner": 2, // bit 2 -- inverted logic: cleared by default
|
||||||
|
"copy": 2, // bit 2 -- Only valid on public-key mode
|
||||||
"print": 4, // bit 3
|
"print": 4, // bit 3
|
||||||
"modify": 8, // bit 4
|
"modify": 8, // bit 4
|
||||||
"copy": 16, // bit 5
|
"copy-extract": 16, // bit 5
|
||||||
"annot-forms": 32, // bit 6
|
"annot-forms": 32, // bit 6
|
||||||
"fill-forms": 256, // bit 9
|
"fill-forms": 256, // bit 9
|
||||||
"extract": 512, // bit 10
|
"extract": 512, // bit 10
|
||||||
"assemble": 1024,// bit 11
|
"assemble": 1024,// bit 11 -- On public-key mode, it means adding, deleting or rotating pages.
|
||||||
"print-high": 2048 // bit 12
|
"print-high": 2048 // bit 12
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -148,7 +164,7 @@ z.Crypto = {
|
||||||
}
|
}
|
||||||
for(i=0; i<rc4.length; i++){
|
for(i=0; i<rc4.length; i++){
|
||||||
t = rc4[i];
|
t = rc4[i];
|
||||||
j = (j + t + k.charCodeAt(i)) % 256;
|
j = (j + t + k.charCodeAt(i)) & 0xFF;
|
||||||
rc4[i] = rc4[j];
|
rc4[i] = rc4[j];
|
||||||
rc4[j] = t;
|
rc4[j] = t;
|
||||||
}
|
}
|
||||||
|
@ -166,12 +182,12 @@ z.Crypto = {
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
var out = "";
|
var out = "";
|
||||||
for(i=0; i<len; i++){
|
for(i=0; i<len; i++){
|
||||||
a = (a + 1) % 256;
|
a = (a + 1) & 0xFF;
|
||||||
t = rc4[a];
|
t = rc4[a];
|
||||||
b = (b + t) % 256;
|
b = (b + t) & 0xFF;
|
||||||
rc4[a] = rc4[b];
|
rc4[a] = rc4[b];
|
||||||
rc4[b] = t;
|
rc4[b] = t;
|
||||||
k = rc4[(rc4[a] + rc4[b]) % 256];
|
k = rc4[(rc4[a] + rc4[b]) & 0xFF];
|
||||||
out += String.fromCharCode(txt.charCodeAt(i) ^ k);
|
out += String.fromCharCode(txt.charCodeAt(i) ^ k);
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
|
@ -191,7 +207,7 @@ z.Crypto = {
|
||||||
permissions.forEach(function(a_itm){
|
permissions.forEach(function(a_itm){
|
||||||
/** @type {number} */
|
/** @type {number} */
|
||||||
var a_p = z.Crypto.Permission[a_itm];
|
var a_p = z.Crypto.Permission[a_itm];
|
||||||
if(a_p){
|
if(a_p && a_itm != "copy"){
|
||||||
if(mode > 0 || a_p <= 32){
|
if(mode > 0 || a_p <= 32){
|
||||||
// set only valid permissions
|
// set only valid permissions
|
||||||
if(a_p == 2){
|
if(a_p == 2){
|
||||||
|
@ -212,13 +228,41 @@ z.Crypto = {
|
||||||
* @param {number} protection 32bit encryption permission value (P value)
|
* @param {number} protection 32bit encryption permission value (P value)
|
||||||
* @return {string}
|
* @return {string}
|
||||||
*/
|
*/
|
||||||
getEncPermissionsString: function(protection){
|
getUserPermissionsString: function(protection){
|
||||||
/** @type {forge.util.ByteStringBuffer} */
|
/** @type {forge.util.ByteStringBuffer} */
|
||||||
var buff = new forge.util.ByteStringBuffer();
|
var buff = new forge.util.ByteStringBuffer();
|
||||||
buff.putInt32Le(protection);
|
buff.putInt32Le(protection);
|
||||||
return buff.getBytes();
|
return buff.getBytes();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a string of permissions, high-order byte first.
|
||||||
|
* @param {Array<string>=} permissions the set of permissions (specify the ones you want to block).
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
getCertPermissionsString: function(permissions){
|
||||||
|
/**
|
||||||
|
* Although the permissions is 4 bytes(32 bits), the first 2 bytes are 0xFF,0xFF,
|
||||||
|
* so we only check the last 2 bytes.
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
var protection = 65535; // 16 bit: (11111111 11111111)
|
||||||
|
if(permissions){
|
||||||
|
permissions.forEach(function(a_itm){
|
||||||
|
/** @type {number} */
|
||||||
|
var a_p = z.Crypto.Permission[a_itm];
|
||||||
|
if(a_p){
|
||||||
|
protection -= a_p;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/** @type {forge.util.ByteStringBuffer} */
|
||||||
|
var buff = new forge.util.ByteStringBuffer();
|
||||||
|
buff.fillWithByte(0xFF, 2);
|
||||||
|
buff.putInt16(protection);
|
||||||
|
return buff.getBytes();
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encrypts a string using MD5 and returns it's value as a binary string.
|
* Encrypts a string using MD5 and returns it's value as a binary string.
|
||||||
* @param {string} str input string
|
* @param {string} str input string
|
||||||
|
@ -241,7 +285,7 @@ z.Crypto = {
|
||||||
_AES: function(key, txt){
|
_AES: function(key, txt){
|
||||||
// padding (RFC 2898, PKCS #5: Password-Based Cryptography Specification Version 2.0)
|
// padding (RFC 2898, PKCS #5: Password-Based Cryptography Specification Version 2.0)
|
||||||
/** @type {number} */
|
/** @type {number} */
|
||||||
var padding = 16 - (txt.length % 16);
|
var padding = 16 - (txt.length & 0x0F);
|
||||||
/** @type {forge.util.ByteStringBuffer} */
|
/** @type {forge.util.ByteStringBuffer} */
|
||||||
var buff = forge.util.createBuffer(txt);
|
var buff = forge.util.createBuffer(txt);
|
||||||
buff.fillWithByte(padding, padding);
|
buff.fillWithByte(padding, padding);
|
||||||
|
@ -295,8 +339,8 @@ z.PdfCryptor = class{
|
||||||
this.pubkeys = encopt.pubkeys;
|
this.pubkeys = encopt.pubkeys;
|
||||||
/** @private @type {z.Crypto.Mode} */
|
/** @private @type {z.Crypto.Mode} */
|
||||||
this.mode = /** @type {z.Crypto.Mode} */(encopt.mode);
|
this.mode = /** @type {z.Crypto.Mode} */(encopt.mode);
|
||||||
/** @private @type {number} */
|
/** @private @type {Array<string>|undefined} */
|
||||||
this.protection = 0;
|
this.permissions = encopt.permissions;
|
||||||
/** @private @type {string} */
|
/** @private @type {string} */
|
||||||
this.userpwd = "";
|
this.userpwd = "";
|
||||||
/** @private @type {string} */
|
/** @private @type {string} */
|
||||||
|
@ -349,6 +393,9 @@ z.PdfCryptor = class{
|
||||||
};
|
};
|
||||||
|
|
||||||
if(this.pubkeys){
|
if(this.pubkeys){
|
||||||
|
if(this.pubkeys.length == 0){
|
||||||
|
throw new Error("Public key information is not specified.");
|
||||||
|
}
|
||||||
if(this.mode == z.Crypto.Mode.RC4_40){
|
if(this.mode == z.Crypto.Mode.RC4_40){
|
||||||
// public-Key Security requires at least 128 bit
|
// public-Key Security requires at least 128 bit
|
||||||
this.mode = z.Crypto.Mode.RC4_128;
|
this.mode = z.Crypto.Mode.RC4_128;
|
||||||
|
@ -356,7 +403,6 @@ z.PdfCryptor = class{
|
||||||
this.Filter = "Adobe.PubSec";
|
this.Filter = "Adobe.PubSec";
|
||||||
this.StmF = "DefaultCryptFilter";
|
this.StmF = "DefaultCryptFilter";
|
||||||
this.StrF = "DefaultCryptFilter";
|
this.StrF = "DefaultCryptFilter";
|
||||||
throw new Error("Public key mode is not supported yet.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(encopt.userpwd){
|
if(encopt.userpwd){
|
||||||
|
@ -407,8 +453,6 @@ z.PdfCryptor = class{
|
||||||
default:
|
default:
|
||||||
throw new Error("Unknown crypto mode. " + this.mode);
|
throw new Error("Unknown crypto mode. " + this.mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.protection = z.Crypto.getUserPermissionCode(encopt.permissions, this.mode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -811,12 +855,15 @@ z.PdfCryptor = class{
|
||||||
_generateencryptionkey(){
|
_generateencryptionkey(){
|
||||||
/** @type {number} */
|
/** @type {number} */
|
||||||
var keybytelen = this.Length / 8;
|
var keybytelen = this.Length / 8;
|
||||||
|
/** @type {forge.md.digest} */
|
||||||
|
var md = null;
|
||||||
// standard mode
|
// standard mode
|
||||||
if(!this.pubkeys){
|
if(!this.pubkeys){
|
||||||
|
/** @type {number} */
|
||||||
|
var protection = z.Crypto.getUserPermissionCode(this.permissions, this.mode);
|
||||||
if(this.mode == z.Crypto.Mode.AES_256){
|
if(this.mode == z.Crypto.Mode.AES_256){
|
||||||
// generate 256 bit random key
|
// generate 256 bit random key
|
||||||
/** @type {forge.md.digest} */
|
md = forge.md.sha256.create();
|
||||||
var md = forge.md.sha256.create();
|
|
||||||
md.update(z.Crypto.getRandomSeed());
|
md.update(z.Crypto.getRandomSeed());
|
||||||
this.key = md.digest().getBytes().substr(0, keybytelen);
|
this.key = md.digest().getBytes().substr(0, keybytelen);
|
||||||
// truncate passwords
|
// truncate passwords
|
||||||
|
@ -831,10 +878,10 @@ z.PdfCryptor = class{
|
||||||
// Compute OE value
|
// Compute OE value
|
||||||
this.OE = this._OEvalue();
|
this.OE = this._OEvalue();
|
||||||
// Compute P value
|
// Compute P value
|
||||||
this.P = this.protection;
|
this.P = protection;
|
||||||
// Computing the encryption dictionary's Perms (permissions) value
|
// Computing the encryption dictionary's Perms (permissions) value
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
var perms = z.Crypto.getEncPermissionsString(this.protection); // bytes 0-3
|
var perms = z.Crypto.getUserPermissionsString(protection); // bytes 0-3
|
||||||
perms += String.fromCharCode(255).repeat(4); // bytes 4-7
|
perms += String.fromCharCode(255).repeat(4); // bytes 4-7
|
||||||
if(typeof this.CF.EncryptMetadata == "boolean" && !this.CF.EncryptMetadata){ // byte 8
|
if(typeof this.CF.EncryptMetadata == "boolean" && !this.CF.EncryptMetadata){ // byte 8
|
||||||
perms += "F";
|
perms += "F";
|
||||||
|
@ -850,12 +897,12 @@ z.PdfCryptor = class{
|
||||||
this.ownerpwd = (this.ownerpwd + z.Crypto.EncPadding).substr(0, 32);
|
this.ownerpwd = (this.ownerpwd + z.Crypto.EncPadding).substr(0, 32);
|
||||||
// Compute O value
|
// Compute O value
|
||||||
this.O = this._Ovalue();
|
this.O = this._Ovalue();
|
||||||
// get default permissions (reverse byte order)
|
// get default permissions string (reverse byte order)
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
var permissions = z.Crypto.getEncPermissionsString(this.protection);
|
var permissionstr = z.Crypto.getUserPermissionsString(protection);
|
||||||
// Compute encryption key
|
// Compute encryption key
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
var tmp = z.Crypto._md5_16(this.userpwd + this.O + permissions + this.fileid);
|
var tmp = z.Crypto._md5_16(this.userpwd + this.O + permissionstr + this.fileid);
|
||||||
if(this.mode > z.Crypto.Mode.RC4_40) {
|
if(this.mode > z.Crypto.Mode.RC4_40) {
|
||||||
for(var i=0; i<50; i++){
|
for(var i=0; i<50; i++){
|
||||||
tmp = z.Crypto._md5_16(tmp.substr(0, keybytelen));
|
tmp = z.Crypto._md5_16(tmp.substr(0, keybytelen));
|
||||||
|
@ -865,10 +912,66 @@ z.PdfCryptor = class{
|
||||||
// Compute U value
|
// Compute U value
|
||||||
this.U = this._Uvalue();
|
this.U = this._Uvalue();
|
||||||
// Compute P value
|
// Compute P value
|
||||||
this.P = this.protection;
|
this.P = protection;
|
||||||
}
|
}
|
||||||
}else{ // Public-Key mode
|
// Public-Key mode
|
||||||
//TODO
|
}else{
|
||||||
|
// random 20-byte seed
|
||||||
|
md = forge.md.sha1.create();
|
||||||
|
md.update(z.Crypto.getRandomSeed());
|
||||||
|
/** @type {string} */
|
||||||
|
var seed = md.digest().getBytes();
|
||||||
|
/** @type {string} */
|
||||||
|
var recipient_bytes = "";
|
||||||
|
/** @type {string} */
|
||||||
|
var pkpermissionstr = z.Crypto.getCertPermissionsString(this.permissions);
|
||||||
|
// for each public certificate
|
||||||
|
this.pubkeys.forEach(function(/** @type {PubKeyInfo} */a_pubkey){
|
||||||
|
// get permissions string
|
||||||
|
/** @type {string} */
|
||||||
|
var a_pkpermissionstr = pkpermissionstr;
|
||||||
|
if(a_pubkey.p){
|
||||||
|
a_pkpermissionstr = z.Crypto.getCertPermissionsString(a_pubkey.p);
|
||||||
|
}
|
||||||
|
// envelope data
|
||||||
|
/** @type {string} */
|
||||||
|
var a_envelope = seed + a_pkpermissionstr;
|
||||||
|
/** @type {forge_cert} */
|
||||||
|
var a_cert = null;
|
||||||
|
if(typeof a_pubkey.c == "string"){
|
||||||
|
/** @type {forge.asn1} */
|
||||||
|
var a_asn1 = forge.asn1.fromDer(a_pubkey.c);
|
||||||
|
a_cert = forge.pki.certificateFromAsn1(a_asn1);
|
||||||
|
z.fixCertAttributes(a_cert);
|
||||||
|
}else if(a_pubkey.c){
|
||||||
|
a_cert = a_pubkey.c;
|
||||||
|
}else{
|
||||||
|
throw new Error("We need a certificate.");
|
||||||
|
}
|
||||||
|
// create a p7 enveloped message
|
||||||
|
/** @type {forge.pkcs7} */
|
||||||
|
var a_p7 = forge.pkcs7.createEnvelopedData();
|
||||||
|
// add a recipient
|
||||||
|
a_p7.addRecipient(a_cert);
|
||||||
|
// set content
|
||||||
|
a_p7.content = forge.util.createBuffer(a_envelope);
|
||||||
|
// encrypt
|
||||||
|
a_p7.encrypt();
|
||||||
|
/** @type {forge.util.ByteStringBuffer} */
|
||||||
|
var a_signature = forge.asn1.toDer(a_p7.toAsn1());
|
||||||
|
/** @type {string} */
|
||||||
|
var a_hexsignature = a_signature.toHex();
|
||||||
|
this.Recipients.push(a_hexsignature);
|
||||||
|
recipient_bytes += a_signature.getBytes();
|
||||||
|
}.bind(this));
|
||||||
|
// calculate encryption key
|
||||||
|
if(this.mode == z.Crypto.Mode.AES_256){
|
||||||
|
md = forge.md.sha256.create();
|
||||||
|
}else{ // RC4-40, RC4-128, AES-128
|
||||||
|
md = forge.md.sha1.create();
|
||||||
|
}
|
||||||
|
md.update(seed + recipient_bytes);
|
||||||
|
this.key = md.digest().getBytes().substr(0, keybytelen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
136
zgapdfsigner.js
136
zgapdfsigner.js
|
@ -25,6 +25,12 @@ z.PdfSigner = class{
|
||||||
this.DEFAULT_BYTE_RANGE_PLACEHOLDER = "**********";
|
this.DEFAULT_BYTE_RANGE_PLACEHOLDER = "**********";
|
||||||
/** @private @type {SignOption} */
|
/** @private @type {SignOption} */
|
||||||
this.opt = signopt;
|
this.opt = signopt;
|
||||||
|
/** @type {forge_key} */
|
||||||
|
this.privateKey = null;
|
||||||
|
/** @type {Array<forge_cert>} */
|
||||||
|
this.certs = [];
|
||||||
|
/** @type {number} */
|
||||||
|
this.certIdx = 0;
|
||||||
/** @private @type {?TsaServiceInfo} */
|
/** @private @type {?TsaServiceInfo} */
|
||||||
this.tsainf = null;
|
this.tsainf = null;
|
||||||
/** @private @type {number} */
|
/** @private @type {number} */
|
||||||
|
@ -103,7 +109,27 @@ z.PdfSigner = class{
|
||||||
this.addSignHolder(pdfdoc);
|
this.addSignHolder(pdfdoc);
|
||||||
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} */
|
||||||
|
var cert = this.loadP12cert(this.opt.p12cert, this.opt.pwd);
|
||||||
|
if(cert){
|
||||||
|
z.fixCertAttributes(cert);
|
||||||
|
}
|
||||||
|
|
||||||
if(cypopt){
|
if(cypopt){
|
||||||
|
if(cypopt.pubkeys){
|
||||||
|
if(cypopt.pubkeys.length == 0){
|
||||||
|
cypopt.pubkeys.push({
|
||||||
|
c: cert,
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
cypopt.pubkeys.forEach(function(/** @type {PubKeyInfo} */a_pubkey){
|
||||||
|
// If there is no c in the PubKeyInfo, set cert to it.
|
||||||
|
if(!a_pubkey.c){
|
||||||
|
a_pubkey.c = cert;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
/** @type {Zga.PdfCryptor} */
|
/** @type {Zga.PdfCryptor} */
|
||||||
var cypt = new z.PdfCryptor(cypopt);
|
var cypt = new z.PdfCryptor(cypopt);
|
||||||
pdfdoc = await cypt.encryptPdf(pdfdoc, true);
|
pdfdoc = await cypt.encryptPdf(pdfdoc, true);
|
||||||
|
@ -274,6 +300,59 @@ z.PdfSigner = class{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {Array<number>|Uint8Array|ArrayBuffer|string} p12cert
|
||||||
|
* @param {string} pwd
|
||||||
|
* @return {forge_cert}
|
||||||
|
*/
|
||||||
|
loadP12cert(p12cert, pwd){
|
||||||
|
// load P12 certificate
|
||||||
|
if(typeof p12cert !== "string"){
|
||||||
|
p12cert = z.u8arrToRaw(new Uint8Array(p12cert));
|
||||||
|
}
|
||||||
|
// Convert Buffer P12 to a forge implementation.
|
||||||
|
/** @type {forge.asn1} */
|
||||||
|
var p12Asn1 = forge.asn1.fromDer(p12cert);
|
||||||
|
/** @type {forge.pkcs12} */
|
||||||
|
var p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, true, pwd);
|
||||||
|
// Extract safe bags by type.
|
||||||
|
// We will need all the certificates and the private key.
|
||||||
|
/** @type {Object<string|number, P12Bag>} */
|
||||||
|
var certBags = p12.getBags({
|
||||||
|
"bagType": forge.pki.oids.certBag,
|
||||||
|
})[forge.pki.oids.certBag];
|
||||||
|
/** @type {Object<string|number, P12Bag>} */
|
||||||
|
var keyBags = p12.getBags({
|
||||||
|
"bagType": forge.pki.oids.pkcs8ShroudedKeyBag,
|
||||||
|
})[forge.pki.oids.pkcs8ShroudedKeyBag];
|
||||||
|
this.privateKey = keyBags[0].key;
|
||||||
|
if(certBags){
|
||||||
|
// Get all the certificates (-cacerts & -clcerts)
|
||||||
|
// Keep track of the last found client certificate.
|
||||||
|
// This will be the public key that will be bundled in the signature.
|
||||||
|
Object.keys(certBags).forEach(function(a_ele){
|
||||||
|
/** @type {forge_cert} */
|
||||||
|
var a_cert = certBags[a_ele].cert;
|
||||||
|
|
||||||
|
this.certs.push(a_cert);
|
||||||
|
|
||||||
|
// Try to find the certificate that matches the private key.
|
||||||
|
if(this.privateKey.n.compareTo(a_cert.publicKey.n) === 0
|
||||||
|
&& this.privateKey.e.compareTo(a_cert.publicKey.e) === 0){
|
||||||
|
this.certIdx = this.certs.length;
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
}
|
||||||
|
if(this.certIdx > 0){
|
||||||
|
return this.certs[--this.certIdx];
|
||||||
|
// z.fixCertAttributes(this.certs[this.certIdx]);
|
||||||
|
}else{
|
||||||
|
throw new Error("Failed to find a certificate.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @param {string} pdfstr
|
* @param {string} pdfstr
|
||||||
|
@ -323,70 +402,21 @@ z.PdfSigner = class{
|
||||||
// Remove the placeholder signature
|
// Remove the placeholder signature
|
||||||
pdfstr = pdfstr.slice(0, byteRange[1]) + pdfstr.slice(byteRange[2], byteRange[2] + byteRange[3]);
|
pdfstr = pdfstr.slice(0, byteRange[1]) + pdfstr.slice(byteRange[2], byteRange[2] + byteRange[3]);
|
||||||
|
|
||||||
if(typeof this.opt.p12cert !== "string"){
|
|
||||||
this.opt.p12cert = z.u8arrToRaw(new Uint8Array(this.opt.p12cert));
|
|
||||||
}
|
|
||||||
// Convert Buffer P12 to a forge implementation.
|
|
||||||
/** @type {forge.asn1} */
|
|
||||||
var p12Asn1 = forge.asn1.fromDer(this.opt.p12cert);
|
|
||||||
/** @type {forge.pkcs12} */
|
|
||||||
var p12 = forge.pkcs12.pkcs12FromAsn1(p12Asn1, true, this.opt.pwd);
|
|
||||||
|
|
||||||
// Extract safe bags by type.
|
|
||||||
// We will need all the certificates and the private key.
|
|
||||||
/** @type {Object<string|number, P12Bag>} */
|
|
||||||
var certBags = p12.getBags({
|
|
||||||
"bagType": forge.pki.oids.certBag,
|
|
||||||
})[forge.pki.oids.certBag];
|
|
||||||
/** @type {Object<string|number, P12Bag>} */
|
|
||||||
var keyBags = p12.getBags({
|
|
||||||
"bagType": forge.pki.oids.pkcs8ShroudedKeyBag,
|
|
||||||
})[forge.pki.oids.pkcs8ShroudedKeyBag];
|
|
||||||
|
|
||||||
/** @type {forge_key} */
|
|
||||||
var privateKey = keyBags[0].key;
|
|
||||||
// Here comes the actual PKCS#7 signing.
|
// Here comes the actual PKCS#7 signing.
|
||||||
/** @type {forge.pkcs7} */
|
/** @type {forge.pkcs7} */
|
||||||
var p7 = forge.pkcs7.createSignedData();
|
var p7 = forge.pkcs7.createSignedData();
|
||||||
// Start off by setting the content.
|
// Start off by setting the content.
|
||||||
p7.content = forge.util.createBuffer(pdfstr);
|
p7.content = forge.util.createBuffer(pdfstr);
|
||||||
|
|
||||||
// Then add all the certificates (-cacerts & -clcerts)
|
// Add all the certificates (-cacerts & -clcerts) to p7
|
||||||
// Keep track of the last found client certificate.
|
this.certs.forEach(function(a_cert){
|
||||||
// This will be the public key that will be bundled in the signature.
|
|
||||||
/** @type {forge_cert} */
|
|
||||||
var cert = null;
|
|
||||||
if(certBags){
|
|
||||||
Object.keys(certBags).forEach(function(a_ele){
|
|
||||||
/** @type {forge_cert} */
|
|
||||||
var a_cert = certBags[a_ele].cert;
|
|
||||||
|
|
||||||
p7.addCertificate(a_cert);
|
p7.addCertificate(a_cert);
|
||||||
|
|
||||||
// Try to find the certificate that matches the private key.
|
|
||||||
if(privateKey.n.compareTo(a_cert.publicKey.n) === 0
|
|
||||||
&& privateKey.e.compareTo(a_cert.publicKey.e) === 0){
|
|
||||||
cert = a_cert;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
if(cert){
|
|
||||||
// When converting to asn1, forge will encode the value of issuer to utf8 if the valueTagClass is UTF8.
|
|
||||||
// But the value load from pfx is already utf8 encoded, so the encoding action will break the final signature.
|
|
||||||
// To avoid the broken signature issue, we decode the value before the other actions.
|
|
||||||
cert.issuer.attributes.forEach(function(a_ele, a_idx, a_arr){
|
|
||||||
if(a_ele.valueTagClass === forge.asn1.Type.UTF8){
|
|
||||||
a_ele.value = forge.util.decodeUtf8(a_ele.value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}else{
|
|
||||||
throw new Error("Failed to find a certificate.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a sha256 signer. That's what Adobe.PPKLite adbe.pkcs7.detached expects.
|
// Add a sha256 signer. That's what Adobe.PPKLite adbe.pkcs7.detached expects.
|
||||||
p7.addSigner({
|
p7.addSigner({
|
||||||
key: privateKey,
|
key: this.privateKey,
|
||||||
certificate: cert,
|
certificate: this.certs[this.certIdx],
|
||||||
digestAlgorithm: forge.pki.oids.sha256,
|
digestAlgorithm: forge.pki.oids.sha256,
|
||||||
authenticatedAttributes: [
|
authenticatedAttributes: [
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue