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.

362 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/.
*/
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.
* @param {function} callback Passed two arguments: message for user, and boolean true if OK false if error.
* @returns {undefined}
*/
function loadKeyFromLocalStorage(callback) {
if (typeof keymgr != "undefined") {
callback("Key already loaded.", true);
return;
}
$("#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") + " <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);
});
});
} else {
showPasswordPrompt("Enter password to unlock signing key:", function (pass) {
loadPrivateKey(getStorage("signingkey"), pass, function (key) {
if (typeof key == "undefined") {
callback("Could not unlock key. Password is probably incorrect.", false);
return;
}
keymgr = key;
keyring.add_key_manager(keymgr);
callback("Signing key unlocked.", true);
});
});
}
}
function unloadKey() {
keymgr = undefined;
$("#lockstatus").css("display", "");
showToast("<i class='fas fa-lock'></i> Signing key locked.");
}
function loadKeyFromLocalStorageWithUserFeedback() {
loadKeyFromLocalStorage(function (msg, ok) {
if (ok) {
showToast("<i class='fas fa-unlock'></i> " + msg);
} else {
showAlert("Error: " + msg);
}
});
}
/**
* 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);
});
}
/**
* Read a signed PGP message and return the contents and signer's fingerprint/key ID.
* @param {string} pgpmsg "-----BEGIN PGP MESSAGE----- ..."
* @param {function} callback function(message, fingerprint) {}
* @param {function} onerror function(errormessage) {}
* @returns {undefined}
*/
function verifyMessage(pgpmsg, callback, onerror) {
kbpgp.unbox({keyfetch: keyring, armored: pgpmsg}, function (err, literals) {
if (err != null) {
onerror(err);
} else {
var message = literals[0].toString();
var fingerprint = null;
var ds = km = null;
ds = literals[0].get_data_signer();
if (ds) {
km = ds.get_key_manager();
}
if (km) {
fingerprint = km.get_pgp_fingerprint().toString('hex');
}
callback(message, fingerprint);
}
});
}
/**
* 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 statustextEl = $("#statustext");
statustextEl.html("<i class='fas fa-spin fa-spinner'></i> Generating cryptographic key...");
setTimeout(function () {
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) {
statustextEl.html("<i class='fas fa-check'></i> Key generated!");
setTimeout(function () {
statustextEl.html("");
}, 5000);
callback(alice);
});
});
}
});
}, 100);
}
function exportPublicKey() {
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");
} else {
showAlert("Error: " + message);
}
});
}
/**
* This should be modified to prompt the user for a backup password,
* but that doesn't work. https://github.com/keybase/kbpgp/issues/211
* @returns {undefined}
*/
function exportPrivateKey() {
loadKeyFromLocalStorage(function (message, ok) {
if (!ok) {
showAlert("Error: " + message);
return;
}
openSaveFileDialog(function (path) {
keymgr.export_pgp_private({}, function (err, pgp_private) {
if (err) {
showAlert("Something went wrong.");
} else {
writeToFile(path, pgp_private);
showAlert("Private key backup saved.");
}
});
}, "private-key.asc", ".asc");
});
}
function importPrivateKey() {
if (inStorage("signingkey") && getStorage("signingkey") != "undefined") {
if (!confirm("The restored key will replace the current key, which will be unrecoverable unless you made a backup. Continue?")) {
return;
}
}
keymgr = null;
openFileDialog(function (path) {
var keyfile = getFileAsString(path);
showPasswordPrompt("Enter password for imported key (password was set when exported):", function (pass) {
loadPrivateKey(keyfile, pass, function (key) {
if (typeof key == "undefined") {
showAlert("Could not import key. Password is probably incorrect.");
return;
}
keymgr = key;
setStorage("signingkey", keymgr.armored_pgp_private);
showAlert("Private key imported.");
});
});
}, ".asc");
}
/**
* Call the native system GPG to "decrypt" a PGP signature. This should work when the hacky "base64 decode and search for strings" method fails.
* @param {String} sigdata
* @param {Function} callback (string|null) message, (string|null) fingerprint, (string|null) signername, (bool) verified, (bool) success
* @returns {undefined}
*/
function readSignatureExternally(sigdata, callback) {
const exec = require('child_process').exec;
const os = require('os');
const process = require('process');
const sigfilepath = getNewTempFilePath() + ".asc";
writeToFile(sigfilepath, sigdata);
var gpgexecutable = "gpg";
switch (os.platform()) {
case "win32":
// Most systems will have it here
gpgexecutable = '"C:\\Program Files (x86)\\gnupg\\bin\\gpg.exe"';
if (!fs.existsSync(gpgexecutable)) {
// Let's hope it's in %PATH%
gpgexecutable = "gpg.exe";
}
break;
case "linux":
break;
default:
break;
}
var command = gpgexecutable + " -vv --decrypt " + sigfilepath;
exec(command, function (error, stdout, stderr) {
console.log(stdout);
var msg = null;
if (stdout.length > 50) {
msg = stdout;
} else {
callback(null, null, null, false, false);
}
var verified = false;
var signername = null;
console.log(stderr);
var keyid = null;
var keyidregex = /(keyid|RSA key) ([A-F0-9]+)/;
if (keyidregex.test(stderr)) {
keyid = stderr.match(keyidregex)[2];
}
var goodsigregex = /Good signature from "([a-zA-Z0-9\s]+) <.+@.+>"/;
if (goodsigregex.test(stderr)) {
// GPG actually has a matching public key, so that's cool
verified = true;
signername = stderr.match(goodsigregex)[1];
}
callback(msg, keyid, signername, verified, true);
});
}
function calculateSHA256HashOfBuffer(buffer) {
const hasha = require('hasha');
var hashstr = hasha(Buffer.from(buffer), {algorithm: 'sha256'});
return hashstr;
}
function calculateSHA256HashOfString(str) {
const hasha = require('hasha');
var hashstr = hasha(str, {algorithm: 'sha256'});
return hashstr;
}
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);
showAlert("Public key file loaded. You can now analyze PDFs signed by the key's owner.");
} else {
showAlert("Error loading public key: " + err);
}
});
};
if (typeof nw != 'undefined') {
var keyfile = getFileAsString(path);
importpk(keyfile);
} else {
var fileReader = new FileReader();
fileReader.onload = function (e) {
importpk(e.target.result);
}
fileReader.readAsText(html5file);
}
}, ".asc");
}
/**
* Show visual indicator when private key is not loaded/unlocked.
* @returns {undefined}
*/
setInterval(function () {
if (typeof keymgr == "undefined") {
$("#lockstatus").css("display", "");
} else {
$("#lockstatus").css("display", "none");
}
}, 1000);