keyoxide-web/assets/scripts.js

549 lines
19 KiB
JavaScript
Raw Normal View History

2020-06-25 10:01:06 -06:00
async function verifySignature(opts) {
2020-06-26 05:06:32 -06:00
// Init
2020-06-25 10:01:06 -06:00
const elRes = document.body.querySelector("#result");
const elResContent = document.body.querySelector("#resultContent");
2020-06-26 05:06:32 -06:00
let keyData, feedback, signature, verified, valid;
2020-06-25 10:01:06 -06:00
2020-06-26 05:06:32 -06:00
// Reset feedback
2020-06-25 10:01:06 -06:00
elRes.innerHTML = "";
elResContent.innerHTML = "";
try {
2020-06-26 05:06:32 -06:00
// Get key data
keyData = await fetchKeys(opts);
2020-06-25 10:01:06 -06:00
2020-06-26 05:06:32 -06:00
// Handle missing signature
if (opts.signature == null) { throw("No signature was provided."); }
2020-06-25 10:01:06 -06:00
2020-06-26 05:06:32 -06:00
// Try two different methods of signature reading
2020-06-25 10:01:06 -06:00
let readError = null;
try {
signature = await openpgp.message.readArmored(opts.signature);
} catch(e) {
readError = e;
}
try {
signature = await openpgp.cleartext.readArmored(opts.signature);
} catch(e) {
readError = e;
}
2020-06-26 05:06:32 -06:00
if (signature == null) { throw(readError) };
2020-06-25 10:01:06 -06:00
2020-06-26 05:06:32 -06:00
// Verify the signature
2020-06-25 10:01:06 -06:00
verified = await openpgp.verify({
message: signature,
publicKeys: keyData.publicKey
2020-06-25 10:01:06 -06:00
});
2020-06-26 05:09:18 -06:00
valid = verified.signatures[0].valid;
2020-06-25 10:01:06 -06:00
} catch (e) {
console.error(e);
elRes.innerHTML = e;
elRes.classList.remove('green');
elRes.classList.add('red');
return;
}
2020-06-26 05:06:32 -06:00
// Init feedback to empty string
2020-06-25 10:01:06 -06:00
feedback = '';
2020-06-26 05:06:32 -06:00
// If content was extracted from signature
if (keyData.sigContent) {
2020-06-25 10:01:06 -06:00
elResContent.innerHTML = "<strong>Signature content:</strong><br><span style=\"white-space: pre-line\">"+sigContent+"</span>";
}
2020-06-26 05:06:32 -06:00
// Provide different feedback depending on key input mode
if (opts.mode == "signature" && keyData.sigUserId) {
2020-06-25 10:01:06 -06:00
if (valid) {
feedback += "The message was signed by the userId extracted from the signature.<br>";
feedback += 'UserId: '+keyData.sigUserId+'<br>';
feedback += "Fingerprint: "+keyData.fingerprint+"<br>";
2020-06-25 10:01:06 -06:00
elRes.classList.remove('red');
elRes.classList.add('green');
} else {
feedback += "The message's signature COULD NOT BE verified using the userId extracted from the signature.<br>";
2020-06-26 04:58:02 -06:00
feedback += 'UserId: '+keyData.sigUserId+'<br>';
2020-06-25 10:01:06 -06:00
elRes.classList.remove('green');
elRes.classList.add('red');
}
} else if (opts.mode == "signature" && keyData.sigKeyId) {
2020-06-25 10:01:06 -06:00
if (valid) {
feedback += "The message was signed by the keyId extracted from the signature.<br>";
feedback += 'KeyID: '+keyData.sigKeyId+'<br>';
feedback += "Fingerprint: "+keyData.fingerprint+"<br><br>";
2020-06-25 10:01:06 -06:00
feedback += "!!! You should manually verify the fingerprint to confirm the signer's identity !!!";
elRes.classList.remove('red');
elRes.classList.add('green');
} else {
feedback += "The message's signature COULD NOT BE verified using the keyId extracted from the signature.<br>";
2020-06-26 04:58:02 -06:00
feedback += 'KeyID: '+keyData.sigKeyId+'<br>';
2020-06-25 10:01:06 -06:00
elRes.classList.remove('green');
elRes.classList.add('red');
}
} else {
if (valid) {
feedback += "The message was signed by the provided key ("+opts.mode+").<br>";
feedback += "Fingerprint: "+keyData.fingerprint+"<br>";
2020-06-25 10:01:06 -06:00
elRes.classList.remove('red');
elRes.classList.add('green');
} else {
feedback += "The message's signature COULD NOT BE verified using the provided key ("+opts.mode+").<br>";
elRes.classList.remove('green');
elRes.classList.add('red');
}
}
2020-06-26 05:06:32 -06:00
// Display feedback
2020-06-25 10:01:06 -06:00
elRes.innerHTML = feedback;
};
async function encryptMessage(opts) {
2020-06-26 05:06:32 -06:00
// Init
2020-06-25 10:01:06 -06:00
const elEnc = document.body.querySelector("#messageEncrypted");
const elRes = document.body.querySelector("#result");
let keyData, feedback, message, encrypted;
2020-06-25 10:01:06 -06:00
2020-06-26 05:06:32 -06:00
// Reset feedback
2020-06-25 10:01:06 -06:00
elRes.innerHTML = "";
elEnc.value = "";
try {
2020-06-26 05:06:32 -06:00
// Get key data
keyData = await fetchKeys(opts);
2020-06-25 10:01:06 -06:00
2020-06-26 05:06:32 -06:00
// Handle missing message
2020-06-25 10:01:06 -06:00
if (opts.message == null) {
2020-06-26 05:06:32 -06:00
throw("No message was provided.");
2020-06-25 10:01:06 -06:00
}
2020-06-26 05:06:32 -06:00
// Encrypt the message
2020-06-25 10:01:06 -06:00
encrypted = await openpgp.encrypt({
message: openpgp.message.fromText(opts.message),
publicKeys: keyData.publicKey
2020-06-25 10:01:06 -06:00
});
} catch (e) {
console.error(e);
elRes.innerHTML = e;
elRes.classList.remove('green');
elRes.classList.add('red');
return;
}
2020-06-26 05:06:32 -06:00
// Display encrypted data
2020-06-25 10:01:06 -06:00
elEnc.value = encrypted.data;
};
2020-06-26 07:08:22 -06:00
async function verifyProofs(opts) {
// Init
const elRes = document.body.querySelector("#result");
let keyData, feedback = "", message, encrypted;
// Reset feedback
elRes.innerHTML = "";
try {
// Get key data
keyData = await fetchKeys(opts);
} catch (e) {
console.error(e);
elRes.innerHTML = e;
elRes.classList.remove('green');
elRes.classList.add('red');
return;
}
let notation, isVerified, verifications = [];
for (var i = 0; i < keyData.notations.length; i++) {
notation = keyData.notations[i];
if (!(notation[0] == "proof@keyoxide.org" || notation[0] == "proof@metacode.biz")) { continue; }
verifications.push(await verifyProof(notation[1], keyData.fingerprint));
}
// Generate feedback
for (var i = 0; i < verifications.length; i++) {
feedback += `${verifications[i].type}: <a href="${verifications[i].url}">${verifications[i].display}</a>: ${verifications[i].isVerified}<br>`;
}
// Display feedback
elRes.innerHTML = feedback;
2020-06-26 09:52:01 -06:00
}
async function displayProfile(opts) {
let keyData = await fetchKeys(opts);
let userData = keyData.user.user.userId;
let feedback = "", notation, isVerified, verifications = [];
2020-06-26 16:50:21 -06:00
document.body.querySelector('#profileName').innerHTML = userData.name;
2020-06-26 16:56:51 -06:00
document.title = `${userData.name} - Keyoxide`;
2020-06-26 09:52:01 -06:00
for (var i = 0; i < keyData.notations.length; i++) {
notation = keyData.notations[i];
if (!(notation[0] == "proof@keyoxide.org" || notation[0] == "proof@metacode.biz")) { continue; }
verifications.push(await verifyProof(notation[1], keyData.fingerprint));
}
2020-06-27 04:34:51 -06:00
console.log(keyData);
2020-06-26 09:52:01 -06:00
// Generate feedback
2020-06-26 16:50:21 -06:00
feedback += `<div class="profileDataItem profileDataItem--separator">`;
feedback += `<div class="profileDataItem__label"></div>`;
feedback += `<div class="profileDataItem__value">general information</div>`;
feedback += `</div>`;
2020-06-27 04:34:51 -06:00
for (var i = 0; i < keyData.publicKey.users.length; i++) {
feedback += `<div class="profileDataItem">`;
feedback += `<div class="profileDataItem__label">email</div>`;
feedback += `<div class="profileDataItem__value"><a href="mailto:${keyData.publicKey.users[i].userId.email}">${keyData.publicKey.users[i].userId.email}</a></div>`;
feedback += `</div>`;
}
2020-06-26 16:50:21 -06:00
feedback += `<div class="profileDataItem">`;
feedback += `<div class="profileDataItem__label">fingerprint</div>`;
feedback += `<div class="profileDataItem__value"><a href="https://keys.openpgp.org/pks/lookup?op=get&options=mr&search=0x${keyData.fingerprint}">${keyData.fingerprint}</a></div>`;
feedback += `</div>`;
feedback += `<div class="profileDataItem profileDataItem--separator">`;
feedback += `<div class="profileDataItem__label"></div>`;
feedback += `<div class="profileDataItem__value">proofs</div>`;
feedback += `</div>`;
2020-06-26 09:52:01 -06:00
for (var i = 0; i < verifications.length; i++) {
2020-06-26 16:50:21 -06:00
feedback += `<div class="profileDataItem">`;
feedback += `<div class="profileDataItem__label">${verifications[i].type}</div>`;
feedback += `<div class="profileDataItem__value">`;
feedback += `<a class="proofDisplay" href="${verifications[i].url}">${verifications[i].display}</a>`;
if (verifications[i].isVerified) {
feedback += `<a class="proofUrl proofUrl--verified" href="${verifications[i].proofUrl}">verified &#10004;</a>`;
} else {
feedback += `<a class="proofUrl" href="${verifications[i].proofUrl}">proof</a>`;
}
feedback += `</div>`;
feedback += `</div>`;
2020-06-26 09:52:01 -06:00
}
// Display feedback
2020-06-26 16:50:21 -06:00
document.body.querySelector('#profileData').innerHTML = feedback;
2020-06-26 09:52:01 -06:00
}
2020-06-26 07:08:22 -06:00
async function verifyProof(url, fingerprint) {
// Init
2020-06-26 16:50:21 -06:00
let reVerify, output = {url: url, type: null, proofUrl: url, proofUrlFetch: null, isVerified: false, display: null};
2020-06-26 07:08:22 -06:00
// DNS
if (/^dns:/.test(url)) {
2020-06-26 16:50:21 -06:00
output.type = "website";
output.display = url.replace(/dns:/, '').replace(/\?type=TXT/, '');
2020-06-26 16:53:31 -06:00
output.proofUrl = `https://dns.google.com/resolve?name=${output.display}&type=TXT`;
output.proofUrlFetch = output.proofUrl;
2020-06-26 16:50:21 -06:00
output.url = `https://${output.display}`;
2020-06-26 07:08:22 -06:00
try {
2020-06-26 16:50:21 -06:00
response = await fetch(output.proofUrlFetch, {
2020-06-26 07:08:22 -06:00
headers: {
Accept: 'application/json'
},
credentials: 'omit'
});
if (!response.ok) {
throw new Error('Response failed: ' + response.status);
}
json = await response.json();
reVerify = new RegExp(`openpgp4fpr:${fingerprint}`);
json.Answer.forEach((item, i) => {
if (reVerify.test(item.data)) {
output.isVerified = true;
}
});
} catch (e) {
} finally {
return output;
}
}
// HN
if (/^https:\/\/news.ycombinator.com/.test(url)) {
output.type = "hn";
2020-06-26 16:50:21 -06:00
output.display = url.replace(/https:\/\/news.ycombinator.com\/user\?id=/, "");
output.proofUrlFetch = `https://hacker-news.firebaseio.com/v0/user/${output.display}.json`;
2020-06-26 07:08:22 -06:00
try {
2020-06-26 16:50:21 -06:00
response = await fetch(output.proofUrlFetch, {
2020-06-26 07:08:22 -06:00
headers: {
Accept: 'application/json'
},
credentials: 'omit'
});
if (!response.ok) {
throw new Error('Response failed: ' + response.status);
}
json = await response.json();
reVerify = new RegExp(`openpgp4fpr:${fingerprint}`);
2020-06-26 16:50:21 -06:00
if (reVerify.test(json.about)) {
output.isVerified = true;
}
2020-06-26 07:08:22 -06:00
} catch (e) {
} finally {
return output;
}
}
// Reddit
if (/^https:\/\/www.reddit.com\/user/.test(url)) {
output.type = "reddit";
2020-06-26 16:50:21 -06:00
output.display = url.replace(/^https:\/\/www.reddit.com\/user\//, "").replace(/\/comments\/.*/, "");
output.url = `https://www.reddit.com/user/${output.display}`;
output.proofUrlFetch = url.replace(/\/[a-zA-Z0-9_]*\/$/, ".json");
2020-06-26 07:08:22 -06:00
try {
2020-06-26 16:50:21 -06:00
response = await fetch(output.proofUrlFetch, {
2020-06-26 07:08:22 -06:00
headers: {
Accept: 'application/json'
},
credentials: 'omit'
});
if (!response.ok) {
throw new Error('Response failed: ' + response.status);
}
json = await response.json();
2020-06-26 16:50:21 -06:00
console.log(json);
reVerify = new RegExp(`Verifying my OpenPGP key: openpgp4fpr:${fingerprint}`);
2020-06-26 07:08:22 -06:00
json.Answer.forEach((item, i) => {
if (reVerify.test(item.data)) {
output.isVerified = true;
}
});
} catch (e) {
} finally {
return output;
}
}
// Github
if (/^https:\/\/gist.github.com/.test(url)) {
output.type = "github";
2020-06-26 16:50:21 -06:00
output.display = url.replace(/^https:\/\/gist.github.com\//, "").replace(/\/[a-zA-Z0-9]*$/, "");
output.url = `https://github.com/${output.display}`;
let gistId = url.replace(/^https:\/\/gist.github.com\/[a-zA-Z0-9_-]*\//, "");
output.proofUrlFetch = `https://api.github.com/gists/${gistId}`;
2020-06-26 07:08:22 -06:00
try {
2020-06-26 16:50:21 -06:00
response = await fetch(output.proofUrlFetch, {
2020-06-26 07:08:22 -06:00
headers: {
Accept: 'application/json'
},
credentials: 'omit'
});
if (!response.ok) {
throw new Error('Response failed: ' + response.status);
}
json = await response.json();
2020-06-26 16:50:21 -06:00
reVerify = new RegExp(`[Verifying my OpenPGP key: openpgp4fpr:${fingerprint}]`);
if (reVerify.test(json.files["openpgp.md"].content)) {
output.isVerified = true;
}
2020-06-26 07:08:22 -06:00
} catch (e) {
} finally {
return output;
}
}
// Catchall
try {
response = await fetch(url, {
headers: {
Accept: 'application/json'
},
credentials: 'omit'
});
if (!response.ok) {
throw new Error('Response failed: ' + response.status);
}
json = await response.json();
if ('attachment' in json) {
// Potentially Mastodon
json.attachment.forEach((item, i) => {
if (item.value === fingerprint) {
output.type = "mastodon";
output.display = json.url;
2020-06-26 16:50:21 -06:00
output.proofUrlFetch = json.url;
2020-06-26 07:08:22 -06:00
output.isVerified = true;
}
});
}
} catch (e) {
} finally {
return output;
}
}
async function fetchKeys(opts) {
2020-06-26 05:08:04 -06:00
// Init
let lookupOpts, wkd, hkd, sig, lastPrimarySig;
let output = {
publicKey: null,
user: null,
notations: null,
sigKeyId: null,
sigUserId: null,
sigContent: null
};
2020-06-26 05:08:04 -06:00
// Fetch keys depending on the input mode
switch (opts.mode) {
case "plaintext":
output.publicKey = (await openpgp.key.readArmored(opts.input)).keys[0];
if (!output.publicKey) {
throw("Error: No public keys could be fetched from the plaintext input.");
}
break;
case "wkd":
wkd = new openpgp.WKD();
lookupOpts = {
email: opts.input
};
output.publicKey = (await wkd.lookup(lookupOpts)).keys[0];
if (!output.publicKey) {
throw("Error: No public keys could be fetched using WKD.");
}
break;
case "hkp":
if (!opts.server) {opts.server = "https://keys.openpgp.org/"};
hkp = new openpgp.HKP(opts.server);
lookupOpts = {
query: opts.input
};
output.publicKey = await hkp.lookup(lookupOpts);
output.publicKey = (await openpgp.key.readArmored(output.publicKey)).keys[0];
if (!output.publicKey) {
throw("Error: No public keys could be fetched from the HKP server.");
}
break;
case "signature":
sig = (await openpgp.signature.readArmored(opts.signature));
if ('compressed' in sig.packets[0]) {
sig = sig.packets[0];
output.sigContent = (await openpgp.stream.readToEnd(await sig.packets[1].getText()));
};
output.sigUserId = sig.packets[0].signersUserId;
output.sigKeyId = (await sig.packets[0].issuerKeyId.toHex());
if (!opts.server) {opts.server = "https://keys.openpgp.org/"};
hkp = new openpgp.HKP(opts.server);
lookupOpts = {
query: output.sigUserId ? output.sigUserId : output.sigKeyId
};
output.publicKey = await hkp.lookup(lookupOpts);
output.publicKey = (await openpgp.key.readArmored(output.publicKey)).keys[0];
if (!output.publicKey) {
throw("Error: No public keys could be extracted from the signature.");
}
break;
}
2020-06-26 05:08:04 -06:00
// Gather more data about the primary key and user
output.fingerprint = await output.publicKey.primaryKey.getFingerprint();
output.user = await output.publicKey.getPrimaryUser();
lastPrimarySig = output.user.selfCertification;
output.notations = lastPrimarySig.notations || [];
return output;
}
// General purpose
2020-06-25 10:01:06 -06:00
let elFormVerify = document.body.querySelector("#form-verify"),
2020-06-26 09:52:01 -06:00
elFormEncrypt = document.body.querySelector("#form-encrypt"),
elFormProofs = document.body.querySelector("#form-proofs"),
elProfileUid = document.body.querySelector("#profileUid");
2020-06-25 10:01:06 -06:00
if (elFormVerify) {
elFormVerify.onsubmit = function (evt) {
evt.preventDefault();
let opts = {
signature: null,
mode: null,
input: null,
server: null,
};
opts.signature = document.body.querySelector("#signature").value;
if (document.body.querySelector("#publicKey").value != "") {
opts.input = document.body.querySelector("#publicKey").value;
opts.mode = "plaintext";
} else if (document.body.querySelector("#wkd").value != "") {
opts.input = document.body.querySelector("#wkd").value;
opts.mode = "wkd";
} else if (document.body.querySelector("#hkp_input").value != "") {
opts.input = document.body.querySelector("#hkp_input").value;
opts.server = document.body.querySelector("#hkp_server").value;
opts.mode = "hkp";
} else {
opts.mode = "signature";
2020-06-25 10:01:06 -06:00
}
verifySignature(opts);
};
}
if (elFormEncrypt) {
elFormEncrypt.onsubmit = function (evt) {
evt.preventDefault();
let opts = {
message: null,
mode: null,
input: null,
server: null,
};
opts.message = document.body.querySelector("#message").value;
if (document.body.querySelector("#publicKey").value != "") {
opts.input = document.body.querySelector("#publicKey").value;
opts.mode = "plaintext";
} else if (document.body.querySelector("#wkd").value != "") {
opts.input = document.body.querySelector("#wkd").value;
opts.mode = "wkd";
} else if (document.body.querySelector("#hkp_input").value != "") {
opts.input = document.body.querySelector("#hkp_input").value;
opts.server = document.body.querySelector("#hkp_server").value;
opts.mode = "hkp";
} else {
opts.mode = "signature";
2020-06-25 10:01:06 -06:00
}
encryptMessage(opts);
};
}
2020-06-26 07:08:22 -06:00
if (elFormProofs) {
elFormProofs.onsubmit = function (evt) {
evt.preventDefault();
let opts = {
mode: null,
input: null,
server: null,
};
if (document.body.querySelector("#publicKey").value != "") {
opts.input = document.body.querySelector("#publicKey").value;
opts.mode = "plaintext";
} else if (document.body.querySelector("#wkd").value != "") {
opts.input = document.body.querySelector("#wkd").value;
opts.mode = "wkd";
} else if (document.body.querySelector("#hkp_input").value != "") {
opts.input = document.body.querySelector("#hkp_input").value;
opts.server = document.body.querySelector("#hkp_server").value;
opts.mode = "hkp";
} else {
opts.mode = null;
}
verifyProofs(opts);
};
}
2020-06-26 09:52:01 -06:00
if (elProfileUid) {
let profileUid = elProfileUid.innerHTML;
let opts = {
input: profileUid,
mode: "hkp"
}
displayProfile(opts);
}