diff --git a/src/js/crypto.js b/src/js/crypto.js
index 08252ca..5a4cf40 100644
--- a/src/js/crypto.js
+++ b/src/js/crypto.js
@@ -9,7 +9,7 @@ var keymgr;
var keyring = new kbpgp.keyring.KeyRing();
/**
- * Load and unlock the private key in localstorage, prompting user as needed. If there is no key, generates, saves, and loads a new one.
+ * Load and unlock the private key in localstorage, prompting user as needed.
* @param {function} callback Passed two arguments: message for user, and boolean true if OK false if error.
* @returns {undefined}
*/
@@ -20,18 +20,8 @@ function loadKeyFromLocalStorage(callback) {
}
$("#lockstatus").css("display", "none");
if (!inStorage("signingkey") || getStorage("signingkey") == "undefined") {
- showPasswordPrompt("Generating a new signing key (might take a while, be patient). Enter a password to protect it. You'll need to save this password somewhere safe; it cannot be recovered.", function (pass) {
- generatePrivateKey(getStorage("notary_name") + " <" + (inStorage("notary_email") ? getStorage("notary_email") : "null@null.com") + ">", pass, function (key) {
- if (typeof key == "undefined") {
- callback("Could not generate key.", false);
- return;
- }
- keymgr = key;
- keyring.add_key_manager(keymgr);
- setStorage("signingkey", keymgr.armored_pgp_private);
- callback("Signing key generated.", true);
- });
- });
+ callback("You do not have a private key. Click Settings to create one.", false);
+ return;
} else {
showPasswordPrompt("Enter password to unlock signing key:", function (pass) {
loadPrivateKey(getStorage("signingkey"), pass, function (key) {
@@ -41,12 +31,31 @@ function loadKeyFromLocalStorage(callback) {
}
keymgr = key;
keyring.add_key_manager(keymgr);
- callback("Signing key unlocked.", true);
+ callback("Private key unlocked.", true);
});
});
}
}
+function createKey(callback) {
+ if (!inStorage("signingkey") || getStorage("signingkey") == "undefined") {
+ showPasswordPrompt("Generating a new private key (might take a while, be patient). Enter a password to protect it. You'll need to memorize or write down this password; it cannot be recovered.", function (pass) {
+ generatePrivateKey(getStorage("notary_name") + " <" + (inStorage("notary_email") ? getStorage("notary_email") : "noemailaddressprovided@example.com") + ">", pass, function (key) {
+ if (typeof key == "undefined") {
+ callback("Could not generate key.", false);
+ return;
+ }
+ keymgr = key;
+ keyring.add_key_manager(keymgr);
+ setStorage("signingkey", keymgr.armored_pgp_private);
+ callback("Private key generated.", true);
+ });
+ });
+ } else {
+ callback("You already have a private key. You must erase it before generating a new one.", false);
+ }
+}
+
function unloadKey() {
keymgr = undefined;
$("#lockstatus").css("display", "");
@@ -63,6 +72,16 @@ function loadKeyFromLocalStorageWithUserFeedback() {
});
}
+function createKeyWithUserFeedback() {
+ createKey(function (msg, ok) {
+ if (ok) {
+ showToast("
" + msg);
+ } else {
+ showAlert("Error: " + msg);
+ }
+ });
+}
+
/**
* Load a private key.
* @param {string} armoredkey PGP private key
@@ -184,18 +203,68 @@ function generatePrivateKey(userid, passphrase, callback) {
}, 100);
}
-function exportPublicKey() {
+function exportPublicKeyToFile() {
+ getOwnPublicKey(function (pgp_public) {
+ if (pgp_public == false) {
+ showAlert("Something went wrong.");
+ } else {
+ openSaveFileDialog(function (path) {
+ writeToFile(path, pgp_public);
+ showAlert("Public key saved.");
+ }, "public-key.asc", ".asc");
+ }
+ });
+}
+
+function exportPublicKeyToRegistry() {
+ if (!inStorage("signingkey") || getStorage("signingkey") == "undefined") {
+ showAlert("You must create and back up your private key first.");
+ return;
+ }
+ showOkCancelPrompt("Double-check that your notary profile is complete and that you pressed the Save button after making any changes before continuing.", function (ok) {
+ if (!ok) {
+ return;
+ }
+ getOwnPublicKey(function (pgp_public) {
+ if (pgp_public == false) {
+ showAlert("Something went wrong.");
+ } else {
+ submitPublicKeyToRegistry(
+ pgp_public,
+ getStorage("notary_name"),
+ getStorage("notary_email"),
+ getStorage("notary_location"),
+ getStorage("notary_expires"),
+ getStorage("notary_idnumber"),
+ getStorage("notary_state"),
+ function (msg, ok) {
+ if (!ok) {
+ showAlert("Error: " + msg);
+ } else {
+ showAlert(msg);
+ }
+ }
+ );
+ }
+ });
+ });
+}
+
+/**
+ * Get user's own public key, prompting for key password if needed.
+ * @param {function} callback cb(result): public key string or false on error
+ * @returns {undefined}
+ */
+function getOwnPublicKey(callback) {
loadKeyFromLocalStorage(function (message, ok) {
if (ok) {
- openSaveFileDialog(function (path) {
- keymgr.export_pgp_public({}, function (err, pgp_public) {
- if (err) {
- showAlert("Something went wrong.");
- } else {
- writeToFile(path, pgp_public);
- }
- });
- }, "public-key.asc", ".asc");
+ keymgr.export_pgp_public({}, function (err, pgp_public) {
+ if (err) {
+ callback(false);
+ } else {
+ callback(pgp_public);
+ }
+ });
} else {
showAlert("Error: " + message);
}
@@ -324,14 +393,11 @@ function calculateSHA256HashOfString(str) {
function openPublicKeyFile() {
openFileDialog(function (path, html5file) {
var importpk = function (keyfile) {
- kbpgp.KeyManager.import_from_armored_pgp({
- armored: keyfile
- }, function (err, pubkeymgr) {
- if (!err) {
- keyring.add_key_manager(pubkeymgr);
+ addPublicKeyToKeyring(keyfile, function (res) {
+ if (res === true) {
showAlert("Public key file loaded. You can now analyze PDFs signed by the key's owner.");
} else {
- showAlert("Error loading public key: " + err);
+ showAlert("Error loading public key: " + res);
}
});
};
@@ -349,6 +415,150 @@ function openPublicKeyFile() {
}, ".asc");
}
+/**
+ *
+ * @param {type} keyfile
+ * @param {type} callback cb(result): result is true if successful, an error string if failed.
+ * @returns {undefined}
+ */
+function addPublicKeyToKeyring(keyfile, callback) {
+ kbpgp.KeyManager.import_from_armored_pgp({
+ armored: keyfile
+ }, function (err, pubkeymgr) {
+ if (!err) {
+ keyring.add_key_manager(pubkeymgr);
+ callback(true);
+ } else {
+ callback(err);
+ }
+ });
+}
+
+/**
+ * Look up a full or partial public key fingerprint with the Netsyms notary registry.
+ * @param {string} fingerprint
+ * @param {function} callback cb(result): `result` is an array of notary info and keys (see below), or `false` if there was an error or no results.
+ * @returns {undefined}
+ *
+ * result = [{
+ * fingerprint,
+ * name,
+ * email,
+ * location,
+ * commissionexpires,
+ * idnumber,
+ * state,
+ * publickey
+ * }]
+ *
+ * All but fingerprint and publickey could be null.
+ *
+ */
+function lookupPublicKey(fingerprint, callback) {
+ $.ajax({
+ url: "https://data.netsyms.net/v1/notary/fetchkey/",
+ dataType: "json",
+ data: {
+ fingerprint: fingerprint
+ },
+ success: function (resp) {
+ if (resp.count == 0) {
+ callback(false);
+ return;
+ }
+ callback(resp.results);
+ },
+ error: function () {
+ callback(false);
+ }
+ });
+}
+
+/**
+ * Import multiple public keys and only callback when all are done.
+ * @param {type} keys see lookupPublicKey()
+ * @param {function} callback
+ * @returns {undefined}
+ */
+function importPublicKeysFromRegistry(keys, callback) {
+ var i = 0;
+ var loop = function (keys) {
+ addPublicKeyToKeyring(keys[i].publickey, function () {
+ i++;
+ if (i < keys.length) {
+ loop(keys);
+ } else {
+ callback();
+ }
+ });
+ };
+ loop(keys);
+}
+
+/**
+ * Upload a public key to the Netsyms notary registry server.
+ * @param {string} pubkey PGP public key file contents, armored
+ * @param {string} name Notary name
+ * @param {string} email Notary email
+ * @param {string} location Notary location
+ * @param {string} expires Commission expiration date; server will parse.
+ * @param {string} idnumber Commission ID number
+ * @param {string} state Two-char state
+ * @param {function} callback ((string) message, (bool) okaytrue_errorfalse)
+ * @returns {undefined}
+ */
+function submitPublicKeyToRegistry(pubkey, name, email, location, expires, idnumber, state, callback) {
+ $.ajax({
+ url: "https://data.netsyms.net/v1/notary/publishkey/",
+ method: "POST",
+ dataType: "json",
+ data: {
+ publickey: pubkey,
+ name: name,
+ email: email,
+ location: location,
+ commissionexpires: expires,
+ idnumber: idnumber,
+ state: state
+ },
+ success: function (resp) {
+ if (resp.status == "OK") {
+ callback(resp.msg, true);
+ } else if (resp.status == "ERROR") {
+ callback(resp.msg, false);
+ } else {
+ callback("The registry server didn't send a valid response.", false);
+ }
+ },
+ error: function () {
+ callback("There was a problem communicating with the registry server. Try again later.", false);
+ }
+ });
+}
+
+/**
+ * Erase the local private key data with lots of prompting and dire warnings.
+ * @returns {undefined}
+ */
+function erasePrivateKey() {
+ showOkCancelPrompt("
DANGER: THIS WILL RESULT IN DATA LOSS -- READ CAREFULLY
\n\
+Erasing your private key means you won't be able to notarize or sign electronic documents without generating a new key. \n\
+If you have not exported your public key, electronically verifying documents you have signed will be impossible.\n\
+If you plan on using your private key in the future, press cancel and back up your private key to a file.\n\
+Some states require you use the same key for the entire length of your commission.\n\
+
IF YOU CONTINUE, YOUR PRIVATE KEY WILL NOT BE RECOVERABLE WITHOUT A BACKUP.
", function (ok) {
+ if (!ok) {
+ return;
+ }
+ var txt = prompt("To erase your private key, type \"ERASE MY SIGNING KEY\"");
+ if (txt.toUpperCase() != "ERASE MY SIGNING KEY") {
+ return;
+ }
+ unloadKey();
+ localStorage.removeItem("signingkey");
+ alert("Signing key erased.");
+ });
+}
/**
* Show visual indicator when private key is not loaded/unlocked.
* @returns {undefined}
@@ -359,4 +569,5 @@ setInterval(function () {
} else {
$("#lockstatus").css("display", "none");
}
-}, 1000);
\ No newline at end of file
+}
+, 1000);
\ No newline at end of file
diff --git a/src/js/pdf.js b/src/js/pdf.js
index aab2828..7933bec 100644
--- a/src/js/pdf.js
+++ b/src/js/pdf.js
@@ -70,51 +70,75 @@ function analyzeSignedPDF() {
var pdfdata = pdf.slice(0, splitindex);
var sigdata = pdf.slice(splitindex).toString();
- var verify = function (pdfhash) {
+ var verify = function (pdfhash, reload) {
loadKeyFromLocalStorage(function () {
verifyMessage(sigdata, function (msg, fprint) {
parseAndDisplaySignature(msg, pdfhash, true, fprint);
}, function (err) {
console.error(err);
- console.log(sigdata);
- 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 = window.atob(base64).split("START", 2)[1].split("END", 2)[0];
- parseAndDisplaySignature(msg, pdfhash, false, null);
- } catch (ex) {
readSignatureExternally(sigdata, function (msg, keyprint, signername, verified, ok) {
if (!ok) {
showAlert("Error: could not parse signature data.");
return;
}
- parseAndDisplaySignature(msg, pdfhash, verified, keyprint, signername);
+ // If system GPG has the public key, use that.
+ // Otherwise, try looking up the fingerprint online and if we get hits
+ // add them to the local keyring and try verifying again.
+ if (verified) {
+ parseAndDisplaySignature(msg, pdfhash, verified, keyprint, signername);
+ } else {
+ if (typeof reload == 'undefined' || reload == false) {
+ lookupPublicKey(keyprint, function (res) {
+ if (res == false) {
+ parseAndDisplaySignature(msg, pdfhash, verified, keyprint, signername);
+ } else {
+ importPublicKeysFromRegistry(res, function () {
+ verify(pdfhash, true);
+ });
+ }
+ });
+ } else {
+ parseAndDisplaySignature(msg, pdfhash, verified, keyprint, signername);
+ }
+ }
});
+ } catch (ex) {
console.error(ex);
+ 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 = window.atob(base64).split("START", 2)[1].split("END", 2)[0];
+ parseAndDisplaySignature(msg, pdfhash, false, null);
+ } catch (exx) {
+ console.error(exx);
+ }
}
});
});
- if (typeof nw != 'undefined') {
- pdfjsLib.getDocument(pdf).promise.then(function (pdfDoc_) {
- pdfDoc = pdfDoc_;
-
- renderAllPages(pdfDoc);
- pdfZoom("fitheight");
- });
- } else {
- var fileReader = new FileReader();
- fileReader.onload = function () {
- pdfjsLib.getDocument(new Uint8Array(this.result)).promise.then(function (pdfDoc_) {
+ if (typeof reload == 'undefined' || reload == false) {
+ if (typeof nw != 'undefined') {
+ pdfjsLib.getDocument(pdf).promise.then(function (pdfDoc_) {
pdfDoc = pdfDoc_;
renderAllPages(pdfDoc);
pdfZoom("fitheight");
});
- };
- fileReader.readAsArrayBuffer(html5file);
+ } else {
+ var fileReader = new FileReader();
+ fileReader.onload = function () {
+ pdfjsLib.getDocument(new Uint8Array(this.result)).promise.then(function (pdfDoc_) {
+ pdfDoc = pdfDoc_;
+
+ renderAllPages(pdfDoc);
+ pdfZoom("fitheight");
+ });
+ };
+ fileReader.readAsArrayBuffer(html5file);
+ }
+ $(".enable-when-doc-open").removeClass("disabled");
}
- $(".enable-when-doc-open").removeClass("disabled");
};
if (typeof nw != 'undefined') {
diff --git a/src/js/popups.js b/src/js/popups.js
index b144f49..41fa48b 100644
--- a/src/js/popups.js
+++ b/src/js/popups.js
@@ -25,4 +25,12 @@ function showPasswordPrompt(message, callback) {
$("#passwordModalInput").val("");
passwordModalCallback = callback;
new bootstrap.Modal(document.getElementById('passwordModal')).show();
+}
+
+var okCancelPromptModalCallback = function (okay) {};
+
+function showOkCancelPrompt(message, callback) {
+ $("#okCancelPromptModalText").html(message);
+ okCancelPromptModalCallback = callback;
+ new bootstrap.Modal(document.getElementById('okCancelPromptModal')).show();
}
\ No newline at end of file