Save signed PDFs (close #2)
This commit is contained in:
parent
f5c56af2d2
commit
d892fe81ed
@ -99,10 +99,6 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form style="display: none; height: 0; width: 0; margin: 0; padding: 0;">
|
||||
<input type="file" id="open-file-input" accept=".pdf"/>
|
||||
</form>
|
||||
|
||||
<div id="svgtrimbox"></div>
|
||||
|
||||
|
||||
@ -111,10 +107,13 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
<script src="node_modules/pdfjs-dist/build/pdf.min.js"></script>
|
||||
<script src="node_modules/jspdf/dist/jspdf.umd.min.js"></script>
|
||||
<script src="node_modules/signature_pad/dist/signature_pad.umd.min.js"></script>
|
||||
<script src="node_modules/jshashes/hashes.min.js"></script>
|
||||
<script src="js/kbpgp-2.1.15.js"></script>
|
||||
<script src="js/svg-to-image.js"></script>
|
||||
<script src="js/util.js"></script>
|
||||
<script src="js/storage.js"></script>
|
||||
<script src="js/filesystem.js"></script>
|
||||
<script src="js/crypto.js"></script>
|
||||
<script src="js/drawtools.js"></script>
|
||||
<script src="js/pdf.js"></script>
|
||||
<script src="js/main.js"></script>
|
124
js/crypto.js
Normal file
124
js/crypto.js
Normal file
@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
var keymgr;
|
||||
|
||||
/**
|
||||
* Load and unlock the private key in localstorage, prompting user as needed. If there is no key, generates, saves, and loads a new one.
|
||||
* @param {function} callback Passed two arguments: message for user, and boolean true if OK false if error.
|
||||
* @returns {undefined}
|
||||
*/
|
||||
function loadKeyFromLocalStorage(callback) {
|
||||
if (!inStorage("signingkey") || getStorage("signingkey") == "undefined") {
|
||||
var pass = prompt("Generating a new signing key. Enter a password to protect it. You'll need to save this password somewhere safe; it cannot be recovered.");
|
||||
generatePrivateKey(getStorage("notary_name") + " <null@null.com>", pass, function (key) {
|
||||
if (typeof key == "undefined") {
|
||||
callback("Could not generate key.", false);
|
||||
return;
|
||||
}
|
||||
keymgr = key;
|
||||
setStorage("signingkey", keymgr.armored_pgp_private);
|
||||
callback("Signing key generated.", true);
|
||||
});
|
||||
} else {
|
||||
var pass = prompt("Enter password to unlock signing key.");
|
||||
loadPrivateKey(getStorage("signingkey"), pass, function (key) {
|
||||
if (typeof key == "undefined") {
|
||||
callback("Could not unlock key. Password is probably incorrect.", false);
|
||||
return;
|
||||
}
|
||||
keymgr = key;
|
||||
callback("Signing key unlocked.", true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a private key.
|
||||
* @param {string} armoredkey PGP private key
|
||||
* @param {string} pass key password
|
||||
* @param {function} callback Passed a new keymanager for the key.
|
||||
* @returns {undefined}
|
||||
*/
|
||||
function loadPrivateKey(armoredkey, pass, callback) {
|
||||
kbpgp.KeyManager.import_from_armored_pgp({
|
||||
armored: armoredkey
|
||||
}, function (err, key) {
|
||||
if (!err) {
|
||||
if (key.is_pgp_locked()) {
|
||||
key.unlock_pgp({
|
||||
passphrase: pass
|
||||
}, function (err) {
|
||||
if (!err) {
|
||||
console.log("Loaded private key with passphrase");
|
||||
callback(key);
|
||||
} else {
|
||||
console.error(err);
|
||||
callback(undefined);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log("Loaded private key w/o passphrase");
|
||||
callback(key);
|
||||
}
|
||||
} else {
|
||||
console.error(err);
|
||||
callback(undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign a message with a key and return the signed message in the callback.
|
||||
* @param {type} text
|
||||
* @param {type} key
|
||||
* @param {type} callback
|
||||
* @returns {undefined}
|
||||
*/
|
||||
function signMessage(text, key, callback) {
|
||||
var params = {
|
||||
msg: text,
|
||||
sign_with: key
|
||||
};
|
||||
kbpgp.box(params, function (err, result_string, result_buffer) {
|
||||
//console.log(err, result_string, result_buffer);
|
||||
callback(result_string);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new private key.
|
||||
* @param {string} userid Something like "Test User <test@netsyms.com>"
|
||||
* @param {string} passphrase protects the key
|
||||
* @param {function} callback Passed the keymanager for the new key
|
||||
* @returns {undefined}
|
||||
*/
|
||||
function generatePrivateKey(userid, passphrase, callback) {
|
||||
var F = kbpgp["const"].openpgp;
|
||||
|
||||
var opts = {
|
||||
userid: userid,
|
||||
primary: {
|
||||
nbits: 2048,
|
||||
flags: F.certify_keys | F.sign_data | F.auth | F.encrypt_comm | F.encrypt_storage,
|
||||
expire_in: 0 // never expire
|
||||
},
|
||||
subkeys: []
|
||||
};
|
||||
|
||||
kbpgp.KeyManager.generate(opts, function (err, alice) {
|
||||
if (!err) {
|
||||
alice.sign({}, function (err) {
|
||||
alice.export_pgp_private({
|
||||
passphrase: passphrase
|
||||
}, function (err, pgp_private) {
|
||||
callback(alice);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
@ -3,16 +3,64 @@
|
||||
* 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/.
|
||||
*/
|
||||
//
|
||||
//function openFileDialog(callback) {
|
||||
// $("#open-file-input").off("change");
|
||||
// if (typeof callback != "undefined") {
|
||||
// $("#open-file-input").on("change", function () {
|
||||
// callback($("#open-file-input").val());
|
||||
// });
|
||||
// }
|
||||
// $("#open-file-input").click();
|
||||
//}
|
||||
|
||||
function openFileDialog(callback) {
|
||||
$("#open-file-input").off("change");
|
||||
if (typeof callback != "undefined") {
|
||||
$("#open-file-input").on("change", function () {
|
||||
callback($("#open-file-input").val());
|
||||
});
|
||||
/**
|
||||
* Open a save file dialog and passes the file path back.
|
||||
* @param {function} callback
|
||||
* @param {string} accept HTML5 accept="thisvar" (optional)
|
||||
* @returns {undefined}
|
||||
*/
|
||||
function openFileDialog(callback, accept) {
|
||||
var dialog = document.createElement("input");
|
||||
dialog.setAttribute("type", "file");
|
||||
if (typeof accept != "undefined") {
|
||||
dialog.setAttribute("accept", accept);
|
||||
}
|
||||
$("#open-file-input").click();
|
||||
dialog.onchange = function () {
|
||||
callback(dialog.value);
|
||||
}
|
||||
dialog.dispatchEvent(new MouseEvent("click", {
|
||||
"view": window,
|
||||
"bubbles": false,
|
||||
"cancelable": false
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a save file dialog and passes the file path back.
|
||||
* @param {function} callback
|
||||
* @param {string} defaultfilename something like file.pdf (optional)
|
||||
* @param {string} accept HTML5 accept="thisvar" (optional)
|
||||
* @returns {undefined}
|
||||
*/
|
||||
function openSaveFileDialog(callback, defaultfilename, accept) {
|
||||
var dialog = document.createElement("input");
|
||||
dialog.setAttribute("type", "file");
|
||||
if (typeof defaultfilename == "undefined") {
|
||||
defaultfilename = "";
|
||||
}
|
||||
dialog.setAttribute("nwsaveas", defaultfilename);
|
||||
if (typeof accept != "undefined") {
|
||||
dialog.setAttribute("accept", accept);
|
||||
}
|
||||
dialog.onchange = function () {
|
||||
callback(dialog.value);
|
||||
}
|
||||
dialog.dispatchEvent(new MouseEvent("click", {
|
||||
"view": window,
|
||||
"bubbles": false,
|
||||
"cancelable": false
|
||||
}));
|
||||
}
|
||||
|
||||
function getFileAsString(path) {
|
||||
@ -25,21 +73,26 @@ function getFileAsUint8Array(path) {
|
||||
return fs.readFileSync(path, null);
|
||||
}
|
||||
|
||||
function writeStringToFile(path, text) {
|
||||
const fs = require("fs");
|
||||
fs.writeFileSync(path, text);
|
||||
}
|
||||
|
||||
function writeDataToFile(path, data) {
|
||||
function writeToFile(path, data) {
|
||||
const fs = require("fs");
|
||||
fs.writeFileSync(path, data);
|
||||
}
|
||||
|
||||
function appendToFile(path, data) {
|
||||
const fs = require("fs");
|
||||
fs.appendFileSync(path, data);
|
||||
}
|
||||
|
||||
function copyFile(source, dest) {
|
||||
const fs = require("fs");
|
||||
fs.copyFileSync(source, dest);
|
||||
}
|
||||
|
||||
function deleteFile(path) {
|
||||
const fs = require('fs');
|
||||
fs.unlinkSync(path);
|
||||
}
|
||||
|
||||
function getBasename(fullpath) {
|
||||
var path = require("path");
|
||||
return path.basename(fullpath);
|
||||
|
65428
js/kbpgp-2.1.15.js
Normal file
65428
js/kbpgp-2.1.15.js
Normal file
File diff suppressed because one or more lines are too long
50
js/pdf.js
50
js/pdf.js
@ -7,7 +7,7 @@
|
||||
|
||||
const {jsPDF} = window.jspdf;
|
||||
|
||||
var pdfPageScale = 4;
|
||||
var pdfPageScale = 3;
|
||||
var pdfAssumedDPI = 72;
|
||||
var pdfDoc = null;
|
||||
var pageNumber = 0;
|
||||
@ -27,10 +27,10 @@ function addPDF() {
|
||||
// Initial/first page rendering
|
||||
//renderPage(pageNum);
|
||||
});
|
||||
});
|
||||
}, ".pdf");
|
||||
}
|
||||
|
||||
function savePDF() {
|
||||
function generatePDF() {
|
||||
var canvases = $("#page-canvas-container .page-canvas");
|
||||
const pdf = new jsPDF({
|
||||
unit: "in",
|
||||
@ -51,7 +51,49 @@ function savePDF() {
|
||||
pdf.addPage(pageFormat, pageOrientation);
|
||||
pdf.addImage($("#page-canvas-container .page-canvas")[i].toDataURL(), 0, 0, pageWidthInches, pageHeightInches);
|
||||
}
|
||||
pdf.save("signed.pdf");
|
||||
return pdf;
|
||||
}
|
||||
|
||||
function getPDFAsByteArray(pdf) {
|
||||
return pdf.output("arraybuffer");
|
||||
}
|
||||
|
||||
function makeAndSaveSignedPDF(pdf, savepath, callback) {
|
||||
const hasha = require('hasha');
|
||||
|
||||
var pdfbuffer = pdf.output("arraybuffer");
|
||||
var hashstr = hasha(Buffer.from(pdfbuffer), {algorithm: 'sha256'});
|
||||
|
||||
var message = "HASH:" + hashstr
|
||||
+ "\nNOTARY:" + getStorage("notary_name")
|
||||
+ "\nSTATE:" + getStorage("notary_state")
|
||||
+ "\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() {
|
||||
loadKeyFromLocalStorage(function (message, ok) {
|
||||
if (ok) {
|
||||
openSaveFileDialog(function (path) {
|
||||
var pdf = generatePDF();
|
||||
makeAndSaveSignedPDF(pdf, path, function (result) {
|
||||
alert("File signed and saved.\n SHA256 of file (without signature): " + result.hash);
|
||||
});
|
||||
}, "signed.pdf", ".pdf");
|
||||
} else {
|
||||
alert("Error: " + message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function pdfZoom(str) {
|
||||
|
19
package-lock.json
generated
19
package-lock.json
generated
@ -108,6 +108,15 @@
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz",
|
||||
"integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA=="
|
||||
},
|
||||
"hasha": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz",
|
||||
"integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==",
|
||||
"requires": {
|
||||
"is-stream": "^2.0.0",
|
||||
"type-fest": "^0.8.0"
|
||||
}
|
||||
},
|
||||
"html2canvas": {
|
||||
"version": "1.0.0-rc.7",
|
||||
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.0.0-rc.7.tgz",
|
||||
@ -117,6 +126,11 @@
|
||||
"css-line-break": "1.1.1"
|
||||
}
|
||||
},
|
||||
"is-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz",
|
||||
"integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw=="
|
||||
},
|
||||
"jquery": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz",
|
||||
@ -205,6 +219,11 @@
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
},
|
||||
"type-fest": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
|
||||
"integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.15.3",
|
||||
"bootstrap": "^5.0.2",
|
||||
"hasha": "^5.2.2",
|
||||
"jquery": "^3.6.0",
|
||||
"jspdf": "^2.3.1",
|
||||
"pdf-lib": "^1.16.0",
|
||||
|
Loading…
x
Reference in New Issue
Block a user