You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

289 lines
12 KiB
JavaScript

/*
* Copyright 2021 Netsyms Technologies.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
const {jsPDF} = window.jspdf;
var pdfPageScale = 3;
var pdfAssumedDPI = 72;
var pdfDoc = null;
var pageNumber = 0;
function addPDF() {
openFileDialog(function (path) {
var filedata = getFileAsUint8Array(path);
pdfjsLib.getDocument(filedata).promise.then(function (pdfDoc_) {
pdfDoc = pdfDoc_;
renderAllPages(pdfDoc);
pdfZoom("fitwidth");
// Initial/first page rendering
//renderPage(pageNum);
});
}, ".pdf");
}
function closePDF(showuserconfirm) {
if (showuserconfirm && !confirm("Are you sure you want to close? All unsaved changes will be lost.")) {
return;
}
disableGuideBox();
pageNumber = 0;
pdfDoc = null;
$("#page-canvas-container .page-canvas").remove();
}
function analyzeSignedPDF() {
if ($("#page-canvas-container .page-canvas").length > 0 && !confirm("Opening a PDF to analyze will close the open document. Are you sure?")) {
return;
}
closePDF(false);
openFileDialog(function (path) {
var pdf = Buffer.from(getFileAsUint8Array(path).buffer);
var splitindex = pdf.indexOf("-----BEGIN PGP MESSAGE-----");
if (splitindex == -1) {
alert("Selected file does not contain any recognized signature data.");
return;
}
var pdfdata = pdf.slice(0, splitindex);
var sigdata = pdf.slice(splitindex).toString();
var pdfhash = calculateSHA256HashOfString(pdfdata);
verifyMessage(sigdata, function (msg, fprint) {
parseAndDisplaySignature(msg, pdfhash, true, fprint);
}, function (err) {
var base64 = sigdata.split("\n\n", 2)[1].split("\n-----END PGP MESSAGE-----")[0];
base64 = base64.substring(0, base64.lastIndexOf("\n")).replaceAll("\n", "");
try {
var msg = atob(base64).split("START", 2)[1].split("END", 2)[0];
parseAndDisplaySignature(msg, pdfhash, false, null);
} catch (ex) {
console.error(ex);
alert("Error: could not parse signature data.");
}
});
pdfjsLib.getDocument(pdf).promise.then(function (pdfDoc_) {
pdfDoc = pdfDoc_;
renderAllPages(pdfDoc);
pdfZoom("fitheight");
});
}, ".pdf");
}
function parseAndDisplaySignature(msg, pdfhash, verified, fingerprint) {
var msgparts = {};
// Decode message contents
var msglines = msg.split("\n");
for (var i = 0; i < msglines.length; i++) {
if (msglines[i].includes(":")) {
var parts = msglines[i].split(":", 2);
msgparts[parts[0]] = parts[1];
}
}
if (typeof msgparts["HASH"] == "string") {
if (msgparts["HASH"] == pdfhash) {
if (verified) {
$("#verifyModalStatusMessage").html("<i class=\"fas fa-check-circle\"></i> File contents match signature. File has not been changed since notarization.");
$("#verifyModalStatusMessage").removeClass();
$("#verifyModalStatusMessage").addClass(["alert", "alert-success"]);
} else {
$("#verifyModalStatusMessage").html("<i class=\"fas fa-question-circle\"></i> File contents match signature; however, \
could not verify signature authenticity. It's possible the file was changed then re-signed by an unknown person. If you have the \
public key file for the notary that signed the file, <span class=\"btn btn-outline-secondary btn-sm\"onclick=\"openPublicKeyFile()\">click here</span> to use it, \
then try running the analyze tool again.");
$("#verifyModalStatusMessage").removeClass();
$("#verifyModalStatusMessage").addClass(["alert", "alert-warning"]);
}
} else {
$("#verifyModalStatusMessage").html("<i class=\"fas fa-exclamation-circle\"></i> File contents do not match signature. Document has been modified since notarization.");
$("#verifyModalStatusMessage").removeClass();
$("#verifyModalStatusMessage").addClass(["alert", "alert-danger"]);
}
} else {
$("#verifyModalStatusMessage").html("<i class=\"fas fa-exclamation-circle\"></i> No file hash found in document signature. Could not verify document integrity.");
$("#verifyModalStatusMessage").removeClass();
$("#verifyModalStatusMessage").addClass(["alert", "alert-danger"]);
}
// Add extra data to a list below the big message
$("#verifyModalDetailedInfoList").html("");
if (typeof msgparts["DATE"] == "string" && isNaN(msgparts["DATE"]) == false) {
var datestr = formatTimestamp("F j, Y g:i a", msgparts["DATE"]);
$("#verifyModalDetailedInfoList").append('<li class="list-group-item"><i class="far fa-calendar-alt fa-fw"></i> Notarization date/time: ' + datestr + '</li>');
}
if (typeof msgparts["NOTARY"] == "string") {
$("#verifyModalDetailedInfoList").append('<li class="list-group-item"><i class="fas fa-user fa-fw"></i> Notary: ' + sanitizeHTMLString(msgparts["NOTARY"]) + '</li>');
}
if (typeof msgparts["STATE"] == "string") {
$("#verifyModalDetailedInfoList").append('<li class="list-group-item"><i class="fas fa-map-marked-alt fa-fw"></i> State: ' + sanitizeHTMLString(msgparts["STATE"]).toUpperCase() + '</li>');
}
if (typeof fingerprint == "string") {
$("#verifyModalDetailedInfoList").append('<li class="list-group-item"><i class="fas fa-fingerprint fa-fw"></i> Signature fingerprint: ' + fingerprint + '</li>');
}
new bootstrap.Modal(document.getElementById('verifyModal')).show();
}
function generatePDF(callback) {
var canvases = $("#page-canvas-container .page-canvas");
var statustextEl = $("#statustext");
statustextEl.html("<i class='fas fa-spin fa-spinner'></i> Processing document...");
const pdf = new jsPDF({
unit: "in",
compress: true
});
// creating a PDF creates a blank page that we don't want to use,
// as we haven't done the calculations yet
pdf.deletePage(1);
// Render each page in order with a pause in between to keep UI responsive
var processPage = function (i) {
if (i < canvases.length) {
statustextEl.html("<i class='fas fa-spin fa-spinner'></i> Processing page " + (i + 1) + " of " + canvases.length + "...");
console.log("Processing " + (i + 1));
var canvas = canvases[i];
var widthpx = canvas.getContext("2d").canvas.width;
var heightpx = canvas.getContext("2d").canvas.height;
var pageWidthInches = widthpx / (pdfAssumedDPI * pdfPageScale);
var pageHeightInches = heightpx / (pdfAssumedDPI * pdfPageScale);
console.log(pageWidthInches + " x " + pageHeightInches);
var pageFormat = [pageWidthInches, pageHeightInches];
var pageOrientation = (pageWidthInches > pageHeightInches ? "landscape" : "portrait");
pdf.addPage(pageFormat, pageOrientation);
pdf.addImage(canvases[i].toDataURL(), 0, 0, pageWidthInches, pageHeightInches);
i++;
setTimeout(function () {
processPage(i)
}, 100);
} else {
statustextEl.html("");
callback(pdf);
}
}
processPage(0);
}
function getPDFAsByteArray(pdf) {
return pdf.output("arraybuffer");
}
function makeAndSaveSignedPDF(pdf, savepath, callback) {
var pdfbuffer = pdf.output("arraybuffer");
var hashstr = calculateSHA256HashOfBuffer(pdfbuffer);
var message = "START"
+ "\nHASH:" + hashstr
+ "\nDATE:" + time()
+ "\nNOTARY:" + getStorage("notary_name")
+ "\nSTATE:" + getStorage("notary_state")
+ "\nEND\n";
signMessage(message, keymgr, function (sig) {
writeToFile(savepath, Buffer.from(pdfbuffer));
appendToFile(savepath, sig);
//writeToFile(savepath + ".notsigned.pdf", Buffer.from(pdfbuffer));
writeToFile(savepath + ".sig", sig);
callback({
signature: sig,
hash: hashstr
});
});
}
function savePDF() {
disableGuideBox();
var statustextEl = $("#statustext");
loadKeyFromLocalStorage(function (message, ok) {
if (ok) {
openSaveFileDialog(function (path) {
generatePDF(function (pdf) {
statustextEl.html("<i class='fas fa-spin fa-spinner'></i> Signing document...");
makeAndSaveSignedPDF(pdf, path, function (result) {
statustextEl.html("<i class='fas fa-check'></i> Signed and saved!");
alert("File signed and saved.\nSHA256 of file (without signature): " + result.hash);
setTimeout(function () {
statustextEl.html("");
}, 5000);
});
});
}, "signed.pdf", ".pdf");
} else {
statustextEl.html("");
alert("Error: " + message);
}
});
}
function pdfZoom(str) {
disableGuideBox();
var widthpx = $("#page-canvas-container .page-canvas").css("width").replace("px", "") * 1;
var zoomstep = 100;
console.log(widthpx);
switch (str) {
case "out":
$("#page-canvas-container .page-canvas").css("height", "auto");
widthpx -= zoomstep;
$("#page-canvas-container .page-canvas").css("width", widthpx + "px");
break;
case "in":
$("#page-canvas-container .page-canvas").css("height", "auto");
widthpx += zoomstep;
$("#page-canvas-container .page-canvas").css("width", widthpx + "px");
break;
case "fitwidth":
$("#page-canvas-container .page-canvas").css("width", "100%");
$("#page-canvas-container .page-canvas").css("height", "auto");
break;
case "fitheight":
$("#page-canvas-container .page-canvas").css("height", "100%");
$("#page-canvas-container .page-canvas").css("width", "auto");
break;
}
}
function getNewCanvas(pagenumber) {
var canvas = document.createElement('canvas');
canvas.id = "pdf-canvas-page-" + pagenumber;
canvas.className = "page-canvas";
return canvas;
}
function addPage() {
pageNumber++;
var canvas = getNewCanvas(pageNumber);
var prevPageCanvas = $("#page-canvas-container .page-canvas#pdf-canvas-page-" + (pageNumber - 1))[0];
canvas.width = prevPageCanvas.getContext("2d").canvas.width;
canvas.height = prevPageCanvas.getContext("2d").canvas.height;
$("#page-canvas-container").append(canvas);
}
function renderAllPages() {
var startingPageNumber = pageNumber;
var thisDocPageCount = pdfDoc.numPages;
for (var i = 1; i <= pdfDoc.numPages; i++) {
pdfDoc.getPage(i).then(function (page) {
var viewport = page.getViewport({scale: pdfPageScale});
var canvas = getNewCanvas(page.pageNumber + startingPageNumber);
canvas.height = viewport.height;
canvas.width = viewport.width;
$("#page-canvas-container").append(canvas);
// Render PDF page into canvas context
var renderContext = {
canvasContext: canvas.getContext("2d"),
viewport: viewport
};
page.render(renderContext);
});
}
pageNumber = pageNumber + thisDocPageCount;
//document.getElementById('page_count').textContent = pageNumber;
}