parent
b72d38473a
commit
f932a2bb2a
44
README.md
44
README.md
|
@ -5,6 +5,15 @@ 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.
|
||||||
|
|
||||||
|
## 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,
|
||||||
|
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
|
||||||
|
|
||||||
* [pdf-lib](https://pdf-lib.js.org/)
|
* [pdf-lib](https://pdf-lib.js.org/)
|
||||||
|
@ -16,7 +25,7 @@ Just import the dependencies and this tool.
|
||||||
```html
|
```html
|
||||||
<script src="https://unpkg.com/pdf-lib/dist/pdf-lib.min.js" type="text/javascript"></script>
|
<script src="https://unpkg.com/pdf-lib/dist/pdf-lib.min.js" type="text/javascript"></script>
|
||||||
<script src="https://unpkg.com/node-forge/dist/forge.min.js" type="text/javascript"></script>
|
<script src="https://unpkg.com/node-forge/dist/forge.min.js" type="text/javascript"></script>
|
||||||
<script src="https://github.com/zboris12/zgapdfsigner/releases/download/1.1.0/zgapdfsigner.js" type="text/javascript"></script>
|
<script src="https://github.com/zboris12/zgapdfsigner/releases/download/1.2.0/zgapdfsigner.js" type="text/javascript"></script>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Let's sign
|
## Let's sign
|
||||||
|
@ -94,20 +103,21 @@ var window = globalThis;
|
||||||
// Load pdf-lib
|
// Load pdf-lib
|
||||||
eval(UrlFetchApp.fetch("https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js").getContentText());
|
eval(UrlFetchApp.fetch("https://unpkg.com/pdf-lib@1.17.1/dist/pdf-lib.min.js").getContentText());
|
||||||
// Load node-forge
|
// Load node-forge
|
||||||
eval(UrlFetchApp.fetch("https://unpkg.com/node-forge@1.3.1/dist/forge.min.js").getContentText());
|
eval(UrlFetchApp.fetch("https://github.com/zboris12/zgapdfsigner/releases/download/1.2.0/forge.min.edited.js").getContentText());
|
||||||
// Load ZgaPdfSigner
|
// Load ZgaPdfSigner
|
||||||
eval(UrlFetchApp.fetch("https://github.com/zboris12/zgapdfsigner/releases/download/1.1.0/zgapdfsigner.js").getContentText());
|
eval(UrlFetchApp.fetch("https://github.com/zboris12/zgapdfsigner/releases/download/1.2.0/zgapdfsigner.js").getContentText());
|
||||||
|
|
||||||
// Load pdf, certificate
|
// Load pdf, certificate
|
||||||
var pdfBlob = DriveApp.getFilesByName("_test.pdf").next().getBlob();
|
var pdfBlob = DriveApp.getFilesByName("_test.pdf").next().getBlob();
|
||||||
var certBlob = DriveApp.getFilesByName("_test.pfx").next().getBlob();
|
var certBlob = DriveApp.getFilesByName("_test.pfx").next().getBlob();
|
||||||
// Sign the pdf
|
// Sign the pdf
|
||||||
var sopt = {
|
var sopt = {
|
||||||
p12cert: new Uint8Array(certBlob.getBytes()),
|
p12cert: certBlob.getBytes(),
|
||||||
pwd: "some passphrase",
|
pwd: "some passphrase",
|
||||||
|
signdate: "1",
|
||||||
};
|
};
|
||||||
var signer = new Zga.PdfSigner(sopt);
|
var signer = new Zga.PdfSigner(sopt);
|
||||||
var u8arr = await signer.sign(new Uint8Array(pdfBlob.getBytes()));
|
var u8arr = await signer.sign(pdfBlob.getBytes());
|
||||||
// Save the result pdf to some folder
|
// Save the result pdf to some folder
|
||||||
var fld = DriveApp.getFolderById("a folder's id");
|
var fld = DriveApp.getFolderById("a folder's id");
|
||||||
fld.createFile(Utilities.newBlob(u8arr, "application/pdf").setName("signed_test.pdf"));
|
fld.createFile(Utilities.newBlob(u8arr, "application/pdf").setName("signed_test.pdf"));
|
||||||
|
@ -115,24 +125,36 @@ fld.createFile(Utilities.newBlob(u8arr, "application/pdf").setName("signed_test.
|
||||||
|
|
||||||
## Detail of SignOption
|
## Detail of SignOption
|
||||||
|
|
||||||
* __p12cert__: (Uint8Array|ArrayBuffer|string) :point_right: Certificate's data
|
* __p12cert__: Array<number>|Uint8Array|ArrayBuffer|string :point_right: Certificate's data
|
||||||
* __pwd__: string :point_right: The passphrase of the certificate
|
* __pwd__: string :point_right: The passphrase of the certificate
|
||||||
* __reason__: string :point_right: (Optional) The reason for signing
|
* __reason__: string :point_right: (Optional) The reason for signing
|
||||||
* __location__: string :point_right: (Optional) Your location
|
* __location__: string :point_right: (Optional) Your location
|
||||||
* __contact__: string :point_right: (Optional) Your contact information
|
* __contact__: string :point_right: (Optional) Your contact information
|
||||||
* __signdate__: Date :point_right: (Optional) The date and time of signing
|
* __signdate__: Date|string|_TsaServiceInfo_ :point_right: (Optional)
|
||||||
|
* When it is a Date, it means the date and time for signing.
|
||||||
|
* When it is a string, it can be an url of TSA or an index of the preset TSA as below:
|
||||||
|
* "1": http://ts.ssl.com
|
||||||
|
* "2": http://timestamp.digicert.com
|
||||||
|
* "3": http://timestamp.sectigo.com
|
||||||
|
* "4": http://timestamp.entrust.net/TSS/RFC3161sha2TS
|
||||||
|
* "5": http://timestamp.apple.com/ts01
|
||||||
|
* "6": http://www.langedge.jp/tsa
|
||||||
|
* "7": https://freetsa.org/tsr
|
||||||
|
* When it is a _TsaServiceInfo_, it means a full customized information of TSA.
|
||||||
|
* __url__: string :point_right: The url of TSA
|
||||||
|
* __len__: number :point_right: (Optional) The length of signature's placeholder
|
||||||
* __signame__: string :point_right: (Optional) The name of the signature
|
* __signame__: string :point_right: (Optional) The name of the signature
|
||||||
* __drawinf__: SignDrawInfo :point_right: (Optional) Visible signature's information
|
* __drawinf__: _SignDrawInfo_ :point_right: (Optional) Visible signature's information
|
||||||
* __area__: SignAreaInfo :point_right: The signature's drawing area
|
* __area__: _SignAreaInfo_ :point_right: The signature's drawing area
|
||||||
* __x__: number :point_right: Distance from left
|
* __x__: number :point_right: Distance from left
|
||||||
* __y__: number :point_right: Distance from top
|
* __y__: number :point_right: Distance from top
|
||||||
* __w__: number :point_right: Width
|
* __w__: number :point_right: Width
|
||||||
* __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__: 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, __only support jpg and png__
|
||||||
* __text__: string :point_right: (Optional) A text drawing on signature, __not implemented yet__
|
* __text__: string :point_right: (Optional) A text drawing on signature, __not implemented yet__
|
||||||
* __fontData__: PDFLib.StandardFonts|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, __not implemented yet__
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
|
@ -1,3 +1,11 @@
|
||||||
|
/**
|
||||||
|
* @typedef
|
||||||
|
* {{
|
||||||
|
* url: string,
|
||||||
|
* len: (number|undefined),
|
||||||
|
* }}
|
||||||
|
*/
|
||||||
|
var TsaServiceInfo;
|
||||||
/**
|
/**
|
||||||
* the base point of x, y is top left corner.
|
* the base point of x, y is top left corner.
|
||||||
* @typedef
|
* @typedef
|
||||||
|
@ -14,10 +22,10 @@ var SignAreaInfo;
|
||||||
* {{
|
* {{
|
||||||
* area: SignAreaInfo,
|
* area: SignAreaInfo,
|
||||||
* pageidx: (number|undefined),
|
* pageidx: (number|undefined),
|
||||||
* imgData: (Uint8Array|ArrayBuffer|string|undefined),
|
* imgData: (Array<number>|Uint8Array|ArrayBuffer|string|undefined),
|
||||||
* imgType: (string|undefined),
|
* imgType: (string|undefined),
|
||||||
* text: (string|undefined),
|
* text: (string|undefined),
|
||||||
* fontData: (PDFLib.StandardFonts|Uint8Array|ArrayBuffer|string|undefined),
|
* fontData: (PDFLib.StandardFonts|Array<number>|Uint8Array|ArrayBuffer|string|undefined),
|
||||||
* img: (PDFLib.PDFImage|undefined),
|
* img: (PDFLib.PDFImage|undefined),
|
||||||
* font: (PDFLib.PDFFont|undefined),
|
* font: (PDFLib.PDFFont|undefined),
|
||||||
* }}
|
* }}
|
||||||
|
@ -26,14 +34,15 @@ var SignDrawInfo;
|
||||||
/**
|
/**
|
||||||
* @typedef
|
* @typedef
|
||||||
* {{
|
* {{
|
||||||
* p12cert: (Uint8Array|ArrayBuffer|string),
|
* p12cert: (Array<number>|Uint8Array|ArrayBuffer|string),
|
||||||
* pwd: string,
|
* pwd: string,
|
||||||
* reason: (string|undefined),
|
* reason: (string|undefined),
|
||||||
* location: (string|undefined),
|
* location: (string|undefined),
|
||||||
* contact: (string|undefined),
|
* contact: (string|undefined),
|
||||||
* signdate: (Date|undefined),
|
* signdate: (Date|TsaServiceInfo|string|undefined),
|
||||||
* signame: (string|undefined),
|
* signame: (string|undefined),
|
||||||
* drawinf: (SignDrawInfo|undefined),
|
* drawinf: (SignDrawInfo|undefined),
|
||||||
|
* debug: (boolean|undefined),
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
var SignOption;
|
var SignOption;
|
||||||
|
|
File diff suppressed because one or more lines are too long
208
zgapdfsigner.js
208
zgapdfsigner.js
|
@ -2,6 +2,17 @@
|
||||||
|
|
||||||
globalThis.Zga = {
|
globalThis.Zga = {
|
||||||
|
|
||||||
|
/** @type {Object<string, TsaServiceInfo>} */
|
||||||
|
TSAURLS: {
|
||||||
|
"1": {url: "http://ts.ssl.com", len: 15100},
|
||||||
|
"2": {url: "http://timestamp.digicert.com", len: 14900},
|
||||||
|
"3": {url: "http://timestamp.sectigo.com", len: 12900},
|
||||||
|
"4": {url: "http://timestamp.entrust.net/TSS/RFC3161sha2TS", len: 13900},
|
||||||
|
"5": {url: "http://timestamp.apple.com/ts01", len: 11600},
|
||||||
|
"6": {url: "http://www.langedge.jp/tsa", len: 8700},
|
||||||
|
"7": {url: "https://freetsa.org/tsr", len: 14000},
|
||||||
|
},
|
||||||
|
|
||||||
PdfSigner: class {
|
PdfSigner: class {
|
||||||
/**
|
/**
|
||||||
* @constructor
|
* @constructor
|
||||||
|
@ -10,11 +21,49 @@ PdfSigner: class {
|
||||||
constructor(signopt){
|
constructor(signopt){
|
||||||
/** @private @type {SignOption} */
|
/** @private @type {SignOption} */
|
||||||
this.opt = signopt;
|
this.opt = signopt;
|
||||||
|
/** @private @type {TsaServiceInfo} */
|
||||||
|
this.tsainf = null;
|
||||||
|
/** @private @type {boolean} */
|
||||||
|
this.debug = false;
|
||||||
|
|
||||||
|
if(!globalThis.PDFLib){
|
||||||
|
throw new Error("pdf-lib is not imported.");
|
||||||
|
}
|
||||||
|
if(!globalThis.forge){
|
||||||
|
throw new Error("node-forge is not imported.");
|
||||||
|
}
|
||||||
|
if(signopt.signdate){
|
||||||
|
if(typeof signopt.signdate == "string"){
|
||||||
|
this.tsainf = {
|
||||||
|
url: signopt.signdate,
|
||||||
|
};
|
||||||
|
}else if(signopt.signdate.url){
|
||||||
|
this.tsainf = Object.assign({}, signopt.signdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(this.tsainf){
|
||||||
|
if(!globalThis.UrlFetchApp){
|
||||||
|
throw new Error("Because of the CORS security restrictions, signing with TSA is not supported in web browser.");
|
||||||
|
}
|
||||||
|
if(Zga.TSAURLS[this.tsainf.url]){
|
||||||
|
Object.assign(this.tsainf, Zga.TSAURLS[this.tsainf.url]);
|
||||||
|
}else if(!(new RegExp("^https?://")).test(this.tsainf.url)){
|
||||||
|
throw new Error("Unknown tsa data. " + JSON.stringify(this.tsainf));
|
||||||
|
}
|
||||||
|
if(!this.tsainf.len){
|
||||||
|
this.tsainf.len = 16000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(typeof this.opt.debug == "boolean"){
|
||||||
|
this.debug = this.opt.debug;
|
||||||
|
}else if(globalThis.debug){
|
||||||
|
this.debug = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @public
|
* @public
|
||||||
* @param {PDFLib.PDFDocument|Uint8Array|ArrayBuffer|string} pdf
|
* @param {PDFLib.PDFDocument|Array<number>|Uint8Array|ArrayBuffer|string} pdf
|
||||||
* @return {Promise<Uint8Array>}
|
* @return {Promise<Uint8Array>}
|
||||||
*/
|
*/
|
||||||
async sign(pdf){
|
async sign(pdf){
|
||||||
|
@ -22,15 +71,24 @@ PdfSigner: class {
|
||||||
var pdfdoc = null;
|
var pdfdoc = null;
|
||||||
if(pdf.addPage){
|
if(pdf.addPage){
|
||||||
pdfdoc = pdf;
|
pdfdoc = pdf;
|
||||||
|
}else if(Array.isArray(pdf)){
|
||||||
|
pdfdoc = await PDFLib.PDFDocument.load(new Uint8Array(pdf));
|
||||||
}else{
|
}else{
|
||||||
pdfdoc = await PDFLib.PDFDocument.load(pdf);
|
pdfdoc = await PDFLib.PDFDocument.load(pdf);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.opt.drawinf && this.opt.drawinf.imgData && !this.opt.drawinf.img){
|
if(this.opt.drawinf && this.opt.drawinf.imgData && !this.opt.drawinf.img){
|
||||||
|
/** @type {Uint8Array|ArrayBuffer|string} */
|
||||||
|
var imgData2 = null;
|
||||||
|
if(Array.isArray(this.opt.drawinf.imgData)){
|
||||||
|
imgData2 = new Uint8Array(this.opt.drawinf.imgData);
|
||||||
|
}else{
|
||||||
|
imgData2 = this.opt.drawinf.imgData;
|
||||||
|
}
|
||||||
if(this.opt.drawinf.imgType == "png"){
|
if(this.opt.drawinf.imgType == "png"){
|
||||||
this.opt.drawinf.img = await pdfdoc.embedPng(this.opt.drawinf.imgData);
|
this.opt.drawinf.img = await pdfdoc.embedPng(imgData2);
|
||||||
}else if(this.opt.drawinf.imgType == "jpg"){
|
}else if(this.opt.drawinf.imgType == "jpg"){
|
||||||
this.opt.drawinf.img = await pdfdoc.embedJpg(this.opt.drawinf.imgData);
|
this.opt.drawinf.img = await pdfdoc.embedJpg(imgData2);
|
||||||
}else{
|
}else{
|
||||||
throw new Error("Unkown image type. " + this.opt.drawinf.imgType);
|
throw new Error("Unkown image type. " + this.opt.drawinf.imgType);
|
||||||
}
|
}
|
||||||
|
@ -40,7 +98,12 @@ PdfSigner: class {
|
||||||
var uarr = await pdfdoc.save({"useObjectStreams": false});
|
var uarr = await pdfdoc.save({"useObjectStreams": false});
|
||||||
var pdfstr = Zga.u8arrToRaw(uarr);
|
var pdfstr = Zga.u8arrToRaw(uarr);
|
||||||
|
|
||||||
return this.signPdf(pdfstr);
|
this.log("A signature holder has been added to the pdf.");
|
||||||
|
|
||||||
|
/** @type {Uint8Array} */
|
||||||
|
var ret = this.signPdf(pdfstr);
|
||||||
|
this.log("Signing pdf accomplished.");
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -51,7 +114,7 @@ PdfSigner: class {
|
||||||
/** @const {string} */
|
/** @const {string} */
|
||||||
const DEFAULT_BYTE_RANGE_PLACEHOLDER = "**********";
|
const DEFAULT_BYTE_RANGE_PLACEHOLDER = "**********";
|
||||||
/** @const {number} */
|
/** @const {number} */
|
||||||
const SIGNATURE_LENGTH = 3322;
|
const SIGNATURE_LENGTH = this.tsainf ? this.tsainf.len : 3322;
|
||||||
|
|
||||||
/** @const {VisualSignature} */
|
/** @const {VisualSignature} */
|
||||||
const visign = new Zga.VisualSignature(this.opt.drawinf);
|
const visign = new Zga.VisualSignature(this.opt.drawinf);
|
||||||
|
@ -60,8 +123,10 @@ PdfSigner: class {
|
||||||
/** @const {PDFLib.PDFPage} */
|
/** @const {PDFLib.PDFPage} */
|
||||||
const page = pdfdoc.getPages()[visign.getPageIndex()];
|
const page = pdfdoc.getPages()[visign.getPageIndex()];
|
||||||
|
|
||||||
if(!this.opt.signdate){
|
/** @type {Date} */
|
||||||
this.opt.signdate = new Date();
|
var signdate = new Date();
|
||||||
|
if(this.opt.signdate instanceof Date && !this.tsainf){
|
||||||
|
signdate = this.opt.signdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {PDFLib.PDFArray} */
|
/** @type {PDFLib.PDFArray} */
|
||||||
|
@ -78,7 +143,7 @@ PdfSigner: class {
|
||||||
"SubFilter": "adbe.pkcs7.detached",
|
"SubFilter": "adbe.pkcs7.detached",
|
||||||
"ByteRange": bytrng,
|
"ByteRange": bytrng,
|
||||||
"Contents": PDFLib.PDFHexString.of("0".repeat(SIGNATURE_LENGTH)),
|
"Contents": PDFLib.PDFHexString.of("0".repeat(SIGNATURE_LENGTH)),
|
||||||
"M": PDFLib.PDFString.fromDate(this.opt.signdate),
|
"M": PDFLib.PDFString.fromDate(signdate),
|
||||||
"Prop_Build": pdfdoc.context.obj({
|
"Prop_Build": pdfdoc.context.obj({
|
||||||
"App": pdfdoc.context.obj({
|
"App": pdfdoc.context.obj({
|
||||||
"Name": "ZgaPdfSinger",
|
"Name": "ZgaPdfSinger",
|
||||||
|
@ -133,8 +198,10 @@ PdfSigner: class {
|
||||||
* @return {Uint8Array}
|
* @return {Uint8Array}
|
||||||
*/
|
*/
|
||||||
signPdf(pdfstr){
|
signPdf(pdfstr){
|
||||||
if(!this.opt.signdate){
|
/** @type {Date} */
|
||||||
this.opt.signdate = new 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
|
// Finds ByteRange information within a given PDF Buffer if one exists
|
||||||
|
@ -217,19 +284,37 @@ PdfSigner: class {
|
||||||
"type": forge.pki.oids.messageDigest,
|
"type": forge.pki.oids.messageDigest,
|
||||||
}, {
|
}, {
|
||||||
"type": forge.pki.oids.signingTime,
|
"type": forge.pki.oids.signingTime,
|
||||||
"value": this.opt.signdate,
|
"value": signdate,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if(this.tsainf){
|
||||||
|
//p7.signers[0].unauthenticatedAttributes.push({type: forge.pki.oids.timeStampToken, value: ""})
|
||||||
|
p7.signers[0].unauthenticatedAttributes.push({type: "1.2.840.113549.1.9.16.2.14", value: ""});
|
||||||
|
}
|
||||||
|
|
||||||
// Sign in detached mode.
|
// Sign in detached mode.
|
||||||
p7.sign({"detached": true});
|
p7.sign({"detached": true});
|
||||||
|
|
||||||
|
if(this.tsainf){
|
||||||
|
var tsatoken = this.queryTsa(p7.signers[0].signature);
|
||||||
|
p7.signerInfos[0].value[6].value[0].value[1] = forge.asn1.create(
|
||||||
|
forge.asn1.Class.UNIVERSAL,
|
||||||
|
forge.asn1.Type.SET,
|
||||||
|
true,
|
||||||
|
[tsatoken]
|
||||||
|
);
|
||||||
|
this.log("Timestamp from " + this.tsainf.url + " has been added to the signature.");
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the PDF has a good enough placeholder to fit the signature.
|
// Check if the PDF has a good enough placeholder to fit the signature.
|
||||||
var sighex = forge.asn1.toDer(p7.toAsn1()).toHex();
|
var sighex = forge.asn1.toDer(p7.toAsn1()).toHex();
|
||||||
// placeholderLength represents the length of the HEXified symbols but we're
|
// placeholderLength represents the length of the HEXified symbols but we're
|
||||||
// checking the actual lengths.
|
// checking the actual lengths.
|
||||||
|
this.log("Size of signature is " + sighex.length + "/" + placeholderLength);
|
||||||
if(sighex.length > placeholderLength){
|
if(sighex.length > placeholderLength){
|
||||||
throw new Error("Signature is too big.");
|
throw new Error("Signature is too big. Needs: " + sighex.length);
|
||||||
}else{
|
}else{
|
||||||
// Pad the signature with zeroes so the it is the same length as the placeholder
|
// Pad the signature with zeroes so the it is the same length as the placeholder
|
||||||
sighex += "0".repeat(placeholderLength - sighex.length);
|
sighex += "0".repeat(placeholderLength - sighex.length);
|
||||||
|
@ -260,6 +345,105 @@ PdfSigner: class {
|
||||||
return PDFLib.PDFString.of(str);
|
return PDFLib.PDFString.of(str);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {string} signature
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
genTsrData(signature){
|
||||||
|
// Generate SHA256 hash from signature content for TSA
|
||||||
|
var md = forge.md.sha256.create();
|
||||||
|
md.update(signature);
|
||||||
|
// Generate TSA request
|
||||||
|
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
|
||||||
|
* @return {Object}
|
||||||
|
*/
|
||||||
|
queryTsa(signature){
|
||||||
|
var tsr = this.genTsrData(signature);
|
||||||
|
var tu8s = Zga.rawToU8arr(tsr);
|
||||||
|
var options = {
|
||||||
|
"method": "POST",
|
||||||
|
"headers": {"Content-Type": "application/timestamp-query"},
|
||||||
|
"payload": tu8s,
|
||||||
|
};
|
||||||
|
var tblob = UrlFetchApp.fetch(this.tsainf.url, options).getBlob();
|
||||||
|
var tstr = Zga.u8arrToRaw(new Uint8Array(tblob.getBytes()));
|
||||||
|
var token = forge.asn1.fromDer(tstr).value[1];
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {string} msg
|
||||||
|
*/
|
||||||
|
log(msg){
|
||||||
|
if(this.debug){
|
||||||
|
console.log(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
VisualSignature: class {
|
VisualSignature: class {
|
||||||
|
|
Loading…
Reference in New Issue