Add proof verification page

This commit is contained in:
Yarmo Mackenbach 2020-06-26 15:08:22 +02:00
parent 7155e603f1
commit 8540fe9d9d
6 changed files with 267 additions and 0 deletions

View file

@ -16,6 +16,7 @@
<nav>
<a href="/verify">verify</a>
<a href="/encrypt">encrypt</a>
<a href="/proofs">proofs</a>
</nav>
</div>
</header>

View file

@ -16,6 +16,7 @@
<nav>
<a href="/verify">verify</a>
<a href="/encrypt">encrypt</a>
<a href="/proofs">proofs</a>
</nav>
</div>
</header>

View file

@ -16,6 +16,7 @@
<nav>
<a href="/verify">verify</a>
<a href="/encrypt">encrypt</a>
<a href="/proofs">proofs</a>
</nav>
</div>
</header>
@ -27,6 +28,7 @@
<p>
<a class="bigBtn" href="/verify">verify signature</a>
<a class="bigBtn" href="/encrypt">encrypt message</a>
<a class="bigBtn" href="/proofs">verify proofs</a>
</p>
<h2>About</h2>
<p><a href="/">Keyoxide</a> is a lightweight and FOSS solution to make basic cryptography operations accessible to regular humans.</p>

63
proofs.html Normal file
View file

@ -0,0 +1,63 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Profile - Keyoxide</title>
<script async defer data-domain="keyoxide.org" src="https://plausible.io/js/plausible.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/normalize.css@8.0.1/normalize.css">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<header>
<div class="container">
<a href="/">Keyoxide</a>
<div class="spacer"></div>
<nav>
<a href="/verify">verify</a>
<a href="/encrypt">encrypt</a>
<a href="/proofs">proofs</a>
</nav>
</div>
</header>
<div class="container">
<h1>Proofs</h1>
<div class="content">
<form id="form-proofs" method="post">
<h3>Public Key (1: plaintext)</h3>
<textarea name="publicKey" id="publicKey"></textarea>
<h3>Public Key (2: web key directory)</h3>
<input type="text" name="wkd" id="wkd" placeholder="name@domain.com">
<h3>Public Key (3: HKP server)</h3>
<input type="text" name="hkp_server" id="hkp_server" placeholder="https://keys.openpgp.org/">
<input type="text" name="hkp_input" id="hkp_input" placeholder="Email / key id / fingerprint">
<h3>Result</h3>
<p id="result">Click on the button below.</p>
<p id="resultContent"></p>
<input type="submit" class="bigBtn" name="submit" value="VERIFY PROOFS" value="">
</form>
</div>
<footer>
<p>
<a href="/">Keyoxide</a> makes basic cryptography operations accessible to regular humans.
<br>
Made by <a href="https://yarmo.eu">Yarmo Mackenbach</a>.
<br>
Code hosted on <a href="https://codeberg.org/yarmo/keyoxide">Codeberg</a> (<a href="https://drone.private.foss.best/yarmo/keyoxide/">drone CI/CD</a>).
<br>
Uses <a href="https://github.com/openpgpjs/openpgpjs">openpgp.js</a> (version <a href="https://github.com/openpgpjs/openpgpjs/releases/tag/v4.10.4">4.10.4</a>).
<br>
Privacy-friendly public usage stats by <a href="https://plausible.io/keyoxide.org">Plausible.io</a>.
<br>
Because SO2 + 2NaOH → Na2SO3 + H2O
</p>
</footer>
</div>
</body>
<script src="openpgp.min.js"></script>
<script type="text/javascript" src="scripts.js" charset="utf-8"></script>
</html>

View file

@ -132,6 +132,177 @@ async function encryptMessage(opts) {
elEnc.value = encrypted.data;
};
async function verifyProofs(opts) {
// Init
const elRes = document.body.querySelector("#result");
let keyData, feedback = "", message, encrypted;
// Reset feedback
elRes.innerHTML = "";
try {
// Get key data
keyData = await fetchKeys(opts);
} catch (e) {
console.error(e);
elRes.innerHTML = e;
elRes.classList.remove('green');
elRes.classList.add('red');
return;
}
let notation, isVerified, verifications = [];
for (var i = 0; i < keyData.notations.length; i++) {
notation = keyData.notations[i];
if (!(notation[0] == "proof@keyoxide.org" || notation[0] == "proof@metacode.biz")) { continue; }
verifications.push(await verifyProof(notation[1], keyData.fingerprint));
}
// Generate feedback
for (var i = 0; i < verifications.length; i++) {
feedback += `${verifications[i].type}: <a href="${verifications[i].url}">${verifications[i].display}</a>: ${verifications[i].isVerified}<br>`;
}
// Display feedback
elRes.innerHTML = feedback;
};
async function verifyProof(url, fingerprint) {
// Init
let reVerify, urlFetch, output = {url: url, type: null, isVerified: false, display: null};
// DNS
if (/^dns:/.test(url)) {
output.type = "dns";
let domain = url.replace(/dns:/, '').replace(/\?type=TXT/, '');
urlFetch = `https://dns.google.com/resolve?name=${domain}&type=TXT`;
output.display = domain;
try {
response = await fetch(urlFetch, {
headers: {
Accept: 'application/json'
},
credentials: 'omit'
});
if (!response.ok) {
throw new Error('Response failed: ' + response.status);
}
json = await response.json();
reVerify = new RegExp(`openpgp4fpr:${fingerprint}`);
json.Answer.forEach((item, i) => {
if (reVerify.test(item.data)) {
output.isVerified = true;
}
});
} catch (e) {
} finally {
return output;
}
}
// HN
if (/^https:\/\/news.ycombinator.com/.test(url)) {
output.type = "hn";
try {
response = await fetch(urlFetch, {
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;
}
}
// Reddit
if (/^https:\/\/www.reddit.com\/user/.test(url)) {
output.type = "reddit";
try {
response = await fetch(urlFetch, {
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;
}
}
// Github
if (/^https:\/\/gist.github.com/.test(url)) {
output.type = "github";
try {
response = await fetch(urlFetch, {
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;
}
}
// Catchall
try {
response = await fetch(url, {
headers: {
Accept: 'application/json'
},
credentials: 'omit'
});
if (!response.ok) {
throw new Error('Response failed: ' + response.status);
}
json = await response.json();
if ('attachment' in json) {
// Potentially Mastodon
json.attachment.forEach((item, i) => {
if (item.value === fingerprint) {
output.type = "mastodon";
output.display = json.url;
output.isVerified = true;
}
});
}
} catch (e) {
} finally {
return output;
}
}
async function fetchKeys(opts) {
// Init
let lookupOpts, wkd, hkd, sig, lastPrimarySig;
@ -215,6 +386,7 @@ async function fetchKeys(opts) {
// General purpose
let elFormVerify = document.body.querySelector("#form-verify"),
elFormEncrypt = document.body.querySelector("#form-encrypt");
elFormProofs = document.body.querySelector("#form-proofs");
if (elFormVerify) {
elFormVerify.onsubmit = function (evt) {
@ -275,3 +447,30 @@ if (elFormEncrypt) {
encryptMessage(opts);
};
}
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);
};
}

View file

@ -16,6 +16,7 @@
<nav>
<a href="/verify">verify</a>
<a href="/encrypt">encrypt</a>
<a href="/proofs">proofs</a>
</nav>
</div>
</header>