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
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); |