diff --git a/README.md b/README.md index 7be8fb7..9976d78 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ZgaPdfSigner A javascript tool to sign a pdf or set protection of a pdf in web browser. -And it also can be used in Google Apps Script. +And it also can be used in Google Apps Script and nodejs. 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. @@ -11,7 +11,8 @@ And I use this name to hope the merits from this application will be dedicated t * Sign a pdf with a visible pkcs#7 signature by drawing an image. * Sign a pdf and set DocMDP(document modification detection and prevention). * Add a new signature to a pdf if it has been signed already. (An incremental update) -* Sign a pdf with a timestamp from TSA(Time Stamp Authority). (Only in Google Apps Script) +* Add a document timestamp from TSA(Time Stamp Authority). (Only in Google Apps Script and nodejs) +* Sign a pdf with a timestamp from TSA. (Only in Google Apps Script and nodejs) * Set password protection to a pdf. Supported algorithms: * 40bit RC4 Encryption * 128bit RC4 Encryption @@ -138,8 +139,8 @@ fld.createFile(Utilities.newBlob(u8arr, "application/pdf").setName("signed_test. ## Detail of SignOption -* __p12cert__: Array|Uint8Array|ArrayBuffer|string :point_right: Certificate's data -* __pwd__: string :point_right: The passphrase of the certificate +* __p12cert__: Array|Uint8Array|ArrayBuffer|string :point_right: (Optional) Certificate's data. In the case of adding a document timestamp, it must be omitted. +* __pwd__: string :point_right: (Optional) The passphrase of the certificate. In the case of adding a document timestamp, it must be omitted. * __permission__: number :point_right: (Optional) The modification permissions granted for this document. This is a setting of DocMDP(document modification detection and prevention). Valid values are: * 1: No changes to the document are permitted; any change to the document invalidates the signature. @@ -148,7 +149,7 @@ fld.createFile(Utilities.newBlob(u8arr, "application/pdf").setName("signed_test. * __reason__: string :point_right: (Optional) The reason for signing * __location__: string :point_right: (Optional) Your location * __contact__: string :point_right: (Optional) Your contact information -* __signdate__: Date|string|_TsaServiceInfo_ :point_right: (Optional) +* __signdate__: Date|string|_TsaServiceInfo_ :point_right: (Optional) In the case of adding a document timestamp, it can't be omitted and can't be a Date. * When it is a Date, it means the date and time of signing. * When it is a string, it can be an url of TSA or an index of the preset TSAs as below: * "1": http://ts.ssl.com @@ -161,6 +162,7 @@ fld.createFile(Utilities.newBlob(u8arr, "application/pdf").setName("signed_test. * When it is a _TsaServiceInfo_, it means a full customized information of a TSA. * __url__: string :point_right: The url of TSA * __len__: number :point_right: (Optional) The length of signature's placeholder + * __headers__: Object :point_right: (Optional) The customized headers for sending to tsa server * When it is omitted, the system timestamp will be used. * __signame__: string :point_right: (Optional) The name of the signature * __drawinf__: _SignDrawInfo_ :point_right: (Optional) Visible signature's information diff --git a/closure/forge-ext.js b/closure/forge-ext.js index 4ff33d1..9979d0e 100644 --- a/closure/forge-ext.js +++ b/closure/forge-ext.js @@ -102,6 +102,8 @@ forge.asn1.integerToDer = function(num){}; forge.asn1.oidToDer = function(oid){}; forge.asn1.Type = {}; /** @type {number} */ +forge.asn1.Type.NONE; +/** @type {number} */ forge.asn1.Type.UTF8; /** @type {number} */ forge.asn1.Type.SET; @@ -126,7 +128,7 @@ forge.asn1.Class.CONTEXT_SPECIFIC; * @param {number} tagClass * @param {number} type * @param {boolean} constructed - * @param {Array|string} value + * @param {Array|forge.asn1|number|string} value * @param {Object=} options * @return {forge.asn1} */ diff --git a/closure/zb-externs.js b/closure/zb-externs.js index 3bbc015..aab5191 100644 --- a/closure/zb-externs.js +++ b/closure/zb-externs.js @@ -3,6 +3,7 @@ * {{ * url: string, * len: (number|undefined), + * headers: (Object|undefined), * }} */ var TsaServiceInfo; @@ -32,6 +33,8 @@ var SignAreaInfo; */ var SignDrawInfo; /** + * In the case of adding a document timestamp, the p12cert and pwd must be omitted. But meanwhile the tsa must be provided. + * * permission: (DocMDP) The modification permissions granted for this document. Valid values are: * 1 : No changes to the document are permitted; any change to the document invalidates the signature. * 2 : Permitted changes are filling in forms, instantiating page templates, and signing; other changes invalidate the signature. @@ -39,8 +42,8 @@ var SignDrawInfo; * * @typedef * {{ - * p12cert: (Array|Uint8Array|ArrayBuffer|string), - * pwd: string, + * p12cert: (Array|Uint8Array|ArrayBuffer|string|undefined), + * pwd: (string|undefined), * permission: (number|undefined), * reason: (string|undefined), * location: (string|undefined), diff --git a/test4node.js b/test4node.js index 02482fb..0cb757e 100644 --- a/test4node.js +++ b/test4node.js @@ -11,13 +11,12 @@ globalThis.forge = require("node-forge"); require("./zgapdfcryptor.js"); require("./zgapdfsigner.js"); -Zga.UrlFetchApp = {}; /** * @param {string} url * @param {UrlFetchParams} params * @return {Promise} */ -Zga.UrlFetchApp.fetch = function(url, params){ +Zga.urlFetch = function(url, params){ return new Promise(function(resolve, reject){ /** @type {URL} */ var opts = m_urlparser.parse(url); @@ -91,13 +90,17 @@ async function main(){ } if(!ps){ - throw new Error("The passphrase is not specified."); + // throw new Error("The passphrase is not specified."); + pfxPath = ""; } /** @type {Buffer} */ var pdf = m_fs.readFileSync(pdfPath); /** @type {Buffer} */ - var pfx = m_fs.readFileSync(pfxPath); + var pfx = null; + if(pfxPath){ + pfx = m_fs.readFileSync(pfxPath); + } /** @type {Buffer} */ var img = null; /** @type {string} */ @@ -109,11 +112,11 @@ async function main(){ /** @type {SignOption} */ var sopt = null; - if(pfx){ + if(pdf){ sopt = { p12cert: pfx, pwd: ps, - permission: 1, + permission: pfx ? 1 : 0, signdate: "1", reason: "I have a test reason.", location: "I am on the earth.", diff --git a/zgapdfsigner.js b/zgapdfsigner.js index cc61f7d..930d9a5 100644 --- a/zgapdfsigner.js +++ b/zgapdfsigner.js @@ -18,13 +18,12 @@ z.TSAURLS = { // Google Apps Script if(globalThis.UrlFetchApp){ - z.UrlFetchApp = {}; /** * @param {string} url * @param {UrlFetchParams} params * @return {Promise} */ - z.UrlFetchApp.fetch = function(url, params){ + z.urlFetch = function(url, params){ return new Promise(function(resolve){ /** @type {GBlob} */ var tblob = UrlFetchApp.fetch(url, params).getBlob(); @@ -264,7 +263,7 @@ z.PdfSigner = class{ } } if(this.tsainf){ - if(!z.UrlFetchApp){ + if(!z.urlFetch){ throw new Error("Because of the CORS security restrictions, signing with TSA is not supported in web browser."); } if(z.TSAURLS[this.tsainf.url]){ @@ -326,18 +325,21 @@ z.PdfSigner = class{ } } - - /** @type {boolean} *///append mode or not - var apmode = _this.addSignHolder(pdfdoc); - await pdfdoc.flush(); - _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); + }else if(_this.tsainf){ + _this.log("No certificate is specified, so only add a document timestamp.") + }else{ + throw new Error("Nothing to do because no certificate nor tsa is specified."); } + /** @type {boolean} *///append mode or not + var apmode = _this.addSignHolder(pdfdoc); + await pdfdoc.flush(); + _this.log("A signature holder has been added to the pdf."); + if(apmode){ if(_this.oriU8pdf){ _this.log("The pdf has been signed already, so we add a new signature to it."); @@ -562,22 +564,78 @@ z.PdfSigner = class{ return buff.length - before; } + /** + * @private + * @param {Array|Uint8Array|ArrayBuffer|string=} p12cert + * @param {string=} pwd + * @return {forge_cert} + */ + loadP12cert(p12cert, pwd){ + // load P12 certificate + if(!p12cert){ + return null; + }else 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 {PDFLib.PDFDocument} pdfdoc * @return {boolean} append mode or not */ addSignHolder(pdfdoc){ + /** @const {z.PdfSigner} */ + const _this = this; /** @const {number} */ - const docMdp = (this.opt.permission >= 1 && this.opt.permission <= 3) ? this.opt.permission : 0; + const docMdp = (_this.certs.length > 0 && _this.opt.permission >= 1 && _this.opt.permission <= 3) ? _this.opt.permission : 0; /** @const {PDFLib.PDFContext} */ const pdfcont = pdfdoc.context; /** @const {z.SignatureCreator} */ - const signcrt = new z.SignatureCreator(this.opt.drawinf); + const signcrt = new z.SignatureCreator(_this.opt.drawinf); /** @const {PDFLib.PDFPage} */ const page = pdfdoc.getPages()[signcrt.getPageIndex()]; /** @type {PDFLib.PDFRef} */ - var strmRef = signcrt.createStream(pdfdoc, this.opt.signame); + var strmRef = signcrt.createStream(pdfdoc, _this.opt.signame); if(docMdp && !strmRef){ strmRef = signcrt.createEmptyField(pdfcont); @@ -603,23 +661,23 @@ z.PdfSigner = class{ } /** @type {string} */ - var signm = this.fixSigName(oldSigs, this.opt.signame); + var signm = _this.fixSigName(oldSigs, _this.opt.signame); /** @type {Date} */ var signdate = new Date(); - if(this.opt.signdate instanceof Date && !this.tsainf){ - signdate = this.opt.signdate; + if(_this.opt.signdate instanceof Date && !_this.tsainf){ + signdate = _this.opt.signdate; } /** @type {PDFLib.PDFArray} */ var bytrng = new PDFLib.PDFArray(pdfcont); bytrng.push(PDFLib.PDFNumber.of(0)); - bytrng.push(PDFLib.PDFName.of(this.DEFAULT_BYTE_RANGE_PLACEHOLDER)); - bytrng.push(PDFLib.PDFName.of(this.DEFAULT_BYTE_RANGE_PLACEHOLDER)); - bytrng.push(PDFLib.PDFName.of(this.DEFAULT_BYTE_RANGE_PLACEHOLDER)); + bytrng.push(PDFLib.PDFName.of(_this.DEFAULT_BYTE_RANGE_PLACEHOLDER)); + bytrng.push(PDFLib.PDFName.of(_this.DEFAULT_BYTE_RANGE_PLACEHOLDER)); + bytrng.push(PDFLib.PDFName.of(_this.DEFAULT_BYTE_RANGE_PLACEHOLDER)); - this.siglen = /** @type {number} */(this.tsainf ? this.tsainf.len : 3322); - this.sigContents = PDFLib.PDFHexString.of("0".repeat(this.siglen)); + _this.siglen = /** @type {number} */(_this.tsainf ? _this.tsainf.len : 3322); + _this.sigContents = PDFLib.PDFHexString.of("0".repeat(_this.siglen)); /** @type {Object} */ var signObj = { @@ -627,14 +685,19 @@ z.PdfSigner = class{ "Filter": "Adobe.PPKLite", "SubFilter": "adbe.pkcs7.detached", "ByteRange": bytrng, - "Contents": this.sigContents, - "M": PDFLib.PDFString.fromDate(signdate), + "Contents": _this.sigContents, "Prop_Build": pdfcont.obj({ "App": pdfcont.obj({ "Name": "ZgaPdfSinger", }), }), }; + if(_this.certs.length > 0){ + signObj.M = PDFLib.PDFString.fromDate(signdate); + }else{ + signObj.Type = "DocTimeStamp"; + signObj.SubFilter = "ETSI.RFC3161"; + } if(docMdp){ /** @type {PDFLib.PDFArray} */ var rfrc = new PDFLib.PDFArray(pdfcont); @@ -649,14 +712,14 @@ z.PdfSigner = class{ })); signObj["Reference"] = rfrc; } - if(this.opt.reason){ - signObj["Reason"] = this.convToPDFString(this.opt.reason); + if(_this.opt.reason){ + signObj["Reason"] = _this.convToPDFString(_this.opt.reason); } - if(this.opt.location){ - signObj["Location"] = this.convToPDFString(this.opt.location); + if(_this.opt.location){ + signObj["Location"] = _this.convToPDFString(_this.opt.location); } - if(this.opt.contact){ - signObj["ContactInfo"] = this.convToPDFString(this.opt.contact); + if(_this.opt.contact){ + signObj["ContactInfo"] = _this.convToPDFString(_this.opt.contact); } /** @type {PDFLib.PDFRef} */ var signatureDictRef = pdfcont.register(pdfcont.obj(signObj)); @@ -668,7 +731,7 @@ z.PdfSigner = class{ "FT": "Sig", "Rect": signcrt.getSignRect(), "V": signatureDictRef, - "T": this.convToPDFString(signm), + "T": _this.convToPDFString(signm), "F": 132, "P": page.ref, }; @@ -734,53 +797,23 @@ z.PdfSigner = class{ /** * @private - * @param {Array|Uint8Array|ArrayBuffer|string} p12cert - * @param {string} pwd - * @return {forge_cert} + * @param {string} str + * @return {PDFLib.PDFString|PDFLib.PDFHexString} */ - loadP12cert(p12cert, pwd){ - // load P12 certificate - if(typeof p12cert !== "string"){ - p12cert = z.u8arrToRaw(new Uint8Array(p12cert)); + convToPDFString(str){ + // Check if there is a multi-bytes char in the string. + /** @type {boolean} */ + var flg = false; + for(var i=0; i 0xFF){ + flg = true; + break; + } } - // 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]); + if(flg){ + return PDFLib.PDFHexString.fromText(str); }else{ - throw new Error("Failed to find a certificate."); + return PDFLib.PDFString.of(str); } } @@ -790,12 +823,6 @@ z.PdfSigner = class{ * @return {Promise} */ async signPdf(pdfstr){ - /** @type {Date} */ - var signdate = new Date(); - if(this.opt.signdate instanceof Date && !this.tsainf){ - signdate = this.opt.signdate; - } - // Finds ByteRange information within a given PDF Buffer if one exists /** @type {Array} */ var byteRangeStrings = pdfstr.match(/\/ByteRange\s*\[{1}\s*(?:(?:\d*|\/\*{10})\s+){3}(?:\d+|\/\*{10}){1}\s*]{1}/g); @@ -833,48 +860,61 @@ z.PdfSigner = class{ // Remove the placeholder signature pdfstr = pdfstr.slice(0, byteRange[1]) + pdfstr.slice(byteRange[2], byteRange[2] + byteRange[3]); - // 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); + /** @type {forge.asn1} */ + var asn1sig = null; + if(this.certs.length > 0){ + /** @type {Date} */ + var signdate = new Date(); + if(this.opt.signdate instanceof Date && !this.tsainf){ + signdate = this.opt.signdate; + } - // Add all the certificates (-cacerts & -clcerts) to p7 - this.certs.forEach(function(a_cert){ - p7.addCertificate(a_cert); - }); + // Here comes the actual PKCS#7 signing. + /** @type {forge.pkcs7} */ + var p7 = null; + p7 = forge.pkcs7.createSignedData(); + // Start off by setting the content. + p7.content = forge.util.createBuffer(pdfstr); - // Add a sha256 signer. That's what Adobe.PPKLite adbe.pkcs7.detached expects. - p7.addSigner({ - key: this.privateKey, - certificate: this.certs[this.certIdx], - digestAlgorithm: forge.pki.oids.sha256, - authenticatedAttributes: [ - { - "type": forge.pki.oids.contentType, - "value": forge.pki.oids.data, - }, { - "type": forge.pki.oids.messageDigest, - }, { - "type": forge.pki.oids.signingTime, - "value": signdate, - }, - ], - }); + // Add all the certificates (-cacerts & -clcerts) to p7 + this.certs.forEach(function(a_cert){ + p7.addCertificate(a_cert); + }); - // Sign in detached mode. - p7.sign({"detached": true}); + // Add a sha256 signer. That's what Adobe.PPKLite adbe.pkcs7.detached expects. + p7.addSigner({ + key: this.privateKey, + certificate: this.certs[this.certIdx], + digestAlgorithm: forge.pki.oids.sha256, + authenticatedAttributes: [ + { + "type": forge.pki.oids.contentType, + "value": forge.pki.oids.data, + }, { + "type": forge.pki.oids.messageDigest, + }, { + "type": forge.pki.oids.signingTime, + "value": signdate, + }, + ], + }); - if(this.tsainf){ - /** @type {forge.asn1} */ - var tsatoken = await this.queryTsa(p7.signers[0].signature); - p7.signerInfos[0].value.push(tsatoken); - this.log("Timestamp from " + this.tsainf.url + " has been added to the signature."); + // Sign in detached mode. + p7.sign({"detached": true}); + + if(this.tsainf){ + /** @type {forge.asn1} */ + var tsatoken = await this.queryTsa(p7.signers[0].signature); + p7.signerInfos[0].value.push(tsatoken); + } + asn1sig = p7.toAsn1(); + }else{ + asn1sig = await this.queryTsa(pdfstr, true); } // Check if the PDF has a good enough placeholder to fit the signature. /** @type {string} */ - var sighex = forge.asn1.toDer(p7.toAsn1()).toHex(); + var sighex = forge.asn1.toDer(asn1sig).toHex(); // placeholderLength represents the length of the HEXified symbols but we're // checking the actual lengths. this.log("Size of signature is " + sighex.length + "/" + placeholderLength); @@ -894,134 +934,128 @@ z.PdfSigner = class{ /** * @private - * @param {string} str - * @return {PDFLib.PDFString|PDFLib.PDFHexString} - */ - convToPDFString(str){ - // Check if there is a multi-bytes char in the string. - /** @type {boolean} */ - var flg = false; - for(var i=0; i 0xFF){ - flg = true; - break; - } - } - if(flg){ - return PDFLib.PDFHexString.fromText(str); - }else{ - return PDFLib.PDFString.of(str); - } - } - - /** - * @private - * @param {string=} signature - * @return {string} - */ - genTsrData(signature){ - // Generate SHA256 hash from signature content for TSA - /** @type {forge.md.digest} */ - var md = forge.md.sha256.create(); - md.update(signature); - // Generate TSA request - /** @type {forge.asn1} */ - var asn1Req = forge.asn1.create( - forge.asn1.Class.UNIVERSAL, - forge.asn1.Type.SEQUENCE, - true, - [ - // Version - { - composed: false, - constructed: false, - tagClass: forge.asn1.Class.UNIVERSAL, - type: forge.asn1.Type.INTEGER, - value: forge.asn1.integerToDer(1).data, - }, - { - composed: true, - constructed: true, - tagClass: forge.asn1.Class.UNIVERSAL, - type: forge.asn1.Type.SEQUENCE, - value: [ - { - composed: true, - constructed: true, - tagClass: forge.asn1.Class.UNIVERSAL, - type: forge.asn1.Type.SEQUENCE, - value: [ - { - composed: false, - constructed: false, - tagClass: forge.asn1.Class.UNIVERSAL, - type: forge.asn1.Type.OID, - value: forge.asn1.oidToDer(forge.oids.sha256).data, - }, { - composed: false, - constructed: false, - tagClass: forge.asn1.Class.UNIVERSAL, - type: forge.asn1.Type.NULL, - value: "" - } - ] - }, {// Message imprint - composed: false, - constructed: false, - tagClass: forge.asn1.Class.UNIVERSAL, - type: forge.asn1.Type.OCTETSTRING, - value: md.digest().data, - } - ] - }, { - composed: false, - constructed: false, - tagClass: forge.asn1.Class.UNIVERSAL, - type: forge.asn1.Type.BOOLEAN, - value: 1, // Get REQ certificates - } - ] - ); - - return forge.asn1.toDer(asn1Req).data; - } - - /** - * @private - * @param {string=} signature + * @param {string=} data + * @param {boolean=} nocert * @return {Promise} */ - async queryTsa(signature){ + async queryTsa(data, nocert){ /** @lends {forge.asn1} */ - var asn1 = forge.asn1; + const asn1 = forge.asn1; + /** @lends {forge.asn1.Class} */ + const asnc = asn1.Class; + /** @lends {forge.asn1.Type} */ + const asnt = asn1.Type; + + /** + * @param {string|number|boolean|forge.asn1|Array} aval + * @param {number=} atyp + * @param {number=} atag + * @return {forge.asn1} + */ + var asncreate = function(aval, atyp, atag){ + /** @type {string|number|forge.asn1|Array} */ + var a_val = null; + /** @type {number} */ + var a_typ = (atyp || atyp === asnt.NONE) ? atyp : -1; + /** @type {boolean} */ + var a_con = false; + if(Array.isArray(aval)){ + a_con = true; + }else{ + switch(typeof aval){ + case "string": + if(a_typ == asnt.OID){ + a_val = asn1.oidToDer(aval).getBytes(); + }else if(a_typ < 0){ + a_typ = asnt.OCTETSTRING; + } + break; + case "number": + a_val = asn1.integerToDer(aval).getBytes(); + if(a_typ < 0){ + a_typ = asnt.INTEGER; + } + break; + case "boolean": + if(aval){ + a_val = 1; + }else{ + a_val = 0; + } + if(a_typ < 0){ + a_typ = asnt.BOOLEAN; + } + } + } + if(a_typ < 0){ + a_typ = asnt.SEQUENCE; + } + if(!a_val && a_val !== 0){ + a_val = /** @type {Array|forge.asn1|number|string} */(aval); + } + return asn1.create(atag ? atag : asnc.UNIVERSAL, a_typ, a_con, a_val); + }; + + // Generate SHA256 hash from data for TSA + /** @type {forge.md.digest} */ + var md = forge.md.sha256.create(); + md.update(data); + + // Generate TSA request + /** @type {forge.asn1} */ + var asn1Req = asncreate([ + // Version + asncreate(1), + asncreate([ + asncreate([ + asncreate(forge.oids.sha256, asnt.OID), + asncreate("", asnt.NULL), + ]), + // Message imprint + asncreate(md.digest().getBytes()), + ]), + // Get REQ certificates + asncreate(true), + ]); /** @type {string} */ - var tsr = this.genTsrData(signature); + var tsr = asn1.toDer(asn1Req).getBytes(); /** @type {Uint8Array} */ var tu8s = z.rawToU8arr(tsr); + /** @type {Object} */ + var hds = this.tsainf.headers ? this.tsainf.headers : {}; + if(!hds["Content-Type"]){ + hds["Content-Type"] = "application/timestamp-query"; + } /** @type {UrlFetchParams} */ var options = { "method": "POST", - "headers": {"Content-Type": "application/timestamp-query"}, + "headers": hds, "payload": tu8s, }; /** @type {Uint8Array} */ - var tesp = await z.UrlFetchApp.fetch(this.tsainf.url, options); + var tesp = await z.urlFetch(this.tsainf.url, options); /** @type {string} */ var tstr = z.u8arrToRaw(tesp); /** @type {forge.asn1} */ var token = asn1.fromDer(tstr).value[1]; - // create the asn1 to append to the signature - /** @type {string} *///forge.pki.oids.timeStampToken - var typstr = asn1.oidToDer("1.2.840.113549.1.9.16.2.14").getBytes(); - return asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, [ - asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ - // Attribute Type - asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, typstr), - // Attribute Value - asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [token]), - ]), - ]); + /** @type {forge.asn1} */ + var ret = null; + if(nocert){ + ret = token; + }else{ + // create the asn1 to append to the signature + ret = asncreate([ + asncreate([ + // Attribute Type (forge.pki.oids.timeStampToken) + asncreate("1.2.840.113549.1.9.16.2.14", asnt.OID), + // Attribute Value + asncreate([token], asnt.SET), + ]), + ], 1, asnc.CONTEXT_SPECIFIC); + } + this.log("Timestamp from " + this.tsainf.url + " has been obtained."); + return ret; } /**