Added support to public-key certificate encryption.

pull/2/head
zboris12 2022-10-06 22:02:11 +09:00
parent 14ad61b5cf
commit 299aeb3419
5 changed files with 303 additions and 103 deletions

View File

@ -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

View File

@ -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}
*/ */

View File

@ -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
* {{ * {{

View File

@ -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);
} }
} }
}; };

View File

@ -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. p7.addCertificate(a_cert);
/** @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);
// 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: [
{ {