From 299aeb3419388d7dc8b3feb1e3b35847aa288f46 Mon Sep 17 00:00:00 2001 From: zboris12 Date: Thu, 6 Oct 2022 22:02:11 +0900 Subject: [PATCH] Added support to public-key certificate encryption. --- README.md | 70 ++++++++++++++----- closure/forge-ext.js | 35 +++++++++- closure/zb-externs.js | 8 +-- zgapdfcryptor.js | 153 +++++++++++++++++++++++++++++++++++------- zgapdfsigner.js | 140 +++++++++++++++++++++++--------------- 5 files changed, 303 insertions(+), 103 deletions(-) diff --git a/README.md b/README.md index 61570d7..0f518d7 100644 --- a/README.md +++ b/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. 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 -This tool supports signing with a timestamp from TSA(Time Stamp Authority), -but because of the CORS security restrictions in web browser, -this function can only be used in Google Apps Script. - -And because node-forge hasn't supported unauthenticated attributes in pkcs7 yet, +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, 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 @@ -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 /** @@ -152,13 +163,13 @@ fld.createFile(Utilities.newBlob(u8arr, "application/pdf").setName("signed_test. * __h__: number :point_right: Height * __pageidx__: number :point_right: (Optional) The page index for drawing the signature * __imgData__: Array|Uint8Array|ArrayBuffer|string :point_right: (Optional) The image's data - * __imgType__: string :point_right: (Optional) The image's type, __only support jpg and png__ - * __text__: string :point_right: (Optional) A text drawing on signature, __not implemented yet__ - * __fontData__: PDFLib.StandardFonts|Array|Uint8Array|ArrayBuffer|string :point_right: (Optional) The font's data for drawing text, __not implemented yet__ + * __imgType__: string :point_right: (Optional) The image's type, only support jpg and png + * __text__: string :point_right: (Optional) A text drawing on signature, not implemented yet + * __fontData__: PDFLib.StandardFonts|Array|Uint8Array|ArrayBuffer|string :point_right: (Optional) The font's data for drawing text, not implemented yet ## Let's protect the pdf -Set protection to the pdf. +Set password protection to the pdf. ```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} + */ +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. ```js @@ -217,24 +252,25 @@ async function sign1(pdf, cert, pwd, opwd){ * RC4_128: 128bit RC4 Encryption * AES_128: 128bit AES Encryption * AES_256: 256bit AES Encryption -* __permissions__: Array :point_right: (Optional) The set of permissions you want to block +* __permissions__: Array :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; * "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); * "fill-forms": Fill in existing interactive form fields (including signature fields), even if 'annot-forms' is not specified; * "extract": Extract text and graphics (in support of accessibility to users with disabilities or for other purposes); * "assemble": Assemble the document (insert, rotate, or delete pages and create bookmarks or thumbnail images), even if 'modify' is not set; * "print-high": Print the document to a representation from which a faithful digital copy of the PDF content could be generated. When this is not set, printing is limited to a low-level representation of the appearance, possibly of degraded quality. - * "owner": (inverted logic - only for public-key) when set permits change of encryption and enables all other permissions. * __userpwd__: string :point_right: (Optional) User password. Used when opening the pdf. * __ownerpwd__: string :point_right: (Optional) Owner password. If not specified, a random value is used. -* __pubkeys__: Array<_PubKeyInfo_> :point_right: (Optional) Array of recipients containing public-key certificates ('c') and permissions ('p'). Not supported yet. - * __c__: string :point_right: A public-key certificate - * __p__: Array :point_right: (Optional) Permissions +* __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 :point_right: (Optional) Permissions ## 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 diff --git a/closure/forge-ext.js b/closure/forge-ext.js index 080a54f..103e446 100644 --- a/closure/forge-ext.js +++ b/closure/forge-ext.js @@ -19,6 +19,11 @@ forge.util.ByteStringBuffer.prototype.getBytes = function(){}; * @return {forge.util.ByteStringBuffer} */ forge.util.ByteStringBuffer.prototype.putBytes = function(value){}; +/** + * @param {number} i + * @return {forge.util.ByteStringBuffer} + */ +forge.util.ByteStringBuffer.prototype.putInt16 = function(i){}; /** * @param {number} i * @return {forge.util.ByteStringBuffer} @@ -58,7 +63,7 @@ forge.util.createBuffer = function(input, encoding){}; */ forge.util.hexToBytes = function(hex){}; /** - * @param {string} value + * @param {string=} value * @return {string} */ forge.util.decodeUtf8 = function(value){}; @@ -153,7 +158,7 @@ forge_cert_issuer.prototype.attributes; /** * @typedef * {{ - * valueTagClass: (string|undefined), + * valueTagClass: (number|undefined), * type: (string|undefined), * value: (string|undefined), * }} @@ -178,6 +183,10 @@ forge.pkcs7 = function(){}; * @return {forge.pkcs7} */ forge.pkcs7.createSignedData = function(){}; +/** + * @return {forge.pkcs7} + */ +forge.pkcs7.createEnvelopedData = function(){}; /** * @param {forge_cert} cert */ @@ -190,6 +199,17 @@ forge.pkcs7.prototype.addSigner = function(signer){}; forge.pkcs7.prototype.signers; /** @type {Array} */ 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 * {{ @@ -258,6 +278,12 @@ forge.pki.oids.data; forge.pki.oids.messageDigest; /** @type {string} */ forge.pki.oids.signingTime; +/** + * @param {forge.asn1} obj + * @param {boolean=} computeHash + * @return {forge_cert} + */ +forge.pki.certificateFromAsn1 = function(obj, computeHash){}; forge.md = {}; /** @constructor */ @@ -273,11 +299,16 @@ forge.md.digest.prototype.update = function(msg, encoding){}; */ forge.md.digest.prototype.digest = function(){}; forge.md.md5 = {}; +forge.md.sha1 = {}; forge.md.sha256 = {}; /** * @return {forge.md.digest} */ forge.md.md5.create = function(){}; +/** + * @return {forge.md.digest} + */ +forge.md.sha1.create = function(){}; /** * @return {forge.md.digest} */ diff --git a/closure/zb-externs.js b/closure/zb-externs.js index 1b82355..c98846a 100644 --- a/closure/zb-externs.js +++ b/closure/zb-externs.js @@ -50,26 +50,26 @@ var SignOption; /** * @typedef * {{ - * c: string, + * c: (string|forge_cert|undefined), * p: (Array|undefined), * }} */ var PubKeyInfo; /** * 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; * 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); * fill-forms : Fill in existing interactive form fields (including signature fields), even if 'annot-forms' is not specified; * extract : Extract text and graphics (in support of accessibility to users with disabilities or for other purposes); * assemble : Assemble the document (insert, rotate, or delete pages and create bookmarks or thumbnail images), even if 'modify' is not set; * print-high : Print the document to a representation from which a faithful digital copy of the PDF content could be generated. When this is not set, printing is limited to a low-level representation of the appearance, possibly of degraded quality. - * owner : (inverted logic - only for public-key) when set permits change of encryption and enables all other permissions. * * ownerpwd: Owner password If not specified, a random value is used. * - * pubkeys: Array of recipients containing public-key certificates ('c') and permissions ('p'). + * 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 * {{ diff --git a/zgapdfcryptor.js b/zgapdfcryptor.js index 6064a11..d741720 100644 --- a/zgapdfcryptor.js +++ b/zgapdfcryptor.js @@ -49,6 +49,21 @@ z.rawToU8arr = function(raw){ 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 = { /** * @enum {number} @@ -64,14 +79,15 @@ z.Crypto = { * @enum {number} */ 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 "modify": 8, // bit 4 - "copy": 16, // bit 5 + "copy-extract": 16, // bit 5 "annot-forms": 32, // bit 6 "fill-forms": 256, // bit 9 "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 }, @@ -148,7 +164,7 @@ z.Crypto = { } for(i=0; i 0 || a_p <= 32){ // set only valid permissions if(a_p == 2){ @@ -212,13 +228,41 @@ z.Crypto = { * @param {number} protection 32bit encryption permission value (P value) * @return {string} */ - getEncPermissionsString: function(protection){ + getUserPermissionsString: function(protection){ /** @type {forge.util.ByteStringBuffer} */ var buff = new forge.util.ByteStringBuffer(); buff.putInt32Le(protection); return buff.getBytes(); }, + /** + * Return a string of permissions, high-order byte first. + * @param {Array=} 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. * @param {string} str input string @@ -241,7 +285,7 @@ z.Crypto = { _AES: function(key, txt){ // padding (RFC 2898, PKCS #5: Password-Based Cryptography Specification Version 2.0) /** @type {number} */ - var padding = 16 - (txt.length % 16); + var padding = 16 - (txt.length & 0x0F); /** @type {forge.util.ByteStringBuffer} */ var buff = forge.util.createBuffer(txt); buff.fillWithByte(padding, padding); @@ -295,8 +339,8 @@ z.PdfCryptor = class{ this.pubkeys = encopt.pubkeys; /** @private @type {z.Crypto.Mode} */ this.mode = /** @type {z.Crypto.Mode} */(encopt.mode); - /** @private @type {number} */ - this.protection = 0; + /** @private @type {Array|undefined} */ + this.permissions = encopt.permissions; /** @private @type {string} */ this.userpwd = ""; /** @private @type {string} */ @@ -349,6 +393,9 @@ z.PdfCryptor = class{ }; 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){ // public-Key Security requires at least 128 bit this.mode = z.Crypto.Mode.RC4_128; @@ -356,7 +403,6 @@ z.PdfCryptor = class{ this.Filter = "Adobe.PubSec"; this.StmF = "DefaultCryptFilter"; this.StrF = "DefaultCryptFilter"; - throw new Error("Public key mode is not supported yet."); } if(encopt.userpwd){ @@ -407,8 +453,6 @@ z.PdfCryptor = class{ default: 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(){ /** @type {number} */ var keybytelen = this.Length / 8; + /** @type {forge.md.digest} */ + var md = null; // standard mode if(!this.pubkeys){ + /** @type {number} */ + var protection = z.Crypto.getUserPermissionCode(this.permissions, this.mode); if(this.mode == z.Crypto.Mode.AES_256){ // generate 256 bit random key - /** @type {forge.md.digest} */ - var md = forge.md.sha256.create(); + md = forge.md.sha256.create(); md.update(z.Crypto.getRandomSeed()); this.key = md.digest().getBytes().substr(0, keybytelen); // truncate passwords @@ -831,10 +878,10 @@ z.PdfCryptor = class{ // Compute OE value this.OE = this._OEvalue(); // Compute P value - this.P = this.protection; + this.P = protection; // Computing the encryption dictionary's Perms (permissions) value /** @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 if(typeof this.CF.EncryptMetadata == "boolean" && !this.CF.EncryptMetadata){ // byte 8 perms += "F"; @@ -850,12 +897,12 @@ z.PdfCryptor = class{ this.ownerpwd = (this.ownerpwd + z.Crypto.EncPadding).substr(0, 32); // Compute O value this.O = this._Ovalue(); - // get default permissions (reverse byte order) + // get default permissions string (reverse byte order) /** @type {string} */ - var permissions = z.Crypto.getEncPermissionsString(this.protection); + var permissionstr = z.Crypto.getUserPermissionsString(protection); // Compute encryption key /** @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) { for(var i=0; i<50; i++){ tmp = z.Crypto._md5_16(tmp.substr(0, keybytelen)); @@ -865,10 +912,66 @@ z.PdfCryptor = class{ // Compute U value this.U = this._Uvalue(); // Compute P value - this.P = this.protection; + this.P = protection; } - }else{ // Public-Key mode - //TODO + // Public-Key mode + }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); } } }; diff --git a/zgapdfsigner.js b/zgapdfsigner.js index bf744b9..e44d0a9 100644 --- a/zgapdfsigner.js +++ b/zgapdfsigner.js @@ -25,6 +25,12 @@ z.PdfSigner = class{ this.DEFAULT_BYTE_RANGE_PLACEHOLDER = "**********"; /** @private @type {SignOption} */ this.opt = signopt; + /** @type {forge_key} */ + this.privateKey = null; + /** @type {Array} */ + this.certs = []; + /** @type {number} */ + this.certIdx = 0; /** @private @type {?TsaServiceInfo} */ this.tsainf = null; /** @private @type {number} */ @@ -103,7 +109,27 @@ z.PdfSigner = class{ this.addSignHolder(pdfdoc); 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.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} */ var cypt = new z.PdfCryptor(cypopt); pdfdoc = await cypt.encryptPdf(pdfdoc, true); @@ -274,6 +300,59 @@ z.PdfSigner = class{ return null; } + + /** + * @private + * @param {Array|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} */ + var certBags = p12.getBags({ + "bagType": forge.pki.oids.certBag, + })[forge.pki.oids.certBag]; + /** @type {Object} */ + 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 * @param {string} pdfstr @@ -323,70 +402,21 @@ z.PdfSigner = class{ // Remove the placeholder signature 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} */ - var certBags = p12.getBags({ - "bagType": forge.pki.oids.certBag, - })[forge.pki.oids.certBag]; - /** @type {Object} */ - 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. /** @type {forge.pkcs7} */ var p7 = forge.pkcs7.createSignedData(); // Start off by setting the content. p7.content = forge.util.createBuffer(pdfstr); - // Then add 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. - /** @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 all the certificates (-cacerts & -clcerts) to p7 + this.certs.forEach(function(a_cert){ + p7.addCertificate(a_cert); + }); // Add a sha256 signer. That's what Adobe.PPKLite adbe.pkcs7.detached expects. p7.addSigner({ - key: privateKey, - certificate: cert, + key: this.privateKey, + certificate: this.certs[this.certIdx], digestAlgorithm: forge.pki.oids.sha256, authenticatedAttributes: [ {