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
|
2020-06-26 04:53:12 -06:00
|
|
|
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,
|
2020-06-26 04:53:12 -06:00
|
|
|
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
|
2020-06-26 04:53:12 -06:00
|
|
|
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
|
2020-06-26 04:53:12 -06:00
|
|
|
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>";
|
2020-06-26 04:53:12 -06:00
|
|
|
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');
|
|
|
|
}
|
2020-06-26 04:53:12 -06:00
|
|
|
} 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>";
|
2020-06-26 04:53:12 -06:00
|
|
|
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>";
|
2020-06-26 04:53:12 -06:00
|
|
|
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");
|
2020-06-26 04:53:12 -06:00
|
|
|
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
|
2020-06-26 04:53:12 -06:00
|
|
|
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),
|
2020-06-26 04:53:12 -06:00
|
|
|
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];
|
2020-06-28 05:57:31 -06:00
|
|
|
if (notation[0] != "proof@metacode.biz") { continue; }
|
2020-06-26 07:08:22 -06:00
|
|
|
verifications.push(await verifyProof(notation[1], keyData.fingerprint));
|
|
|
|
}
|
|
|
|
|
2020-06-29 02:46:45 -06:00
|
|
|
// One-line sorting function (order verifications by type)
|
|
|
|
verifications = verifications.sort((a,b) => (a.type > b.type) ? 1 : ((b.type > a.type) ? -1 : 0));
|
|
|
|
|
2020-06-26 07:08:22 -06:00
|
|
|
// Generate feedback
|
2020-06-27 06:59:48 -06:00
|
|
|
feedback += `<p>`;
|
2020-06-26 07:08:22 -06:00
|
|
|
for (var i = 0; i < verifications.length; i++) {
|
2020-06-28 05:58:04 -06:00
|
|
|
if (verifications[i].type == "null") { continue; }
|
2020-06-27 06:59:48 -06:00
|
|
|
feedback += `${verifications[i].type}: `;
|
|
|
|
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 ✔</a>`;
|
|
|
|
} else {
|
|
|
|
feedback += `<a class="proofUrl" href="${verifications[i].proofUrl}">proof</a>`;
|
|
|
|
}
|
|
|
|
feedback += `<br>`;
|
2020-06-26 07:08:22 -06:00
|
|
|
}
|
2020-06-27 06:59:48 -06:00
|
|
|
feedback += `</p>`;
|
2020-06-26 07:08:22 -06:00
|
|
|
|
|
|
|
// Display feedback
|
|
|
|
elRes.innerHTML = feedback;
|
2020-06-26 09:52:01 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
async function displayProfile(opts) {
|
2020-06-29 03:18:06 -06:00
|
|
|
let keyData, feedback = "", notation, isVerified, verifications = [];
|
|
|
|
try {
|
|
|
|
keyData = await fetchKeys(opts);
|
|
|
|
} catch (e) {
|
|
|
|
feedback += `<p>There was a problem fetching the keys.</p>`;
|
|
|
|
feedback += `<code>${e}</code>`;
|
|
|
|
document.body.querySelector('#profileData').innerHTML = feedback;
|
|
|
|
document.body.querySelector('#profileName').innerHTML = "Could not load profile";
|
|
|
|
return;
|
|
|
|
}
|
2020-06-26 09:52:01 -06:00
|
|
|
let userData = keyData.user.user.userId;
|
|
|
|
|
2020-06-26 16:50:21 -06:00
|
|
|
document.body.querySelector('#profileName').innerHTML = userData.name;
|
2020-06-30 00:32:15 -06:00
|
|
|
document.body.querySelector('#profileAvatar').style = "";
|
|
|
|
document.body.querySelector('#profileAvatar').src = `https://www.gravatar.com/avatar/${SparkMD5.hash(userData.email)}?s=128&d=mm`;
|
2020-06-26 16:56:51 -06:00
|
|
|
document.title = `${userData.name} - Keyoxide`;
|
2020-06-26 09:52:01 -06:00
|
|
|
|
|
|
|
// Generate feedback
|
2020-06-27 13:03:49 -06:00
|
|
|
feedback += `<div class="profileDataItem profileDataItem--separator profileDataItem--noLabel">`;
|
2020-06-26 16:50:21 -06:00
|
|
|
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>`;
|
|
|
|
|
2020-06-30 00:44:12 -06:00
|
|
|
if (keyData.notations.length > 0) {
|
2020-06-29 03:07:23 -06:00
|
|
|
feedback += `<div class="profileDataItem profileDataItem--separator profileDataItem--noLabel">`;
|
|
|
|
feedback += `<div class="profileDataItem__label"></div>`;
|
|
|
|
feedback += `<div class="profileDataItem__value">proofs</div>`;
|
2020-06-26 16:50:21 -06:00
|
|
|
feedback += `</div>`;
|
2020-06-30 00:44:12 -06:00
|
|
|
|
|
|
|
feedback += `<div id="profileProofs">`;
|
2020-06-30 06:27:51 -06:00
|
|
|
feedback += `<div class="profileDataItem profileDataItem--noLabel">`;
|
2020-06-30 00:44:12 -06:00
|
|
|
feedback += `<div class="profileDataItem__label"></div>`;
|
|
|
|
feedback += `<div class="profileDataItem__value">Verifying proofs…</div>`;
|
|
|
|
feedback += `</div>`;
|
|
|
|
feedback += `</div>`;
|
2020-06-26 09:52:01 -06:00
|
|
|
}
|
|
|
|
|
2020-06-27 13:03:49 -06:00
|
|
|
feedback += `<div class="profileDataItem profileDataItem--separator profileDataItem--noLabel">`;
|
2020-06-27 06:53:32 -06:00
|
|
|
feedback += `<div class="profileDataItem__label"></div>`;
|
|
|
|
feedback += `<div class="profileDataItem__value">actions</div>`;
|
|
|
|
feedback += `</div>`;
|
2020-06-27 13:03:49 -06:00
|
|
|
feedback += `<div class="profileDataItem profileDataItem--noLabel">`;
|
2020-06-27 06:53:32 -06:00
|
|
|
feedback += `<div class="profileDataItem__label"></div>`;
|
2020-06-28 15:12:03 -06:00
|
|
|
feedback += `<div class="profileDataItem__value"><a href="/verify/${opts.mode}/${opts.input.replace(".", "_")}">verify signature</a></div>`;
|
2020-06-27 06:53:32 -06:00
|
|
|
feedback += `</div>`;
|
2020-06-27 13:03:49 -06:00
|
|
|
feedback += `<div class="profileDataItem profileDataItem--noLabel">`;
|
2020-06-27 06:53:32 -06:00
|
|
|
feedback += `<div class="profileDataItem__label"></div>`;
|
2020-06-28 15:12:03 -06:00
|
|
|
feedback += `<div class="profileDataItem__value"><a href="/encrypt/${opts.mode}/${opts.input.replace(".", "_")}">encrypt message</a></div>`;
|
2020-06-27 06:53:32 -06:00
|
|
|
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-30 00:44:12 -06:00
|
|
|
|
|
|
|
// Verify identity proofs
|
|
|
|
for (var i = 0; i < keyData.notations.length; i++) {
|
|
|
|
notation = keyData.notations[i];
|
|
|
|
if (notation[0] != "proof@metacode.biz") { continue; }
|
|
|
|
verifications.push(await verifyProof(notation[1], keyData.fingerprint));
|
|
|
|
}
|
|
|
|
|
|
|
|
// One-line sorting function (order verifications by type)
|
|
|
|
verifications = verifications.sort((a,b) => (a.type > b.type) ? 1 : ((b.type > a.type) ? -1 : 0));
|
|
|
|
|
|
|
|
feedback = "";
|
|
|
|
if (verifications.length > 0) {
|
|
|
|
for (var i = 0; i < verifications.length; i++) {
|
|
|
|
if (!verifications[i].type) { continue; }
|
|
|
|
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 ✔</a>`;
|
|
|
|
} else {
|
|
|
|
feedback += `<a class="proofUrl" href="${verifications[i].proofUrl}">proof</a>`;
|
|
|
|
}
|
|
|
|
feedback += `</div>`;
|
|
|
|
feedback += `</div>`;
|
|
|
|
}
|
|
|
|
} else {
|
2020-07-01 06:34:11 -06:00
|
|
|
feedback += `<div class="profileDataItem profileDataItem--noLabel">`;
|
|
|
|
feedback += `<div class="profileDataItem__label"></div>`;
|
2020-07-01 06:37:33 -06:00
|
|
|
feedback += `<div class="profileDataItem__value">No proofs found in key</div>`;
|
2020-07-01 06:34:11 -06:00
|
|
|
feedback += `</div>`;
|
2020-06-30 00:44:12 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// Display feedback
|
|
|
|
document.body.querySelector('#profileProofs').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-28 06:35:46 -06:00
|
|
|
let reVerify, match, 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-28 10:32:48 -06:00
|
|
|
output.type = "domain";
|
2020-06-26 16:50:21 -06:00
|
|
|
output.display = url.replace(/dns:/, '').replace(/\?type=TXT/, '');
|
2020-06-27 09:23:57 -06:00
|
|
|
output.proofUrl = `https://dns.shivering-isles.com/dns-query?name=${output.display}&type=TXT`;
|
2020-06-26 16:53:31 -06:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2020-06-28 06:35:46 -06:00
|
|
|
// Twitter
|
|
|
|
if (/^https:\/\/twitter.com/.test(url)) {
|
|
|
|
output.type = "twitter";
|
|
|
|
match = url.match(/https:\/\/twitter\.com\/(.*)\/status\/(.*)/);
|
|
|
|
output.display = `@${match[1]}`;
|
|
|
|
output.url = `https://twitter.com/${match[1]}`;
|
|
|
|
output.proofUrlFetch = `/server/verifyTweet.php?id=${match[2]}&fp=${fingerprint}`;
|
|
|
|
try {
|
|
|
|
response = await fetch(output.proofUrlFetch, {
|
|
|
|
headers: {
|
|
|
|
Accept: 'application/json'
|
|
|
|
},
|
|
|
|
credentials: 'omit'
|
|
|
|
});
|
|
|
|
if (!response.ok) {
|
|
|
|
throw new Error('Response failed: ' + response.status);
|
|
|
|
}
|
|
|
|
json = await response.json();
|
|
|
|
output.isVerified = json.verified;
|
|
|
|
} catch (e) {
|
|
|
|
} finally {
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
}
|
2020-06-26 07:08:22 -06:00
|
|
|
// HN
|
|
|
|
if (/^https:\/\/news.ycombinator.com/.test(url)) {
|
|
|
|
output.type = "hn";
|
2020-06-28 06:47:22 -06:00
|
|
|
match = url.match(/https:\/\/news.ycombinator.com\/user\?id=(.*)/);
|
|
|
|
output.display = match[1];
|
|
|
|
output.proofUrlFetch = `https://hacker-news.firebaseio.com/v0/user/${match[1]}.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-28 06:47:22 -06:00
|
|
|
match = url.match(/https:\/\/www.reddit.com\/user\/(.*)\/comments\/(.*)\/(.*)\//);
|
|
|
|
output.display = match[1];
|
|
|
|
output.url = `https://www.reddit.com/user/${match[1]}`;
|
2020-06-28 07:51:26 -06:00
|
|
|
output.proofUrlFetch = `/server/verifyReddit.php?user=${match[1]}&comment=${match[2]}&fp=${fingerprint}`;
|
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-28 07:51:26 -06:00
|
|
|
output.isVerified = json.verified;
|
2020-06-26 07:08:22 -06:00
|
|
|
} catch (e) {
|
|
|
|
} finally {
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Github
|
|
|
|
if (/^https:\/\/gist.github.com/.test(url)) {
|
|
|
|
output.type = "github";
|
2020-06-28 06:47:22 -06:00
|
|
|
match = url.match(/https:\/\/gist.github.com\/(.*)\/(.*)/);
|
|
|
|
output.display = match[1];
|
|
|
|
output.url = `https://github.com/${match[1]}`;
|
|
|
|
output.proofUrlFetch = `https://api.github.com/gists/${match[2]}`;
|
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;
|
|
|
|
}
|
|
|
|
}
|
2020-06-30 04:42:47 -06:00
|
|
|
// Lobsters
|
|
|
|
if (/^https:\/\/lobste.rs/.test(url)) {
|
|
|
|
output.type = "lobsters";
|
|
|
|
match = url.match(/https:\/\/lobste.rs\/u\/(.*)/);
|
|
|
|
output.display = match[1];
|
|
|
|
output.proofUrlFetch = `/server/verifyLobsters.php?user=${match[1]}&fp=${fingerprint}`;
|
|
|
|
try {
|
|
|
|
response = await fetch(output.proofUrlFetch, {
|
|
|
|
headers: {
|
|
|
|
Accept: 'application/json'
|
|
|
|
},
|
|
|
|
credentials: 'omit'
|
|
|
|
});
|
|
|
|
if (!response.ok) {
|
|
|
|
throw new Error('Response failed: ' + response.status);
|
|
|
|
}
|
|
|
|
json = await response.json();
|
|
|
|
output.isVerified = json.verified;
|
|
|
|
} catch (e) {
|
|
|
|
} finally {
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
}
|
2020-06-30 03:13:22 -06:00
|
|
|
// XMPP
|
|
|
|
if (/^xmpp:/.test(url)) {
|
|
|
|
output.type = "xmpp";
|
|
|
|
match = url.match(/xmpp:(.*)@(.*)/);
|
|
|
|
output.display = `${match[1]}@${match[2]}`;
|
|
|
|
}
|
2020-06-26 07:08:22 -06:00
|
|
|
// 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
|
2020-06-28 06:47:22 -06:00
|
|
|
match = url.match(/https:\/\/(.*)\/@(.*)/);
|
2020-06-26 07:08:22 -06:00
|
|
|
json.attachment.forEach((item, i) => {
|
|
|
|
if (item.value === fingerprint) {
|
|
|
|
output.type = "mastodon";
|
2020-06-28 06:47:22 -06:00
|
|
|
output.display = `@${match[2]}@${[match[1]]}`;
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-26 04:53:12 -06:00
|
|
|
async function fetchKeys(opts) {
|
2020-06-26 05:08:04 -06:00
|
|
|
// Init
|
2020-06-26 04:53:12 -06:00
|
|
|
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
|
2020-06-26 04:53:12 -06:00
|
|
|
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();
|
2020-06-26 04:53:12 -06:00
|
|
|
output.user = await output.publicKey.getPrimaryUser();
|
|
|
|
lastPrimarySig = output.user.selfCertification;
|
|
|
|
output.notations = lastPrimarySig.notations || [];
|
|
|
|
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
|
2020-06-29 12:58:34 -06:00
|
|
|
function encodeZBase32(data) {
|
|
|
|
// Source: https://github.com/openpgpjs/openpgpjs/blob/master/src/util.js
|
|
|
|
if (data.length === 0) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
const ALPHABET = "ybndrfg8ejkmcpqxot1uwisza345h769";
|
|
|
|
const SHIFT = 5;
|
|
|
|
const MASK = 31;
|
|
|
|
let buffer = data[0];
|
|
|
|
let index = 1;
|
|
|
|
let bitsLeft = 8;
|
|
|
|
let result = '';
|
|
|
|
while (bitsLeft > 0 || index < data.length) {
|
|
|
|
if (bitsLeft < SHIFT) {
|
|
|
|
if (index < data.length) {
|
|
|
|
buffer <<= 8;
|
|
|
|
buffer |= data[index++] & 0xff;
|
|
|
|
bitsLeft += 8;
|
|
|
|
} else {
|
|
|
|
const pad = SHIFT - bitsLeft;
|
|
|
|
buffer <<= pad;
|
|
|
|
bitsLeft += pad;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
bitsLeft -= SHIFT;
|
|
|
|
result += ALPHABET[MASK & (buffer >> bitsLeft)];
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function computeWKDLocalPart(message) {
|
|
|
|
const encoder = new TextEncoder();
|
|
|
|
const data = encoder.encode(message);
|
|
|
|
const hash = await crypto.subtle.digest('SHA-1', data);
|
|
|
|
return encodeZBase32(new Uint8Array(hash));
|
|
|
|
}
|
|
|
|
|
2020-06-26 04:53:12 -06:00
|
|
|
// 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"),
|
2020-06-28 16:09:31 -06:00
|
|
|
elProfileUid = document.body.querySelector("#profileUid"),
|
2020-06-29 12:58:34 -06:00
|
|
|
elProfileMode = document.body.querySelector("#profileMode"),
|
|
|
|
elUtilWKD = document.body.querySelector("#form-util-wkd");
|
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";
|
2020-06-26 04:53:12 -06:00
|
|
|
} 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";
|
2020-06-26 04:53:12 -06:00
|
|
|
} 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) {
|
2020-06-28 14:52:16 -06:00
|
|
|
let match, opts, profileUid = elProfileUid.innerHTML;
|
2020-06-28 16:09:31 -06:00
|
|
|
switch (elProfileMode.innerHTML) {
|
|
|
|
default:
|
|
|
|
case "auto":
|
|
|
|
if (/.*@.*/.test(profileUid)) {
|
|
|
|
// Match email for wkd
|
|
|
|
opts = {
|
|
|
|
input: profileUid,
|
|
|
|
mode: "wkd"
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// Match fingerprint for hkp
|
|
|
|
opts = {
|
|
|
|
input: profileUid,
|
|
|
|
mode: "hkp"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2020-06-28 16:10:13 -06:00
|
|
|
case "hkp":
|
|
|
|
case "wkd":
|
2020-06-28 16:09:31 -06:00
|
|
|
opts = {
|
|
|
|
input: profileUid,
|
|
|
|
mode: elProfileMode.innerHTML
|
|
|
|
}
|
|
|
|
break;
|
2020-06-26 09:52:01 -06:00
|
|
|
}
|
|
|
|
displayProfile(opts);
|
|
|
|
}
|
2020-06-29 12:58:34 -06:00
|
|
|
|
|
|
|
if (elUtilWKD) {
|
|
|
|
elUtilWKD.onsubmit = function (evt) {
|
|
|
|
evt.preventDefault();
|
|
|
|
}
|
|
|
|
|
|
|
|
const elInput = document.body.querySelector("#input");
|
|
|
|
const elOutput = document.body.querySelector("#output");
|
|
|
|
|
|
|
|
elInput.addEventListener("input", async function(evt) {
|
|
|
|
if (evt.target.value) {
|
|
|
|
elOutput.value = await computeWKDLocalPart(evt.target.value);
|
|
|
|
} else {
|
|
|
|
elOutput.value = "";
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|