2020-07-30 04:05:11 -06:00
|
|
|
/*
|
2021-01-11 06:58:47 -07:00
|
|
|
Copyright (C) 2021 Yarmo Mackenbach
|
2020-07-30 04:05:11 -06:00
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify it under
|
|
|
|
the terms of the GNU Affero General Public License as published by the Free
|
|
|
|
Software Foundation, either version 3 of the License, or (at your option)
|
|
|
|
any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful, but WITHOUT
|
|
|
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
|
|
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
|
|
|
details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Affero General Public License along
|
|
|
|
with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
Also add information on how to contact you by electronic and paper mail.
|
|
|
|
|
|
|
|
If your software can interact with users remotely through a computer network,
|
|
|
|
you should also make sure that it provides a way for users to get its source.
|
|
|
|
For example, if your program is a web application, its interface could display
|
|
|
|
a "Source" link that leads users to an archive of the code. There are many
|
|
|
|
ways you could offer source, and different solutions will be better for different
|
|
|
|
programs; see section 13 for the specific requirements.
|
|
|
|
|
|
|
|
You should also get your employer (if you work as a programmer) or school,
|
|
|
|
if any, to sign a "copyright disclaimer" for the program, if necessary. For
|
|
|
|
more information on this, and how to apply and follow the GNU AGPL, see <https://www.gnu.org/licenses/>.
|
|
|
|
*/
|
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 = "";
|
2020-07-01 18:44:16 -06:00
|
|
|
elRes.classList.remove('green');
|
|
|
|
elRes.classList.remove('red');
|
2020-06-25 10:01:06 -06:00
|
|
|
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-07-01 18:36:15 -06:00
|
|
|
const elEnc = document.body.querySelector("#message");
|
2020-06-25 10:01:06 -06:00
|
|
|
const elRes = document.body.querySelector("#result");
|
2020-07-01 18:36:15 -06:00
|
|
|
const elBtn = document.body.querySelector("[name='submit']");
|
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 = "";
|
2020-07-01 18:44:16 -06:00
|
|
|
elRes.classList.remove('green');
|
|
|
|
elRes.classList.remove('red');
|
2020-06-25 10:01:06 -06:00
|
|
|
|
|
|
|
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-07-01 18:36:15 -06:00
|
|
|
elEnc.toggleAttribute("readonly");
|
|
|
|
elBtn.setAttribute("disabled", "true");
|
2020-06-25 10:01:06 -06:00
|
|
|
};
|
|
|
|
|
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 = "";
|
2020-07-01 18:44:16 -06:00
|
|
|
elRes.classList.remove('green');
|
|
|
|
elRes.classList.remove('red');
|
2020-06-26 07:08:22 -06:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-08-15 00:41:04 -06:00
|
|
|
let notations = [], notationsRaw = [];
|
2020-08-14 16:48:48 -06:00
|
|
|
for (var i = 0; i < keyData.publicKey.users.length; i++) {
|
2020-08-15 00:41:04 -06:00
|
|
|
notationsRaw = notationsRaw.concat(keyData.publicKey.users[i].selfCertifications[0].notations);
|
2020-08-14 16:48:48 -06:00
|
|
|
}
|
2020-08-15 00:41:04 -06:00
|
|
|
notationsRaw.forEach((item, i) => {
|
|
|
|
if (item[0] == "proof@metacode.biz") {
|
|
|
|
notations.push(item[1]);
|
|
|
|
}
|
|
|
|
});
|
2020-08-14 16:48:48 -06:00
|
|
|
notations = Array.from(new Set(notations)); // Deduplicate (ES6)
|
|
|
|
|
2020-07-01 18:36:15 -06:00
|
|
|
// Display feedback
|
|
|
|
elRes.innerHTML = "Verifying proofs…";
|
|
|
|
|
2020-06-26 07:08:22 -06:00
|
|
|
let notation, isVerified, verifications = [];
|
2020-08-14 16:48:48 -06:00
|
|
|
for (var i = 0; i < notations.length; i++) {
|
|
|
|
notation = notations[i];
|
2020-08-15 00:41:04 -06:00
|
|
|
verifications.push(await verifyProof(notation, keyData.fingerprint));
|
2020-06-26 07:08:22 -06:00
|
|
|
}
|
|
|
|
|
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-07-11 14:22:20 -06:00
|
|
|
if (verifications[i].type == null) { continue; }
|
2020-06-27 06:59:48 -06:00
|
|
|
feedback += `${verifications[i].type}: `;
|
2020-07-19 04:28:29 -06:00
|
|
|
feedback += `<a class="proofDisplay" href="${verifications[i].url}" rel="me">${verifications[i].display}</a>`;
|
2020-06-27 06:59:48 -06:00
|
|
|
if (verifications[i].isVerified) {
|
2020-07-19 04:28:29 -06:00
|
|
|
feedback += `<a class="proofUrl proofUrl--verified" href="${verifications[i].proofUrl}">verified ✔</a>`;
|
2020-06-27 06:59:48 -06:00
|
|
|
} else {
|
2020-07-16 11:35:36 -06:00
|
|
|
feedback += `<a class="proofUrl" href="${verifications[i].proofUrl}">unverified</a>`;
|
2020-06-27 06:59:48 -06:00
|
|
|
}
|
|
|
|
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) {
|
2021-01-07 08:44:33 -07:00
|
|
|
let keyData, keyLink, sigVerification, sigKeyUri, fingerprint, feedback = "", verifications = [];
|
2020-07-23 02:36:42 -06:00
|
|
|
let icon_qr = '<svg style="width:24px;height:24px" viewBox="0 0 24 24"><path fill="#ffffff" d="M3,11H5V13H3V11M11,5H13V9H11V5M9,11H13V15H11V13H9V11M15,11H17V13H19V11H21V13H19V15H21V19H19V21H17V19H13V21H11V17H15V15H17V13H15V11M19,19V15H17V19H19M15,3H21V9H15V3M17,5V7H19V5H17M3,3H9V9H3V3M5,5V7H7V5H5M3,15H9V21H3V15M5,17V19H7V17H5Z" /></svg>';
|
|
|
|
|
2021-03-02 07:26:13 -07:00
|
|
|
const doipOpts = {
|
|
|
|
proxyPolicy: 'adaptive',
|
|
|
|
}
|
|
|
|
|
2021-01-07 08:44:33 -07:00
|
|
|
// Reset the avatar
|
|
|
|
document.body.querySelector('#profileAvatar').style = 'display: none';
|
|
|
|
document.body.querySelector('#profileAvatar').src = '/static/img/avatar_placeholder.png';
|
|
|
|
|
|
|
|
if (opts.mode == 'sig') {
|
|
|
|
try {
|
2021-03-02 07:26:13 -07:00
|
|
|
sigVerification = await doip.signatures.verify(opts.input, doipOpts);
|
2021-01-09 08:28:42 -07:00
|
|
|
keyData = sigVerification.publicKey.data;
|
|
|
|
fingerprint = sigVerification.publicKey.fingerprint;
|
2021-01-07 08:44:33 -07:00
|
|
|
} catch (e) {
|
|
|
|
feedback += `<p>There was a problem reading the signature.</p>`;
|
2021-01-10 09:12:02 -07:00
|
|
|
if ('errors' in e) {
|
|
|
|
feedback += `<code>${e.errors.join(', ')}</code>`;
|
|
|
|
} else {
|
|
|
|
feedback += `<code>${e}</code>`;
|
|
|
|
}
|
2021-01-07 08:44:33 -07:00
|
|
|
document.body.querySelector('#profileData').innerHTML = feedback;
|
|
|
|
document.body.querySelector('#profileName').innerHTML = "Could not load profile";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
let keyURI;
|
|
|
|
if (opts.mode === 'hkp' && opts.server) {
|
|
|
|
keyURI = `${opts.mode}:${opts.server}:${opts.input}`
|
|
|
|
} else {
|
|
|
|
keyURI = `${opts.mode}:${opts.input}`
|
|
|
|
}
|
|
|
|
keyData = await doip.keys.fetch.uri(keyURI);
|
|
|
|
fingerprint = keyData.keyPacket.getFingerprint();
|
|
|
|
} 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;
|
2021-01-05 05:20:06 -07:00
|
|
|
}
|
2020-06-29 03:18:06 -06:00
|
|
|
}
|
2020-08-14 16:48:48 -06:00
|
|
|
|
2020-12-11 04:06:21 -07:00
|
|
|
const userPrimary = await keyData.getPrimaryUser();
|
|
|
|
const userData = userPrimary.user.userId;
|
|
|
|
const userName = userData.name ? userData.name : userData.email;
|
|
|
|
const userMail = userData.email ? userData.email : null;
|
2020-06-26 09:52:01 -06:00
|
|
|
|
2020-08-20 07:24:29 -06:00
|
|
|
let imgUri = null;
|
|
|
|
|
2020-07-03 04:06:55 -06:00
|
|
|
// Determine WKD or HKP link
|
2021-01-07 08:44:33 -07:00
|
|
|
let keyUriMode = opts.mode;
|
2021-01-09 08:28:42 -07:00
|
|
|
let keyUriServer = null;
|
2021-01-07 08:44:33 -07:00
|
|
|
let keyUriId = opts.input;
|
|
|
|
if (opts.mode === 'sig') {
|
2021-01-09 08:28:42 -07:00
|
|
|
const keyUriMatch = sigVerification.publicKey.uri.match(/([^:]*)(?:\:(.*))?:(.*)/);
|
2021-01-07 08:44:33 -07:00
|
|
|
keyUriMode = keyUriMatch[1];
|
2021-01-09 08:28:42 -07:00
|
|
|
keyUriServer = keyUriMatch[2];
|
|
|
|
keyUriId = keyUriMatch[3];
|
2021-01-07 08:44:33 -07:00
|
|
|
}
|
2021-03-02 07:26:13 -07:00
|
|
|
|
2021-01-07 08:44:33 -07:00
|
|
|
switch (keyUriMode) {
|
2020-07-05 07:04:12 -06:00
|
|
|
case "wkd":
|
2021-01-07 08:44:33 -07:00
|
|
|
const [, localPart, domain] = /(.*)@(.*)/.exec(keyUriId);
|
2020-07-05 07:04:12 -06:00
|
|
|
const localEncoded = await computeWKDLocalPart(localPart.toLowerCase());
|
|
|
|
const urlAdvanced = `https://openpgpkey.${domain}/.well-known/openpgpkey/${domain}/hu/${localEncoded}`;
|
|
|
|
const urlDirect = `https://${domain}/.well-known/openpgpkey/hu/${localEncoded}`;
|
2020-07-03 04:06:55 -06:00
|
|
|
|
2020-07-03 04:14:50 -06:00
|
|
|
try {
|
2020-07-05 07:04:12 -06:00
|
|
|
keyLink = await fetch(urlAdvanced).then(function(response) {
|
2020-07-03 04:06:55 -06:00
|
|
|
if (response.status === 200) {
|
2020-07-05 07:04:12 -06:00
|
|
|
return urlAdvanced;
|
2020-07-03 04:06:55 -06:00
|
|
|
}
|
|
|
|
});
|
2020-07-03 04:15:37 -06:00
|
|
|
} catch (e) {
|
2020-07-03 04:16:24 -06:00
|
|
|
console.warn(e);
|
2020-07-03 04:06:55 -06:00
|
|
|
}
|
2020-07-05 07:04:12 -06:00
|
|
|
if (!keyLink) {
|
|
|
|
try {
|
|
|
|
keyLink = await fetch(urlDirect).then(function(response) {
|
|
|
|
if (response.status === 200) {
|
|
|
|
return urlDirect;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} catch (e) {
|
|
|
|
console.warn(e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!keyLink) {
|
2021-01-09 08:28:42 -07:00
|
|
|
keyLink = `https://${keyUriServer ? keyUriServer : 'keys.openpgp.org'}/pks/lookup?op=get&options=mr&search=0x${fingerprint}`;
|
2020-07-05 07:04:12 -06:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "hkp":
|
2021-01-09 08:28:42 -07:00
|
|
|
keyLink = `https://${keyUriServer ? keyUriServer : 'keys.openpgp.org'}/pks/lookup?op=get&options=mr&search=0x${fingerprint}`;
|
2020-07-05 07:04:12 -06:00
|
|
|
break;
|
|
|
|
|
|
|
|
case "keybase":
|
|
|
|
keyLink = opts.keyLink;
|
|
|
|
break;
|
2020-07-03 04:06:55 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// Fill in various data
|
2020-07-30 15:51:47 -06:00
|
|
|
document.body.querySelector('#profileName').innerHTML = userName;
|
2020-06-30 00:32:15 -06:00
|
|
|
document.body.querySelector('#profileAvatar').style = "";
|
2020-07-23 02:44:07 -06:00
|
|
|
const profileHash = openpgp.util.str_to_hex(openpgp.util.Uint8Array_to_str(await openpgp.crypto.hash.md5(openpgp.util.str_to_Uint8Array(userData.email))));
|
2020-08-20 07:24:29 -06:00
|
|
|
if (imgUri) {
|
|
|
|
document.body.querySelector('#profileAvatar').src = imgUri;
|
|
|
|
} else {
|
|
|
|
document.body.querySelector('#profileAvatar').src = `https://www.gravatar.com/avatar/${profileHash}?s=128&d=mm`;
|
|
|
|
}
|
2020-07-30 15:51:47 -06:00
|
|
|
document.title = `${userName} - 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>`;
|
2020-12-25 19:38:41 -07:00
|
|
|
feedback += `<div class="profileDataItem__value">General information</div>`;
|
2020-06-26 16:50:21 -06:00
|
|
|
feedback += `</div>`;
|
2020-12-11 04:06:21 -07:00
|
|
|
|
2020-06-26 16:50:21 -06:00
|
|
|
feedback += `<div class="profileDataItem">`;
|
|
|
|
feedback += `<div class="profileDataItem__label">fingerprint</div>`;
|
2020-12-11 04:06:21 -07:00
|
|
|
feedback += `<div class="profileDataItem__value"><a href="${keyLink}">${fingerprint}</a>`;
|
2020-07-05 07:04:12 -06:00
|
|
|
if (opts.mode == "hkp") {
|
2020-12-11 04:06:21 -07:00
|
|
|
feedback += `<a class="proofQR green" href="/util/qr/${encodeURIComponent(`OPENPGP4FPR:${fingerprint.toUpperCase()}`)}" target="_blank" title="QR Code">${icon_qr}</a>`;
|
2020-07-05 07:04:12 -06:00
|
|
|
}
|
2020-07-23 02:36:42 -06:00
|
|
|
feedback += `</div></div>`;
|
|
|
|
|
2020-12-11 04:06:21 -07:00
|
|
|
feedback += `<div id="profileProofs">`;
|
|
|
|
feedback += `<div class="profileDataItem profileDataItem--noLabel">`;
|
|
|
|
feedback += `<div class="profileDataItem__label"></div>`;
|
|
|
|
feedback += `<div class="profileDataItem__value">Verifying proofs…</div>`;
|
|
|
|
feedback += `</div>`;
|
|
|
|
feedback += `</div>`;
|
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>`;
|
2020-12-25 19:38:41 -07:00
|
|
|
feedback += `<div class="profileDataItem__value">Actions</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>`;
|
2021-01-07 13:30:21 -07:00
|
|
|
feedback += `<div class="profileDataItem__value"><a href="/verify/${keyUriMode}/${keyUriId}">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>`;
|
2021-01-07 13:30:21 -07:00
|
|
|
feedback += `<div class="profileDataItem__value"><a href="/encrypt/${keyUriMode}/${keyUriId}">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
|
|
|
|
2020-12-11 04:06:21 -07:00
|
|
|
try {
|
2021-01-07 08:44:33 -07:00
|
|
|
if (sigVerification) {
|
|
|
|
verifications = sigVerification.claims
|
|
|
|
} else {
|
2021-03-02 07:26:13 -07:00
|
|
|
verifications = await doip.claims.verify(keyData, fingerprint, doipOpts)
|
2021-01-07 08:44:33 -07:00
|
|
|
}
|
2020-12-11 04:06:21 -07:00
|
|
|
} catch (e) {
|
|
|
|
feedback += `<p>There was a problem verifying the claims.</p>`;
|
|
|
|
feedback += `<code>${e}</code>`;
|
|
|
|
document.body.querySelector('#profileData').innerHTML = feedback;
|
|
|
|
document.body.querySelector('#profileName').innerHTML = "Could not load profile";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-07-03 04:18:49 -06:00
|
|
|
// Exit if no notations are available
|
2020-12-11 04:06:21 -07:00
|
|
|
if (verifications.length == 0) {
|
2020-07-03 04:18:49 -06:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-12-11 04:06:21 -07:00
|
|
|
feedback = "";
|
2021-01-07 08:44:33 -07:00
|
|
|
|
|
|
|
if (opts.mode === 'sig') {
|
|
|
|
feedback += `<div class="profileDataItem profileDataItem--separator profileDataItem--noLabel">`;
|
|
|
|
feedback += `<div class="profileDataItem__label"></div>`;
|
|
|
|
feedback += `<div class="profileDataItem__value">proofs</div>`;
|
|
|
|
feedback += `</div>`;
|
|
|
|
|
|
|
|
verifications = verifications.filter((a) => (a && a.errors.length == 0 && a.serviceproviderData))
|
|
|
|
verifications = verifications.sort((a,b) => (a.serviceproviderData.serviceprovider.name > b.serviceproviderData.serviceprovider.name) ? 1 : ((b.serviceproviderData.serviceprovider.name > a.serviceproviderData.serviceprovider.name) ? -1 : 0));
|
|
|
|
|
|
|
|
verifications.forEach((claim, i) => {
|
|
|
|
const claimData = claim.serviceproviderData;
|
|
|
|
if (!claimData.serviceprovider.name) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
feedback += `<div class="profileDataItem">`;
|
2021-01-10 09:34:05 -07:00
|
|
|
feedback += `<div class="profileDataItem__label">${capitalizeLetteredServices(claimData.serviceprovider.name)}</div>`;
|
2021-01-07 08:44:33 -07:00
|
|
|
feedback += `<div class="profileDataItem__value">`;
|
|
|
|
feedback += `<a class="proofDisplay" href="${claimData.profile.uri}" rel="me">${claimData.profile.display}</a>`;
|
|
|
|
if (claim.isVerified) {
|
|
|
|
feedback += `<a class="proofUrl proofUrl--verified" href="${claimData.proof.uri}">verified ✔</a>`;
|
|
|
|
} else {
|
|
|
|
feedback += `<a class="proofUrl" href="${claimData.proof.uri}">unverified</a>`;
|
2020-12-26 15:48:17 -07:00
|
|
|
}
|
2021-01-07 08:44:33 -07:00
|
|
|
if (claim.isVerified && claimData.profile.qr) {
|
|
|
|
feedback += `<a class="proofQR green" href="/util/qr/${encodeURIComponent(claimData.profile.qr)}" target="_blank" title="QR Code">${icon_qr}</a>`;
|
|
|
|
}
|
|
|
|
feedback += `</div>`;
|
|
|
|
feedback += `</div>`;
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
let primaryClaims;
|
|
|
|
|
|
|
|
if (userMail) {
|
|
|
|
verifications.forEach((userId, i) => {
|
|
|
|
if (!keyData.users[i].userId) {
|
|
|
|
keyData.users[i].userId = {
|
|
|
|
email: 'email not specified'
|
|
|
|
}
|
|
|
|
}
|
2020-12-26 15:48:17 -07:00
|
|
|
|
2021-01-07 08:44:33 -07:00
|
|
|
if (keyData.users[i].userId.email != userMail) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
feedback += `<div class="profileDataItem profileDataItem--separator profileDataItem--noLabel">`;
|
|
|
|
feedback += `<div class="profileDataItem__label"></div>`;
|
|
|
|
// feedback += `<div class="profileDataItem__value"><a href="mailto:${keyData.users[i].userId.email}">${keyData.users[i].userId.email}</a> (primary)</div>`;
|
2021-01-07 13:30:21 -07:00
|
|
|
feedback += `<div class="profileDataItem__value">${keyData.users[i].userId.email} <small class="primary">primary</small></div>`;
|
2021-01-07 08:44:33 -07:00
|
|
|
feedback += `</div>`;
|
|
|
|
|
|
|
|
if (userId.length == 0) {
|
|
|
|
feedback += `<div class="profileDataItem profileDataItem--noLabel">`;
|
|
|
|
feedback += `<div class="profileDataItem__label"></div>`;
|
|
|
|
feedback += `<div class="profileDataItem__value">No claims associated</div>`;
|
|
|
|
feedback += `</div>`;
|
|
|
|
return;
|
|
|
|
}
|
2021-03-02 07:26:13 -07:00
|
|
|
|
2021-01-07 08:44:33 -07:00
|
|
|
userId = userId.filter((a) => (a && a.errors.length == 0 && a.serviceproviderData))
|
|
|
|
userId = userId.sort((a,b) => (a.serviceproviderData.serviceprovider.name > b.serviceproviderData.serviceprovider.name) ? 1 : ((b.serviceproviderData.serviceprovider.name > a.serviceproviderData.serviceprovider.name) ? -1 : 0));
|
|
|
|
|
|
|
|
primaryClaims = userId;
|
|
|
|
|
|
|
|
userId.forEach((claim, i) => {
|
|
|
|
const claimData = claim.serviceproviderData;
|
|
|
|
if (!claimData.serviceprovider.name) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
feedback += `<div class="profileDataItem">`;
|
2021-01-10 09:34:05 -07:00
|
|
|
feedback += `<div class="profileDataItem__label">${capitalizeLetteredServices(claimData.serviceprovider.name)}</div>`;
|
2021-01-07 08:44:33 -07:00
|
|
|
feedback += `<div class="profileDataItem__value">`;
|
|
|
|
feedback += `<a class="proofDisplay" href="${claimData.profile.uri}" rel="me">${claimData.profile.display}</a>`;
|
|
|
|
if (claim.isVerified) {
|
|
|
|
feedback += `<a class="proofUrl proofUrl--verified" href="${claimData.proof.uri}">verified ✔</a>`;
|
|
|
|
} else {
|
|
|
|
feedback += `<a class="proofUrl" href="${claimData.proof.uri}">unverified</a>`;
|
|
|
|
}
|
|
|
|
if (claim.isVerified && claimData.profile.qr) {
|
|
|
|
feedback += `<a class="proofQR green" href="/util/qr/${encodeURIComponent(claimData.profile.qr)}" target="_blank" title="QR Code">${icon_qr}</a>`;
|
|
|
|
}
|
|
|
|
feedback += `</div>`;
|
|
|
|
feedback += `</div>`;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2020-12-26 15:48:17 -07:00
|
|
|
|
2021-01-07 08:44:33 -07:00
|
|
|
verifications.forEach((userId, i) => {
|
|
|
|
if (userMail && keyData.users[i].userId.email == userMail) {
|
2020-12-11 04:06:21 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
feedback += `<div class="profileDataItem profileDataItem--separator profileDataItem--noLabel">`;
|
|
|
|
feedback += `<div class="profileDataItem__label"></div>`;
|
2021-01-07 08:44:33 -07:00
|
|
|
feedback += `<div class="profileDataItem__value">${keyData.users[i].userId.email}</div>`;
|
2020-12-11 04:06:21 -07:00
|
|
|
feedback += `</div>`;
|
|
|
|
|
2020-12-25 19:38:41 -07:00
|
|
|
if (userId.length === 0) {
|
2020-12-11 04:06:21 -07:00
|
|
|
feedback += `<div class="profileDataItem profileDataItem--noLabel">`;
|
|
|
|
feedback += `<div class="profileDataItem__label"></div>`;
|
|
|
|
feedback += `<div class="profileDataItem__value">No claims associated</div>`;
|
|
|
|
feedback += `</div>`;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-12-20 15:32:59 -07:00
|
|
|
userId = userId.filter((a) => (a && a.errors.length == 0 && a.serviceproviderData))
|
2020-12-11 04:06:21 -07:00
|
|
|
userId = userId.sort((a,b) => (a.serviceproviderData.serviceprovider.name > b.serviceproviderData.serviceprovider.name) ? 1 : ((b.serviceproviderData.serviceprovider.name > a.serviceproviderData.serviceprovider.name) ? -1 : 0));
|
|
|
|
|
2021-01-07 08:44:33 -07:00
|
|
|
if (primaryClaims && primaryClaims.toString() == userId.toString()) {
|
|
|
|
feedback += `<div class="profileDataItem profileDataItem--noLabel">`;
|
|
|
|
feedback += `<div class="profileDataItem__label"></div>`;
|
|
|
|
feedback += `<div class="profileDataItem__value">Identical to primary</div>`;
|
|
|
|
feedback += `</div>`;
|
|
|
|
return;
|
|
|
|
}
|
2020-12-14 01:40:09 -07:00
|
|
|
|
2020-12-11 04:06:21 -07:00
|
|
|
userId.forEach((claim, i) => {
|
|
|
|
const claimData = claim.serviceproviderData;
|
|
|
|
if (!claimData.serviceprovider.name) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
feedback += `<div class="profileDataItem">`;
|
2020-12-25 19:38:41 -07:00
|
|
|
feedback += `<div class="profileDataItem__label">${capitalizeLetteredServices(claimData.serviceprovider.name)}</div>`;
|
2020-12-11 04:06:21 -07:00
|
|
|
feedback += `<div class="profileDataItem__value">`;
|
|
|
|
feedback += `<a class="proofDisplay" href="${claimData.profile.uri}" rel="me">${claimData.profile.display}</a>`;
|
|
|
|
if (claim.isVerified) {
|
|
|
|
feedback += `<a class="proofUrl proofUrl--verified" href="${claimData.proof.uri}">verified ✔</a>`;
|
|
|
|
} else {
|
|
|
|
feedback += `<a class="proofUrl" href="${claimData.proof.uri}">unverified</a>`;
|
|
|
|
}
|
|
|
|
if (claim.isVerified && claimData.profile.qr) {
|
|
|
|
feedback += `<a class="proofQR green" href="/util/qr/${encodeURIComponent(claimData.profile.qr)}" target="_blank" title="QR Code">${icon_qr}</a>`;
|
|
|
|
}
|
|
|
|
feedback += `</div>`;
|
|
|
|
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-07-22 07:37:38 -06:00
|
|
|
let reVerify, match, output = {url: url, type: null, proofUrl: url, proofUrlFetch: null, isVerified: false, display: null, qr: null};
|
2020-06-26 07:08:22 -06:00
|
|
|
|
2020-10-01 05:35:04 -06:00
|
|
|
try {
|
|
|
|
// DNS
|
|
|
|
if (/^dns:/.test(url)) {
|
|
|
|
output.type = "domain";
|
|
|
|
output.display = url.replace(/dns:/, '').replace(/\?type=TXT/, '');
|
|
|
|
output.proofUrl = `https://dns.shivering-isles.com/dns-query?name=${output.display}&type=TXT`;
|
|
|
|
output.proofUrlFetch = output.proofUrl;
|
|
|
|
output.url = `https://${output.display}`;
|
2020-06-26 07:08:22 -06:00
|
|
|
|
2020-10-01 05:35:04 -06:00
|
|
|
try {
|
|
|
|
response = await fetch(output.proofUrlFetch, {
|
|
|
|
headers: {
|
|
|
|
Accept: 'application/json'
|
|
|
|
},
|
|
|
|
credentials: 'omit'
|
|
|
|
});
|
|
|
|
if (!response.ok) {
|
|
|
|
throw new Error('Response failed: ' + response.status);
|
2020-06-26 07:08:22 -06:00
|
|
|
}
|
2020-10-01 05:35:04 -06:00
|
|
|
json = await response.json();
|
|
|
|
reVerify = new RegExp(`openpgp4fpr:${fingerprint}`, 'i');
|
|
|
|
json.Answer.forEach((item, i) => {
|
|
|
|
if (reVerify.test(item.data)) {
|
|
|
|
output.isVerified = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} catch (e) {
|
|
|
|
} finally {
|
|
|
|
return output;
|
|
|
|
}
|
2020-06-26 07:08:22 -06:00
|
|
|
}
|
2020-10-01 05:35:04 -06:00
|
|
|
// XMPP
|
|
|
|
if (/^xmpp:/.test(url)) {
|
|
|
|
output.type = "xmpp";
|
|
|
|
match = url.match(/xmpp:([a-zA-Z0-9\.\-\_]*)@([a-zA-Z0-9\.\-\_]*)(?:\?(.*))?/);
|
|
|
|
output.display = `${match[1]}@${match[2]}`;
|
|
|
|
output.proofUrl = `https://PLACEHOLDER__XMPP_VCARD_SERVER_DOMAIN/api/vcard/${output.display}/DESC`;
|
|
|
|
output.qr = url;
|
2020-07-22 07:37:38 -06:00
|
|
|
|
2020-10-01 05:35:04 -06:00
|
|
|
try {
|
|
|
|
response = await fetchWithTimeout(output.proofUrl);
|
|
|
|
if (!response.ok) {
|
|
|
|
throw new Error('Response failed: ' + response.status);
|
|
|
|
}
|
|
|
|
json = await response.json();
|
|
|
|
reVerify = new RegExp(`[Verifying my OpenPGP key: openpgp4fpr:${fingerprint}]`, 'i');
|
|
|
|
if (reVerify.test(json)) {
|
|
|
|
output.isVerified = true;
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
} finally {
|
|
|
|
return output;
|
2020-07-22 06:34:52 -06:00
|
|
|
}
|
|
|
|
}
|
2020-10-01 05:35:04 -06:00
|
|
|
// Twitter
|
|
|
|
if (/^https:\/\/twitter.com/.test(url)) {
|
|
|
|
output.type = "twitter";
|
|
|
|
match = url.match(/https:\/\/twitter\.com\/(.*)\/status\/([0-9]*)(?:\?.*)?/);
|
|
|
|
output.display = `@${match[1]}`;
|
|
|
|
output.url = `https://twitter.com/${match[1]}`;
|
|
|
|
output.proofUrlFetch = `/server/verify/twitter
|
2020-08-08 13:40:22 -06:00
|
|
|
?tweetId=${encodeURIComponent(match[2])}
|
2020-08-15 05:51:16 -06:00
|
|
|
&account=${encodeURIComponent(match[1])}
|
2020-08-08 13:40:22 -06:00
|
|
|
&fingerprint=${fingerprint}`;
|
2020-10-01 05:35:04 -06:00
|
|
|
try {
|
|
|
|
response = await fetch(output.proofUrlFetch);
|
|
|
|
if (!response.ok) {
|
|
|
|
throw new Error('Response failed: ' + response.status);
|
|
|
|
}
|
|
|
|
json = await response.json();
|
|
|
|
output.isVerified = json.isVerified;
|
|
|
|
} catch (e) {
|
|
|
|
} finally {
|
|
|
|
return output;
|
2020-06-28 06:35:46 -06:00
|
|
|
}
|
|
|
|
}
|
2020-10-01 05:35:04 -06:00
|
|
|
// HN
|
|
|
|
if (/^https:\/\/news.ycombinator.com/.test(url)) {
|
|
|
|
output.type = "hackernews";
|
|
|
|
match = url.match(/https:\/\/news.ycombinator.com\/user\?id=(.*)/);
|
|
|
|
output.display = match[1];
|
|
|
|
output.proofUrl = `https://hacker-news.firebaseio.com/v0/user/${match[1]}.json`;
|
|
|
|
output.proofUrlFetch = output.proofUrl;
|
|
|
|
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();
|
|
|
|
reVerify = new RegExp(`openpgp4fpr:${fingerprint}`, 'i');
|
|
|
|
if (reVerify.test(json.about)) {
|
|
|
|
output.isVerified = true;
|
|
|
|
}
|
|
|
|
} catch (e) {
|
2020-06-26 16:50:21 -06:00
|
|
|
}
|
2020-07-28 19:06:56 -06:00
|
|
|
|
2020-10-01 05:35:04 -06:00
|
|
|
if (!output.isVerified) {
|
|
|
|
output.proofUrlFetch = `/server/verify/proxy
|
2020-08-08 13:40:22 -06:00
|
|
|
?url=${encodeURIComponent(output.proofUrl)}
|
|
|
|
&fingerprint=${fingerprint}
|
|
|
|
&checkRelation=contains
|
|
|
|
&checkPath=about
|
|
|
|
&checkClaimFormat=message`;
|
2020-10-01 05:35:04 -06:00
|
|
|
try {
|
|
|
|
response = await fetch(output.proofUrlFetch);
|
|
|
|
if (!response.ok) {
|
|
|
|
throw new Error('Response failed: ' + response.status);
|
|
|
|
}
|
|
|
|
json = await response.json();
|
|
|
|
output.isVerified = json.verified;
|
|
|
|
} catch (e) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
// dev.to
|
|
|
|
if (/^https:\/\/dev\.to\//.test(url)) {
|
|
|
|
output.type = "dev.to";
|
|
|
|
match = url.match(/https:\/\/dev\.to\/(.*)\/(.*)/);
|
|
|
|
output.display = match[1];
|
|
|
|
output.url = `https://dev.to/${match[1]}`;
|
|
|
|
output.proofUrlFetch = `https://dev.to/api/articles/${match[1]}/${match[2]}`;
|
2020-07-28 19:06:56 -06:00
|
|
|
try {
|
2020-08-08 13:40:22 -06:00
|
|
|
response = await fetch(output.proofUrlFetch);
|
2020-07-28 19:06:56 -06:00
|
|
|
if (!response.ok) {
|
|
|
|
throw new Error('Response failed: ' + response.status);
|
|
|
|
}
|
|
|
|
json = await response.json();
|
2020-10-01 05:35:04 -06:00
|
|
|
reVerify = new RegExp(`[Verifying my OpenPGP key: openpgp4fpr:${fingerprint}]`, 'i');
|
|
|
|
if (reVerify.test(json.body_markdown)) {
|
|
|
|
output.isVerified = true;
|
|
|
|
}
|
2020-07-30 16:17:11 -06:00
|
|
|
} catch (e) {
|
2020-10-01 05:35:04 -06:00
|
|
|
} finally {
|
|
|
|
return output;
|
2020-07-30 16:17:11 -06:00
|
|
|
}
|
2020-07-28 19:06:56 -06:00
|
|
|
}
|
2020-10-01 05:35:04 -06:00
|
|
|
// Reddit
|
|
|
|
if (/^https:\/\/(?:www\.)?reddit\.com\/user/.test(url)) {
|
|
|
|
output.type = "reddit";
|
|
|
|
match = url.match(/https:\/\/(?:www\.)?reddit\.com\/user\/(.*)\/comments\/(.*)\/([^/]*)/);
|
|
|
|
output.display = match[1];
|
|
|
|
output.url = `https://www.reddit.com/user/${match[1]}`;
|
|
|
|
output.proofUrl = `https://www.reddit.com/user/${match[1]}/comments/${match[2]}.json`;
|
|
|
|
output.proofUrlFetch = `/server/verify/proxy
|
2020-08-08 13:40:22 -06:00
|
|
|
?url=${encodeURIComponent(output.proofUrl)}
|
|
|
|
&fingerprint=${fingerprint}
|
|
|
|
&checkRelation=contains
|
|
|
|
&checkPath=data,children,data,selftext
|
|
|
|
&checkClaimFormat=message`;
|
2020-10-01 05:35:04 -06:00
|
|
|
try {
|
|
|
|
response = await fetch(output.proofUrlFetch);
|
|
|
|
if (!response.ok) {
|
|
|
|
throw new Error('Response failed: ' + response.status);
|
|
|
|
}
|
|
|
|
json = await response.json();
|
|
|
|
output.isVerified = json.isVerified;
|
|
|
|
} catch (e) {
|
|
|
|
} finally {
|
|
|
|
return output;
|
2020-06-26 07:08:22 -06:00
|
|
|
}
|
|
|
|
}
|
2020-10-01 05:35:04 -06:00
|
|
|
// Gitea
|
|
|
|
if (/\/gitea_proof$/.test(url)) {
|
|
|
|
output.type = "gitea";
|
|
|
|
match = url.match(/https:\/\/(.*)\/(.*)\/gitea_proof/);
|
|
|
|
output.display = `${match[2]}@${match[1]}`;
|
|
|
|
output.url = `https://${match[1]}/${match[2]}`;
|
|
|
|
output.proofUrl = `https://${match[1]}/api/v1/repos/${match[2]}/gitea_proof`;
|
|
|
|
output.proofUrlFetch = `/server/verify/proxy
|
2020-08-20 08:27:12 -06:00
|
|
|
?url=${encodeURIComponent(output.proofUrl)}
|
|
|
|
&fingerprint=${fingerprint}
|
|
|
|
&checkRelation=eq
|
|
|
|
&checkPath=description
|
|
|
|
&checkClaimFormat=message`;
|
2020-10-01 05:35:04 -06:00
|
|
|
output.proofUrl = url; // Actually set the proof URL to something user-friendly
|
|
|
|
try {
|
|
|
|
response = await fetch(output.proofUrlFetch);
|
|
|
|
if (!response.ok) {
|
|
|
|
throw new Error('Response failed: ' + response.status);
|
|
|
|
}
|
|
|
|
json = await response.json();
|
|
|
|
output.isVerified = json.isVerified;
|
|
|
|
} catch (e) {
|
|
|
|
} finally {
|
|
|
|
return output;
|
2020-08-20 08:27:12 -06:00
|
|
|
}
|
|
|
|
}
|
2020-10-01 05:35:04 -06:00
|
|
|
// Github
|
|
|
|
if (/^https:\/\/gist.github.com/.test(url)) {
|
|
|
|
output.type = "github";
|
|
|
|
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]}`;
|
|
|
|
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();
|
|
|
|
reVerify = new RegExp(`[Verifying my OpenPGP key: openpgp4fpr:${fingerprint}]`, 'i');
|
|
|
|
if (reVerify.test(json.files["openpgp.md"].content)) {
|
|
|
|
output.isVerified = true;
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
} finally {
|
|
|
|
return output;
|
2020-06-26 16:50:21 -06:00
|
|
|
}
|
2020-06-26 07:08:22 -06:00
|
|
|
}
|
2020-10-01 05:35:04 -06:00
|
|
|
// GitLab
|
|
|
|
if (/\/gitlab_proof$/.test(url)) {
|
|
|
|
output.type = "gitlab";
|
|
|
|
match = url.match(/https:\/\/(.*)\/(.*)\/gitlab_proof/);
|
|
|
|
output.display = `${match[2]}@${match[1]}`;
|
|
|
|
output.url = `https://${match[1]}/${match[2]}`;
|
|
|
|
output.proofUrlFetch = `https://${match[1]}/api/v4/users?username=${match[2]}`;
|
|
|
|
try {
|
|
|
|
const opts = {
|
|
|
|
headers: {
|
|
|
|
Accept: 'application/json'
|
|
|
|
},
|
|
|
|
credentials: 'omit'
|
|
|
|
};
|
|
|
|
// Get user
|
|
|
|
response = await fetch(output.proofUrlFetch, opts);
|
|
|
|
if (!response.ok) {
|
|
|
|
throw new Error('Response failed: ' + response.status);
|
|
|
|
}
|
|
|
|
json = await response.json();
|
|
|
|
const user = json.find(user => user.username === match[2]);
|
|
|
|
if (!user) {
|
|
|
|
throw new Error('No user with username ' + match[2]);
|
|
|
|
}
|
|
|
|
// Get project
|
|
|
|
output.proofUrlFetch = `https://${match[1]}/api/v4/users/${user.id}/projects`;
|
|
|
|
response = await fetch(output.proofUrlFetch, opts);
|
|
|
|
if (!response.ok) {
|
|
|
|
throw new Error('Response failed: ' + response.status);
|
|
|
|
}
|
|
|
|
json = await response.json();
|
|
|
|
const project = json.find(proj => proj.path === 'gitlab_proof');
|
|
|
|
if (!project) {
|
|
|
|
throw new Error('No project at ' + url);
|
|
|
|
}
|
|
|
|
reVerify = new RegExp(`[Verifying my OpenPGP key: openpgp4fpr:${fingerprint}]`, 'i');
|
|
|
|
if (reVerify.test(project.description)) {
|
|
|
|
output.isVerified = true;
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
} finally {
|
|
|
|
return output;
|
2020-08-18 12:02:16 -06:00
|
|
|
}
|
|
|
|
}
|
2020-10-01 05:35:04 -06:00
|
|
|
// Lobsters
|
|
|
|
if (/^https:\/\/lobste.rs/.test(url)) {
|
|
|
|
output.type = "lobsters";
|
|
|
|
match = url.match(/https:\/\/lobste.rs\/u\/(.*)/);
|
|
|
|
output.display = match[1];
|
|
|
|
output.proofUrl = `https://lobste.rs/u/${match[1]}.json`;
|
|
|
|
output.proofUrlFetch = `/server/verify/proxy
|
2020-08-08 13:40:22 -06:00
|
|
|
?url=${encodeURIComponent(output.proofUrl)}
|
|
|
|
&fingerprint=${fingerprint}
|
|
|
|
&checkRelation=contains
|
|
|
|
&checkPath=about
|
|
|
|
&checkClaimFormat=message`;
|
2020-10-01 05:35:04 -06:00
|
|
|
try {
|
|
|
|
response = await fetch(output.proofUrlFetch);
|
|
|
|
if (!response.ok) {
|
|
|
|
throw new Error('Response failed: ' + response.status);
|
|
|
|
}
|
|
|
|
json = await response.json();
|
|
|
|
output.isVerified = json.isVerified;
|
|
|
|
} catch (e) {
|
|
|
|
} finally {
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Catchall
|
|
|
|
// Fediverse
|
2020-06-30 04:42:47 -06:00
|
|
|
try {
|
2020-10-01 05:35:04 -06:00
|
|
|
response = await fetch(url, {
|
|
|
|
headers: {
|
|
|
|
Accept: 'application/json'
|
|
|
|
},
|
|
|
|
credentials: 'omit'
|
|
|
|
});
|
2020-06-30 04:42:47 -06:00
|
|
|
if (!response.ok) {
|
|
|
|
throw new Error('Response failed: ' + response.status);
|
|
|
|
}
|
|
|
|
json = await response.json();
|
2020-10-01 05:35:04 -06:00
|
|
|
if ('attachment' in json) {
|
|
|
|
match = url.match(/https:\/\/(.*)\/@(.*)/);
|
|
|
|
json.attachment.forEach((item, i) => {
|
|
|
|
reVerify = new RegExp(fingerprint, 'i');
|
|
|
|
if (reVerify.test(item.value)) {
|
|
|
|
output.type = "fediverse";
|
|
|
|
output.display = `@${json.preferredUsername}@${[match[1]]}`;
|
|
|
|
output.proofUrlFetch = json.url;
|
|
|
|
output.isVerified = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
if (!output.type && 'summary' in json) {
|
|
|
|
match = url.match(/https:\/\/(.*)\/users\/(.*)/);
|
|
|
|
reVerify = new RegExp(`[Verifying my OpenPGP key: openpgp4fpr:${fingerprint}]`, 'i');
|
|
|
|
if (reVerify.test(json.summary)) {
|
2020-07-07 00:14:57 -06:00
|
|
|
output.type = "fediverse";
|
|
|
|
output.display = `@${json.preferredUsername}@${[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;
|
|
|
|
}
|
2020-07-07 00:14:57 -06:00
|
|
|
}
|
2020-10-01 05:35:04 -06:00
|
|
|
if (output.type) {
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
console.warn(e);
|
2020-07-07 00:14:57 -06:00
|
|
|
}
|
2020-10-01 05:35:04 -06:00
|
|
|
// Discourse
|
|
|
|
try {
|
|
|
|
match = url.match(/https:\/\/(.*)\/u\/(.*)/);
|
|
|
|
output.proofUrl = `${url}.json`;
|
|
|
|
output.proofUrlFetch = `/server/verify/proxy
|
2020-08-08 13:40:22 -06:00
|
|
|
?url=${encodeURIComponent(output.proofUrl)}
|
|
|
|
&fingerprint=${fingerprint}
|
|
|
|
&checkRelation=contains
|
|
|
|
&checkPath=user,bio_raw
|
|
|
|
&checkClaimFormat=message`;
|
2020-10-01 05:35:04 -06:00
|
|
|
try {
|
|
|
|
response = await fetch(output.proofUrlFetch);
|
|
|
|
if (!response.ok) {
|
|
|
|
throw new Error('Response failed: ' + response.status);
|
|
|
|
}
|
|
|
|
json = await response.json();
|
|
|
|
if (json.isVerified) {
|
|
|
|
output.type = "discourse";
|
|
|
|
output.display = `${match[2]}@${match[1]}`;
|
|
|
|
output.isVerified = json.isVerified;
|
|
|
|
return output;
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
console.warn(e);
|
2020-07-06 03:44:36 -06:00
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
console.warn(e);
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
console.warn(e);
|
2020-06-26 07:08:22 -06:00
|
|
|
}
|
2020-12-11 04:06:21 -07:00
|
|
|
|
2020-07-07 00:14:57 -06:00
|
|
|
// Return output without confirmed proof
|
2020-07-06 03:44:36 -06:00
|
|
|
return output;
|
2020-06-26 07:08:22 -06:00
|
|
|
}
|
|
|
|
|
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-07-01 18:36:15 -06:00
|
|
|
// Autodetect mode
|
|
|
|
if (opts.mode == "auto") {
|
|
|
|
if (/.*@.*\..*/.test(opts.input)) {
|
|
|
|
opts.mode = "wkd";
|
|
|
|
} else {
|
|
|
|
opts.mode = "hkp";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
2020-07-05 07:04:12 -06:00
|
|
|
case "keybase":
|
|
|
|
opts.keyLink = `https://keybase.io/${opts.username}/pgp_keys.asc?fingerprint=${opts.fingerprint}`;
|
|
|
|
opts.input = `${opts.username}/${opts.fingerprint}`;
|
|
|
|
try {
|
|
|
|
opts.plaintext = await fetch(opts.keyLink).then(function(response) {
|
|
|
|
if (response.status === 200) {
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.then(response => response.text());
|
|
|
|
} catch (e) {
|
2020-07-05 09:25:11 -06:00
|
|
|
throw(`Error: No public keys could be fetched from the Keybase account (${e}).`);
|
2020-07-05 07:04:12 -06:00
|
|
|
}
|
|
|
|
output.publicKey = (await openpgp.key.readArmored(opts.plaintext)).keys[0];
|
|
|
|
|
|
|
|
if (!output.publicKey) {
|
|
|
|
throw("Error: No public keys could be read from the Keybase account.");
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2020-06-26 04:53:12 -06:00
|
|
|
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
|
|
|
async function computeWKDLocalPart(message) {
|
2020-07-23 02:53:13 -06:00
|
|
|
const data = openpgp.util.str_to_Uint8Array(message);
|
|
|
|
const hash = await openpgp.crypto.hash.sha1(data);
|
|
|
|
return openpgp.util.encodeZBase32(hash);
|
2020-06-29 12:58:34 -06:00
|
|
|
}
|
|
|
|
|
2020-07-05 05:51:23 -06:00
|
|
|
async function generateProfileURL(data) {
|
2020-08-14 07:16:41 -06:00
|
|
|
let hostname = window.location.hostname;
|
|
|
|
|
2020-07-05 05:51:23 -06:00
|
|
|
if (data.input == "") {
|
|
|
|
return "Waiting for input...";
|
|
|
|
}
|
|
|
|
switch (data.source) {
|
|
|
|
case "wkd":
|
2020-08-14 07:16:41 -06:00
|
|
|
return `https://${hostname}/${data.input}`;
|
2020-07-05 05:51:23 -06:00
|
|
|
break;
|
|
|
|
case "hkp":
|
|
|
|
if (/.*@.*\..*/.test(data.input)) {
|
2020-08-14 07:16:41 -06:00
|
|
|
return `https://${hostname}/hkp/${data.input}`;
|
2020-07-05 05:51:23 -06:00
|
|
|
} else {
|
2020-08-14 07:16:41 -06:00
|
|
|
return `https://${hostname}/${data.input}`;
|
2020-07-05 05:51:23 -06:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case "keybase":
|
|
|
|
const re = /https\:\/\/keybase.io\/(.*)\/pgp_keys\.asc\?fingerprint\=(.*)/;
|
|
|
|
if (!re.test(data.input)) {
|
|
|
|
return "Incorrect Keybase public key URL.";
|
|
|
|
}
|
|
|
|
const match = data.input.match(re);
|
2020-08-14 07:16:41 -06:00
|
|
|
return `https://${hostname}/keybase/${match[1]}/${match[2]}`;
|
2020-07-05 05:51:23 -06:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-14 17:03:45 -06:00
|
|
|
async function fetchWithTimeout(url, timeout = 3000) {
|
|
|
|
return Promise.race([
|
|
|
|
fetch(url),
|
|
|
|
new Promise((_, reject) =>
|
|
|
|
setTimeout(() => reject(new Error('timeout')), timeout)
|
|
|
|
)
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
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"),
|
2021-01-07 08:44:33 -07:00
|
|
|
elFormSignatureProfile = document.body.querySelector("#form-generate-signature-profile"),
|
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"),
|
2021-01-05 05:20:06 -07:00
|
|
|
elProfileServer = document.body.querySelector("#profileServer"),
|
2020-07-01 18:36:15 -06:00
|
|
|
elModeSelect = document.body.querySelector("#modeSelect"),
|
2020-07-02 14:39:56 -06:00
|
|
|
elUtilWKD = document.body.querySelector("#form-util-wkd"),
|
2020-07-22 06:58:11 -06:00
|
|
|
elUtilQRFP = document.body.querySelector("#form-util-qrfp"),
|
2020-07-05 05:51:23 -06:00
|
|
|
elUtilQR = document.body.querySelector("#form-util-qr"),
|
|
|
|
elUtilProfileURL = document.body.querySelector("#form-util-profile-url");
|
2020-06-25 10:01:06 -06:00
|
|
|
|
2020-07-01 18:36:15 -06:00
|
|
|
if (elModeSelect) {
|
|
|
|
elModeSelect.onchange = function (evt) {
|
|
|
|
let elAllModes = document.body.querySelectorAll('.modes');
|
|
|
|
elAllModes.forEach(function(el) {
|
|
|
|
el.classList.remove('modes--visible');
|
|
|
|
});
|
|
|
|
document.body.querySelector(`.modes--${elModeSelect.value}`).classList.add('modes--visible');
|
|
|
|
}
|
|
|
|
elModeSelect.dispatchEvent(new Event("change"));
|
|
|
|
}
|
|
|
|
|
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;
|
2020-07-01 18:36:15 -06:00
|
|
|
opts.mode = document.body.querySelector("#modeSelect").value;
|
|
|
|
|
|
|
|
switch (opts.mode) {
|
|
|
|
default:
|
|
|
|
case "auto":
|
|
|
|
opts.input = document.body.querySelector("#auto_input").value;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "wkd":
|
|
|
|
opts.input = document.body.querySelector("#wkd_input").value;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "hkp":
|
|
|
|
opts.input = document.body.querySelector("#hkp_input").value;
|
|
|
|
opts.server = document.body.querySelector("#hkp_server").value;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "plaintext":
|
|
|
|
opts.input = document.body.querySelector("#plaintext_input").value;
|
|
|
|
break;
|
2020-07-05 07:04:12 -06:00
|
|
|
|
|
|
|
case "keybase":
|
|
|
|
opts.username = document.body.querySelector("#keybase_username").value;
|
|
|
|
opts.fingerprint = document.body.querySelector("#keybase_fingerprint").value;
|
|
|
|
break;
|
2020-07-01 18:36:15 -06:00
|
|
|
}
|
2020-06-25 10:01:06 -06:00
|
|
|
|
2020-07-01 18:36:15 -06:00
|
|
|
// If no input was detect
|
2020-07-05 07:04:12 -06:00
|
|
|
if (!opts.input && !opts.username) {
|
2020-06-26 04:53:12 -06:00
|
|
|
opts.mode = "signature";
|
2020-06-25 10:01:06 -06:00
|
|
|
}
|
2020-07-01 18:36:15 -06:00
|
|
|
|
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;
|
2020-07-01 18:36:15 -06:00
|
|
|
opts.mode = document.body.querySelector("#modeSelect").value;
|
|
|
|
|
|
|
|
switch (opts.mode) {
|
|
|
|
default:
|
|
|
|
case "auto":
|
|
|
|
opts.input = document.body.querySelector("#auto_input").value;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "wkd":
|
|
|
|
opts.input = document.body.querySelector("#wkd_input").value;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "hkp":
|
|
|
|
opts.input = document.body.querySelector("#hkp_input").value;
|
|
|
|
opts.server = document.body.querySelector("#hkp_server").value;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "plaintext":
|
|
|
|
opts.input = document.body.querySelector("#plaintext_input").value;
|
|
|
|
break;
|
2020-07-05 07:04:12 -06:00
|
|
|
|
|
|
|
case "keybase":
|
|
|
|
opts.username = document.body.querySelector("#keybase_username").value;
|
|
|
|
opts.fingerprint = document.body.querySelector("#keybase_fingerprint").value;
|
|
|
|
break;
|
2020-06-25 10:01:06 -06:00
|
|
|
}
|
2020-07-01 18:36:15 -06:00
|
|
|
|
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,
|
|
|
|
};
|
|
|
|
|
2020-07-01 18:36:15 -06:00
|
|
|
opts.mode = document.body.querySelector("#modeSelect").value;
|
|
|
|
|
|
|
|
switch (opts.mode) {
|
|
|
|
default:
|
|
|
|
case "auto":
|
|
|
|
opts.input = document.body.querySelector("#auto_input").value;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "wkd":
|
|
|
|
opts.input = document.body.querySelector("#wkd_input").value;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "hkp":
|
|
|
|
opts.input = document.body.querySelector("#hkp_input").value;
|
|
|
|
opts.server = document.body.querySelector("#hkp_server").value;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case "plaintext":
|
|
|
|
opts.input = document.body.querySelector("#plaintext_input").value;
|
|
|
|
break;
|
2020-06-26 07:08:22 -06:00
|
|
|
}
|
2020-07-01 18:36:15 -06:00
|
|
|
|
2020-06-26 07:08:22 -06:00
|
|
|
verifyProofs(opts);
|
|
|
|
};
|
|
|
|
}
|
2020-06-26 09:52:01 -06:00
|
|
|
|
|
|
|
if (elProfileUid) {
|
2021-01-05 05:20:06 -07:00
|
|
|
let opts, profileUid = elProfileUid.innerHTML;
|
2020-06-28 16:09:31 -06:00
|
|
|
switch (elProfileMode.innerHTML) {
|
|
|
|
default:
|
2021-01-07 08:44:33 -07:00
|
|
|
case "sig":
|
|
|
|
elFormSignatureProfile.onsubmit = function (evt) {
|
|
|
|
evt.preventDefault();
|
|
|
|
|
|
|
|
opts = {
|
|
|
|
input: document.body.querySelector("#plaintext_input").value,
|
|
|
|
mode: elProfileMode.innerHTML
|
|
|
|
}
|
|
|
|
|
|
|
|
displayProfile(opts)
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2020-06-28 16:09:31 -06:00
|
|
|
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":
|
2021-01-05 05:20:06 -07:00
|
|
|
opts = {
|
|
|
|
input: profileUid,
|
|
|
|
server: elProfileServer.innerHTML,
|
|
|
|
mode: elProfileMode.innerHTML
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2020-06-28 16:10:13 -06:00
|
|
|
case "wkd":
|
2020-06-28 16:09:31 -06:00
|
|
|
opts = {
|
|
|
|
input: profileUid,
|
|
|
|
mode: elProfileMode.innerHTML
|
|
|
|
}
|
2020-07-05 07:04:12 -06:00
|
|
|
break;
|
|
|
|
|
|
|
|
case "keybase":
|
|
|
|
let match = profileUid.match(/(.*)\/(.*)/);
|
|
|
|
opts = {
|
|
|
|
username: match[1],
|
|
|
|
fingerprint: match[2],
|
|
|
|
mode: elProfileMode.innerHTML
|
|
|
|
}
|
2020-06-28 16:09:31 -06:00
|
|
|
break;
|
2020-06-26 09:52:01 -06:00
|
|
|
}
|
2021-01-07 08:44:33 -07:00
|
|
|
|
|
|
|
if (elProfileMode.innerHTML !== 'sig') {
|
|
|
|
displayProfile(opts);
|
|
|
|
}
|
2020-06-26 09:52:01 -06:00
|
|
|
}
|
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");
|
2020-07-23 03:07:49 -06:00
|
|
|
const elOutputDirect = document.body.querySelector("#output_url_direct");
|
|
|
|
const elOutputAdvanced = document.body.querySelector("#output_url_advanced");
|
|
|
|
let match;
|
2020-06-29 12:58:34 -06:00
|
|
|
|
|
|
|
elInput.addEventListener("input", async function(evt) {
|
|
|
|
if (evt.target.value) {
|
2020-07-23 03:07:49 -06:00
|
|
|
if (/(.*)@(.{1,}\..{1,})/.test(evt.target.value)) {
|
|
|
|
match = evt.target.value.match(/(.*)@(.*)/);
|
|
|
|
elOutput.innerText = await computeWKDLocalPart(match[1]);
|
|
|
|
elOutputDirect.innerText = `https://${match[2]}/.well-known/openpgpkey/hu/${elOutput.innerText}?l=${match[1]}`;
|
|
|
|
elOutputAdvanced.innerText = `https://openpgpkey.${match[2]}/.well-known/openpgpkey/${match[2]}/hu/${elOutput.innerText}?l=${match[1]}`;
|
|
|
|
} else {
|
|
|
|
elOutput.innerText = await computeWKDLocalPart(evt.target.value);
|
|
|
|
elOutputDirect.innerText = "Waiting for input";
|
|
|
|
elOutputAdvanced.innerText = "Waiting for input";
|
|
|
|
}
|
2020-06-29 12:58:34 -06:00
|
|
|
} else {
|
2020-07-23 03:07:49 -06:00
|
|
|
elOutput.innerText = "Waiting for input";
|
|
|
|
elOutputDirect.innerText = "Waiting for input";
|
|
|
|
elOutputAdvanced.innerText = "Waiting for input";
|
2020-06-29 12:58:34 -06:00
|
|
|
}
|
|
|
|
});
|
2020-07-23 03:07:49 -06:00
|
|
|
|
|
|
|
elInput.dispatchEvent(new Event("input"));
|
2020-06-29 12:58:34 -06:00
|
|
|
}
|
2020-07-02 14:39:56 -06:00
|
|
|
|
2020-07-22 06:58:11 -06:00
|
|
|
if (elUtilQRFP) {
|
|
|
|
elUtilQRFP.onsubmit = function (evt) {
|
|
|
|
evt.preventDefault();
|
|
|
|
}
|
|
|
|
|
2020-08-30 04:21:45 -06:00
|
|
|
const qrTarget = document.getElementById('qrcode');
|
|
|
|
const qrContext = qrTarget.getContext('2d');
|
|
|
|
const qrOpts = {
|
|
|
|
errorCorrectionLevel: 'H',
|
|
|
|
margin: 1,
|
2020-07-22 06:58:11 -06:00
|
|
|
width: 256,
|
2020-08-30 04:21:45 -06:00
|
|
|
height: 256
|
|
|
|
};
|
2020-07-22 06:58:11 -06:00
|
|
|
|
|
|
|
const elInput = document.body.querySelector("#input");
|
|
|
|
|
|
|
|
elInput.addEventListener("input", async function(evt) {
|
|
|
|
if (evt.target.value) {
|
2020-08-30 04:21:45 -06:00
|
|
|
QRCode.toCanvas(qrTarget, evt.target.value, qrOpts, function (error) {
|
|
|
|
if (error) {
|
|
|
|
qrContext.clearRect(0, 0, qrTarget.width, qrTarget.height);
|
|
|
|
console.error(error);
|
|
|
|
}
|
|
|
|
});
|
2020-07-22 06:58:11 -06:00
|
|
|
} else {
|
2020-08-30 04:21:45 -06:00
|
|
|
qrContext.clearRect(0, 0, qrTarget.width, qrTarget.height);
|
2020-07-22 06:58:11 -06:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
elInput.dispatchEvent(new Event("input"));
|
|
|
|
}
|
|
|
|
|
2020-07-02 14:39:56 -06:00
|
|
|
if (elUtilQR) {
|
|
|
|
elUtilQR.onsubmit = function (evt) {
|
|
|
|
evt.preventDefault();
|
|
|
|
}
|
|
|
|
|
2020-08-30 04:21:45 -06:00
|
|
|
const qrTarget = document.getElementById('qrcode');
|
|
|
|
const qrContext = qrTarget.getContext('2d');
|
|
|
|
const qrOpts = {
|
|
|
|
errorCorrectionLevel: 'L',
|
|
|
|
margin: 1,
|
2020-07-02 14:39:56 -06:00
|
|
|
width: 256,
|
2020-08-30 04:21:45 -06:00
|
|
|
height: 256
|
|
|
|
};
|
2020-07-02 14:39:56 -06:00
|
|
|
|
|
|
|
const elInput = document.body.querySelector("#input");
|
|
|
|
|
2020-07-23 02:36:42 -06:00
|
|
|
if (elInput.innerText) {
|
|
|
|
elInput.innerText = decodeURIComponent(elInput.innerText);
|
2020-08-30 04:21:45 -06:00
|
|
|
|
|
|
|
QRCode.toCanvas(qrTarget, elInput.innerText, qrOpts, function (error) {
|
|
|
|
if (error) {
|
|
|
|
document.body.querySelector("#qrcode--altLink").href = "#";
|
|
|
|
qrContext.clearRect(0, 0, qrTarget.width, qrTarget.height);
|
|
|
|
console.error(error);
|
|
|
|
} else {
|
|
|
|
document.body.querySelector("#qrcode--altLink").href = elInput.innerText;
|
|
|
|
}
|
|
|
|
});
|
2020-07-22 07:37:38 -06:00
|
|
|
} else {
|
2020-08-30 04:21:45 -06:00
|
|
|
qrContext.clearRect(0, 0, qrTarget.width, qrTarget.height);
|
2020-07-22 07:37:38 -06:00
|
|
|
}
|
2020-07-02 14:39:56 -06:00
|
|
|
}
|
2020-07-05 05:51:23 -06:00
|
|
|
|
|
|
|
if (elUtilProfileURL) {
|
|
|
|
elUtilProfileURL.onsubmit = function (evt) {
|
|
|
|
evt.preventDefault();
|
|
|
|
}
|
|
|
|
|
|
|
|
const elInput = document.body.querySelector("#input"),
|
|
|
|
elSource = document.body.querySelector("#source"),
|
|
|
|
elOutput = document.body.querySelector("#output");
|
|
|
|
|
|
|
|
let data = {
|
|
|
|
input: elInput.value,
|
|
|
|
source: elSource.value
|
|
|
|
};
|
|
|
|
|
|
|
|
elInput.addEventListener("input", async function(evt) {
|
|
|
|
data = {
|
|
|
|
input: elInput.value,
|
|
|
|
source: elSource.value
|
|
|
|
};
|
|
|
|
elOutput.innerText = await generateProfileURL(data);
|
|
|
|
});
|
|
|
|
|
|
|
|
elSource.addEventListener("input", async function(evt) {
|
|
|
|
data = {
|
|
|
|
input: elInput.value,
|
|
|
|
source: elSource.value
|
|
|
|
};
|
|
|
|
elOutput.innerText = await generateProfileURL(data);
|
|
|
|
});
|
|
|
|
|
|
|
|
elInput.dispatchEvent(new Event("input"));
|
|
|
|
}
|
2020-12-25 19:38:41 -07:00
|
|
|
|
|
|
|
function capitalizeLetteredServices(serviceName) {
|
|
|
|
var servName = serviceName.toLowerCase();
|
|
|
|
if (servName === 'dns' || servName === 'xmpp') {
|
|
|
|
return servName.toUpperCase();
|
|
|
|
}
|
|
|
|
return serviceName;
|
2021-01-07 13:30:21 -07:00
|
|
|
}
|