Revert social-proof commits

This reverts commit 149a698a3f.
This reverts commit 29018f024d.
This reverts commit 42372f27cb.
This reverts commit a424642256.
This commit is contained in:
Vincent Breitmoser 2020-11-05 12:51:00 +01:00
parent 606acdde39
commit 10162c6f88
19 changed files with 0 additions and 899 deletions

View File

@ -9,14 +9,6 @@ file_filter = po/hagrid/<lang>.po
trans.zh-Hans = po/hagrid/zh_Hans.po
type = PO
[hagrid.about-proofs]
minimum_perc = 100
source_file = templates-untranslated/about/proofs.html.hbs
file_filter = templates-translated/<lang>/about/proofs.html.hbs
trans.zh-Hans = templates-translated/zh_Hans/about/proofs.html.hbs
source_lang = en
type = HTML
[hagrid.about-about]
minimum_perc = 100
source_file = templates-untranslated/about/about.html.hbs

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M458.622 255.92l45.985-45.005c13.708-12.977 7.316-36.039-10.664-40.339l-62.65-15.99 17.661-62.015c4.991-17.838-11.829-34.663-29.661-29.671l-61.994 17.667-15.984-62.671C337.085.197 313.765-6.276 300.99 7.228L256 53.57 211.011 7.229c-12.63-13.351-36.047-7.234-40.325 10.668l-15.984 62.671-61.995-17.667C74.87 57.907 58.056 74.738 63.046 92.572l17.661 62.015-62.65 15.99C.069 174.878-6.31 197.944 7.392 210.915l45.985 45.005-45.985 45.004c-13.708 12.977-7.316 36.039 10.664 40.339l62.65 15.99-17.661 62.015c-4.991 17.838 11.829 34.663 29.661 29.671l61.994-17.667 15.984 62.671c4.439 18.575 27.696 24.018 40.325 10.668L256 458.61l44.989 46.001c12.5 13.488 35.987 7.486 40.325-10.668l15.984-62.671 61.994 17.667c17.836 4.994 34.651-11.837 29.661-29.671l-17.661-62.015 62.65-15.99c17.987-4.302 24.366-27.367 10.664-40.339l-45.984-45.004z" style="fill: #73a6ff"/></svg>

Before

Width:  |  Height:  |  Size: 933 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M502.3 190.8c3.9-3.1 9.7-.2 9.7 4.7V400c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V195.6c0-5 5.7-7.8 9.7-4.7 22.4 17.4 52.1 39.5 154.1 113.6 21.1 15.4 56.7 47.8 92.2 47.6 35.7.3 72-32.8 92.3-47.6 102-74.1 131.6-96.3 154-113.7zM256 320c23.2.4 56.6-29.2 73.4-41.4 132.7-96.3 142.8-104.7 173.4-128.7 5.8-4.5 9.2-11.5 9.2-18.9v-19c0-26.5-21.5-48-48-48H48C21.5 64 0 85.5 0 112v19c0 7.4 3.4 14.3 9.2 18.9 30.6 23.9 40.7 32.4 173.4 128.7 16.8 12.2 50.2 41.8 73.4 41.4z"/></svg>

Before

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"/></svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 496 512"><path d="M336.5 160C322 70.7 287.8 8 248 8s-74 62.7-88.5 152h177zM152 256c0 22.2 1.2 43.5 3.3 64h185.3c2.1-20.5 3.3-41.8 3.3-64s-1.2-43.5-3.3-64H155.3c-2.1 20.5-3.3 41.8-3.3 64zm324.7-96c-28.6-67.9-86.5-120.4-158-141.6 24.4 33.8 41.2 84.7 50 141.6h108zM177.2 18.4C105.8 39.6 47.8 92.1 19.3 160h108c8.7-56.9 25.5-107.8 49.9-141.6zM487.4 192H372.7c2.1 21 3.3 42.5 3.3 64s-1.2 43-3.3 64h114.6c5.5-20.5 8.6-41.8 8.6-64s-3.1-43.5-8.5-64zM120 256c0-21.5 1.2-43 3.3-64H8.6C3.2 212.5 0 233.8 0 256s3.2 43.5 8.6 64h114.6c-2-21-3.2-42.5-3.2-64zm39.5 96c14.5 89.3 48.7 152 88.5 152s74-62.7 88.5-152h-177zm159.3 141.6c71.4-21.2 129.4-73.7 158-141.6h-108c-8.8 56.9-25.6 107.8-50 141.6zM19.3 352c28.6 67.9 86.5 120.4 158 141.6-24.4-33.8-41.2-84.7-50-141.6h-108z"/></svg>

Before

Width:  |  Height:  |  Size: 818 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M0 32v448h448V32H0zm21.2 197.2H21c.1-.1.2-.3.3-.4 0 .1 0 .3-.1.4zm218 53.9V384h-31.4V281.3L128 128h37.3c52.5 98.3 49.2 101.2 59.3 125.6 12.3-27 5.8-24.4 60.6-125.6H320l-80.8 155.1z"/></svg>

Before

Width:  |  Height:  |  Size: 260 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M512 176.001C512 273.203 433.202 352 336 352c-11.22 0-22.19-1.062-32.827-3.069l-24.012 27.014A23.999 23.999 0 0 1 261.223 384H224v40c0 13.255-10.745 24-24 24h-40v40c0 13.255-10.745 24-24 24H24c-13.255 0-24-10.745-24-24v-78.059c0-6.365 2.529-12.47 7.029-16.971l161.802-161.802C163.108 213.814 160 195.271 160 176 160 78.798 238.797.001 335.999 0 433.488-.001 512 78.511 512 176.001zM336 128c0 26.51 21.49 48 48 48s48-21.49 48-48-21.49-48-48-48-48 21.49-48 48z"/></svg>

Before

Width:  |  Height:  |  Size: 538 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M433 179.11c0-97.2-63.71-125.7-63.71-125.7-62.52-28.7-228.56-28.4-290.48 0 0 0-63.72 28.5-63.72 125.7 0 115.7-6.6 259.4 105.63 289.1 40.51 10.7 75.32 13 103.33 11.4 50.81-2.8 79.32-18.1 79.32-18.1l-1.7-36.9s-36.31 11.4-77.12 10.1c-40.41-1.4-83-4.4-89.63-54a102.54 102.54 0 0 1-.9-13.9c85.63 20.9 158.65 9.1 178.75 6.7 56.12-6.7 105-41.3 111.23-72.9 9.8-49.8 9-121.5 9-121.5zm-75.12 125.2h-46.63v-114.2c0-49.7-64-51.6-64 6.9v62.5h-46.33V197c0-58.5-64-56.6-64-6.9v114.2H90.19c0-122.1-5.2-147.9 18.41-175 25.9-28.9 79.82-30.8 103.83 6.1l11.6 19.5 11.6-19.5c24.11-37.1 78.12-34.8 103.83-6.1 23.71 27.3 18.4 53 18.4 175z"/></svg>

Before

Width:  |  Height:  |  Size: 695 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M201.5 305.5c-13.8 0-24.9-11.1-24.9-24.6 0-13.8 11.1-24.9 24.9-24.9 13.6 0 24.6 11.1 24.6 24.9 0 13.6-11.1 24.6-24.6 24.6zM504 256c0 137-111 248-248 248S8 393 8 256 119 8 256 8s248 111 248 248zm-132.3-41.2c-9.4 0-17.7 3.9-23.8 10-22.4-15.5-52.6-25.5-86.1-26.6l17.4-78.3 55.4 12.5c0 13.6 11.1 24.6 24.6 24.6 13.8 0 24.9-11.3 24.9-24.9s-11.1-24.9-24.9-24.9c-9.7 0-18 5.8-22.1 13.8l-61.2-13.6c-3-.8-6.1 1.4-6.9 4.4l-19.1 86.4c-33.2 1.4-63.1 11.3-85.5 26.8-6.1-6.4-14.7-10.2-24.1-10.2-34.9 0-46.3 46.9-14.4 62.8-1.1 5-1.7 10.2-1.7 15.5 0 52.6 59.2 95.2 132 95.2 73.1 0 132.3-42.6 132.3-95.2 0-5.3-.6-10.8-1.9-15.8 31.3-16 19.8-62.5-14.9-62.5zM302.8 331c-18.2 18.2-76.1 17.9-93.6 0-2.2-2.2-6.1-2.2-8.3 0-2.5 2.5-2.5 6.4 0 8.6 22.8 22.8 87.3 22.8 110.2 0 2.5-2.2 2.5-6.1 0-8.6-2.2-2.2-6.1-2.2-8.3 0zm7.7-75c-13.6 0-24.6 11.1-24.6 24.9 0 13.6 11.1 24.6 24.6 24.6 13.8 0 24.9-11.1 24.9-24.6 0-13.8-11-24.9-24.9-24.9z"/></svg>

Before

Width:  |  Height:  |  Size: 988 B

View File

@ -1,295 +0,0 @@
(async function() {
function getVerifier(proofs, proofUrl, fingerprint) {
for (const proof of proofs) {
// check if the proof URI matches one of our known definitions
const matches = proofUrl.match(new RegExp(proof.matcher));
if (!matches) continue;
// get all variables that were matched from the URI
const bound = Object.entries(proof.variables)
.map(([key, value]) => [key, matches[value || 0]])
.reduce((previous, current) => {
previous[current[0]] = current[1];
return previous;
}, { FINGERPRINT: fingerprint });
// replacer function that will substitute variables in text
const replace = text => text.replace(/\{([A-Z]+)\}/g, (_, name) => bound[name]);
// return description of what we matched including extracted data
return {
profile: [proof.profile, replace(proof.profile)],
proofUrl,
proofJson: [proof.proof, replace(proof.proof)],
username: [proof.username, replace(proof.username)],
service: proof.service,
checks: (proof.checks || []).map(check => ({
relation: check.relation,
proof: check.proof,
claim: check.claim.replace(/\{([A-Z]+)\}/g, (_, name) => bound[name])
})),
matcher: proof.matcher,
variables: Object.entries(proof.variables).map(entry => (entry[2] = matches[entry[1]], entry))
};
}
// no match
return null;
}
const email = location.href.split('/').pop();
const keyUrl = "/vks/v1/by-email/" + email;
const response = await fetch(keyUrl);
const armor = await response.text();
const key = (await openpgp.key.readArmored(armor)).keys[0];
// add fingerprint header
const fingerprint = key.primaryKey.getFingerprint();
const fprLink = document.querySelector('.fpr');
fprLink.href = keyUrl;
fprLink.firstElementChild.textContent = fingerprint;
// verify primary user to use in the profile name
const primaryUser = await key.getPrimaryUser();
if (!await primaryUser.user.verify(key.primaryKey)) {
throw new Error('Primary user is not valid.');
}
document.title = primaryUser.user.userId.name + ' — ' + document.title;
const name = primaryUser.user.userId.name;
document.querySelector('.name').textContent = name;
// grab avatar from MD5 of primary user's e-mail address
const util = openpgp.util;
const digest = await openpgp.crypto.hash.md5(util.str_to_Uint8Array(email));
const profileHash = util.str_to_hex(util.Uint8Array_to_str(digest));
document.querySelector('.avatar').src = `https://www.gravatar.com/avatar/${profileHash}?s=148&d=mm`;
// we support reading proof URIs from these notations
const supportedNotations = ['proof@metacode.biz', 'proof@keys.openpgp.org'];
const proofDefinitions = (await (await fetch('/assets/proofs.json')).json()).proofs;
// proof URLs are gathered from all UIDs and sorted by insertion order
const proofUrls = new Set();
const emails = new Set([email]);
for (const user of key.users) {
// if verification fails for the User ID still continue with others
try {
const valid = await user.verify(key.primaryKey);
// validate the User ID to avoid issues like:
// https://bitbucket.org/skskeyserver/sks-keyserver/issues/41
if (valid) {
// get latest self-certification
const cert = user.selfCertifications.reduce((a, b) => a.created > b.created ? a : b);
(cert.notations || [])
// filter out non-supported notations
.filter(notation => supportedNotations.includes(notation[0]) && typeof notation[1] === 'string')
// select only values (proof URIs)
.map(notation => notation[1])
.forEach(proofUrls.add.bind(proofUrls));
// add proof links from User Attributes too
// this won't work on Hagrid as Hagrid never exposes User Attributes currently (2020-07-09)
// but would work on a custom page
if (user.userAttribute && user.userAttribute.attributes[0][0] === 'e') {
proofUrls.add(user.userAttribute.attributes[0].substring(user.userAttribute.attributes[0].indexOf('@') + 1));
}
if (user.userId && user.userId.email) {
emails.add(user.userId.email);
}
}
} catch (e) {
console.error('User verification error:', e);
}
}
// add e-mails to the UI
function addEmail(email) {
const li = document.createElement('li');
const keyLink = document.createElement('a');
keyLink.href = 'mailto:' + email;
keyLink.textContent = email;
keyLink.className = 'email';
li.appendChild(keyLink);
return li;
}
for (const email of emails) {
document.querySelector('.info').appendChild(addEmail(email));
}
// get text to be rendered, this could be improved by using <template>s
function translate(id) {
return document.getElementsByClassName('text-' + id)[0].textContent;
}
// add proof links for all proof URIs
[...proofUrls]
.sort()
// verifier parses the proof URI
.map(proofUrl => getVerifier(proofDefinitions, proofUrl, fingerprint))
// filter out unknown proof URIs
.filter(Boolean)
.map(proof => {
const li = document.createElement('li');
const profile = document.createElement('a');
profile.rel = 'me noopener nofollow';
profile.target = '_blank';
profile.href = proof.profile[1];
profile.textContent = proof.username[1];
profile.className = 'service service-' + proof.service.toLowerCase();
li.appendChild(profile);
const proofDiv = document.createElement('div');
proofDiv.className = 'prooflog';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.id = 'chx-' + Math.random();
proofDiv.appendChild(checkbox);
const verification = document.createElement('label');
verification.className = 'proof';
verification.rel = 'me noopener nofollow';
verification.target = '_blank';
verification.href = proof.proofUrl;
verification.textContent = translate('proof');
verification.htmlFor = checkbox.id;
proofDiv.appendChild(verification);
const verificationLog = document.getElementById('verification-log').content.cloneNode(true);
proofDiv.appendChild(verificationLog)
li.appendChild(proofDiv);
return li;
}).forEach(document.querySelector('.proofs').appendChild.bind(document.querySelector('.proofs')));
const randomRange = (from, to) => Math.ceil(Math.random() * (to - from) + from);
const delay = () => new Promise(resolve => setTimeout(resolve, randomRange(800, 1100)));
async function checkProofs() {
const proofs = document.querySelectorAll('.proof');
proofs.forEach(async function(proofLink, key) {
// validation inserts random delay so it's visible that the verification
// happens in the browser
await delay();
console.log(key + '. Verification of: ' + proofLink.href);
const verifier = getVerifier(proofDefinitions, proofLink.href, fingerprint);
console.log(key + '. Verifier details:', verifier);
const verificationLog = proofLink.parentNode.querySelector('.balloon');
const checks = verifier.checks;
let json;
let error = null;
let success = true;
let step = 1;
// fetching proof document using CORS mode and no referrer header
// note that this still leaks domain name through `Origin` header
// Accept:application/json header is required to get JSON representation
// in some cases (like Mastodon) it is critical
try {
json = await (await fetch(verifier.proofJson[1], {
headers: {
Accept: 'application/json'
},
mode: 'cors',
referrerPolicy: 'no-referrer'
})).json();
} catch (e) {
console.log(key + '. Could not download JSON proof: ', e);
error = e;
success = false;
}
let claimValue = checks[checks.length - 1].claim;
if (!error) {
step = 2;
try {
// do proof related checks on the JSON document
for (const check of checks) {
// extract value from the proof JSON document
const proofValue = check.proof.reduce((previous, current) => {
if (current == null || previous == null) return null;
if (Array.isArray(previous) && typeof current === 'string') {
return previous.map(value => value[current]);
}
return previous[current];
}, json);
// get the value that we need to see in the JSON document
claimValue = check.claim;
if (check.relation === 'eq') {
// we need strict equality for this value
// (useful for simple fields like OpenPGP field in Mastodon)
if (proofValue !== claimValue) {
console.log(`${key}. Check failed: claimed value ${claimValue} is not equal to ${proofValue}`);
success = false;
}
} else if (check.relation === 'contains') {
// the text need to include the claimed value
// (useful to large fields like Description or Bio on HackerNews)
if (!proofValue || proofValue.indexOf(claimValue) === -1) {
console.log(`${key}. Check failed: claimed value ${claimValue} is not present in ${proofValue}`);
success = false;
}
} else if (check.relation === 'oneOf') {
// claimed value must be one of the values in the resulting array
// useful when we have multiple fields (like DNS TXT records)
if (!proofValue || proofValue.indexOf(claimValue) === -1) {
console.log(`${key}. Check failed: claimed value ${claimValue} is not included in ${proofValue}`);
success = false;
}
}
if (!success){
throw new Error('Verification failed.');
}
}
proofLink.textContent = translate('verified');
proofLink.classList.add('verified');
} catch (e) {
console.error(key + '. Could not verify proof: ', e);
error = e;
success = false;
}
}
console.log(key + '. ' + (success ? 'Success' : 'Failure'));
// renders verification log based on verification results
const model = {
proofUrl: verifier.proofUrl,
verificationText: claimValue,
username: verifier.username[1],
service: verifier.service,
error: error ? error.message : null,
step,
success
};
bindModel(verificationLog, model);
});
}
checkProofs();
function bindModel(root, model) {
for (const elem of root.querySelectorAll('[data-bind]')) {
for (const [modelKey, elemKey] of elem.dataset.bind.split(' ').map(pair => pair.split(':'))) {
elem[elemKey] = model[modelKey];
}
}
}
document.addEventListener('click', e => {
let element = e.target;
// close the ballon if someone clicked "close" button or outside of the ballon
while (element) {
if (element.classList.contains('close')) {
break;
}
if (element.classList.contains('balloon') || 'checked' in element) {
return;
}
element = element.parentElement;
}
Array.from(document.querySelectorAll('input[type="checkbox"]'), checkbox => checkbox.checked = false);
});
}());

File diff suppressed because one or more lines are too long

251
dist/assets/proofs.css vendored
View File

@ -1,251 +0,0 @@
.second ul {
list-style-type: none;
padding: 0;
}
.second ul .key {
font-family: monospace;
}
.ui {
margin-bottom: 50px;
margin-top: 50px;
font-size: 120%;
}
.ui .first {
flex: 3;
}
.ui .second {
flex: 5;
}
.top_link {
position: absolute;
top: 10px;
margin: 10px;
font-size: 12px;
}
.top_link a {
color: #bbb;
}
.back_link {
left: 10px;
}
.proofs_link {
right: 10px;
}
.proofs .proof {
font-size: small;
font-weight: bolder;
margin-left: 10px;
color: #73a6ff
}
.proofs .verified {
color: green;
}
.proofs .proof::before {
background-image: url(/assets/img/certificate.svg);
width: 14px;
height: 14px;
display: inline-block;
margin-right: 3px;
content: "";
background-repeat: no-repeat;
vertical-align: middle;
}
.proofs .verified::before {
content: "✅";
background-image: none;
vertical-align: initial;
}
.avatar {
width: 148px;
height: 148px;
border-radius: 74px;
display: block;
margin-left: auto;
margin-right: auto;
}
.second ul li {
padding: 4px;
}
.proofs .service {
vertical-align: top;
}
.proofs .service::before, .key::before, .email::before {
width: 16px;
height: 16px;
display: inline-block;
margin: -2px;
margin-right: .5em;
content: "";
background-repeat: no-repeat;
}
.proofs .service-github::before {
background-image: url(/assets/img/github.svg);
}
.proofs .service-reddit::before {
background-image: url(/assets/img/reddit.svg);
}
.proofs .service-hackernews::before {
background-image: url(/assets/img/hackernews.svg);
}
.proofs .service-mastodon::before {
background-image: url(/assets/img/mastodon.svg);
}
.proofs .service-dns::before, .proofs .service-https::before {
background-image: url(/assets/img/globe.svg);
}
.key::before {
background-image: url(/assets/img/key.svg);
}
.email::before {
background-image: url(/assets/img/envelope.svg);
}
.info {
margin-bottom: .5em;
}
.balloon {
display: table;
position: relative;
border: solid 1px silver;
box-shadow: 3px 3px 6px rgba(0,0,0,.32);
padding: 0 .5em;
border-radius: 4px;
margin-top: 9px;
background-color: white;
display: none;
margin-left: -40px;
position: absolute;
padding: 20px;
left: 30px;
right: 0;
}
summary {
font-weight: bold;
cursor: pointer;
}
.proofs input {
display: none;
}
.proofs label {
cursor: pointer;
}
.proofs label:hover {
text-decoration: underline;
}
input:checked ~ .balloon {
display: block;
}
.balloon::before, .balloon::after {
position: absolute;
content: "";
border: 10px solid transparent;
border-top-width: 0;
white-space: nowrap;
}
.balloon::before {
border-bottom-color: rgba(215, 215, 215, 0.9);
top: -10px;
}
.balloon::after {
border-bottom-color: #fff;
top: -7px;
z-index: 1;
}
.balloon ul {
list-style-type: disc;
}
.balloon h3 {
margin-top: 0;
margin-bottom: 10px;
}
.balloon li {
white-space: pre-wrap;
word-wrap: break-word;
line-height: 120%;
}
.balloon .close {
float: right;
color: silver;
margin-top: -13px;
margin-right: 3px;
cursor: pointer;
font-size: xx-large;
}
.prooflog {
display: inline-block;
}
.prooflog p {
margin: 0;
}
code {
background-color: inherit;
border-radius: inherit;
box-shadow: none;
white-space: pre-wrap;
word-wrap: break-word;
}
@media (min-width: 800px) {
.ui {
display: flex;
margin: 20px;
margin-top: 70px;
}
.ui > div {
margin: 0;
}
.second ul {
padding-left: 40px;
margin-top: 0;
margin-left: 50px;
}
.balloon {
left: 100px;
right: 100px;
}
.balloon::before, .balloon::after {
right: 50px;
}
}

View File

@ -1,143 +0,0 @@
{
"version": 5.0,
"proofs": [
{
"matcher": "^https://gist.github.com/([A-Za-z0-9_-]+)/([0-9a-f]+)$",
"variables": {
"USERNAME": 1,
"PROOFID": 2
},
"profile": "https://github.com/{USERNAME}",
"proof": "https://api.github.com/gists/{PROOFID}",
"username": "{USERNAME}",
"service": "GitHub",
"checks": [
{
"relation": "eq",
"proof": [
"owner",
"login"
],
"claim": "{USERNAME}"
},
{
"relation": "eq",
"proof": [
"owner",
"html_url"
],
"claim": "https://github.com/{USERNAME}"
},
{
"relation": "contains",
"proof": [
"files",
"openpgp.md",
"content"
],
"claim": "[Verifying my OpenPGP key: openpgp4fpr:{FINGERPRINT}]"
}
]
},
{
"matcher": "^https://news.ycombinator.com/user\\?id=([A-Za-z0-9-]+)$",
"variables": {
"USERNAME": 1,
"PROFILE": 0
},
"profile": "{PROFILE}",
"proof": "https://hacker-news.firebaseio.com/v0/user/{USERNAME}.json",
"username": "{USERNAME}",
"service": "HackerNews",
"checks": [
{
"relation": "contains",
"proof": [
"about"
],
"claim": "[Verifying my OpenPGP key: openpgp4fpr:{FINGERPRINT}]"
}
]
},
{
"matcher": "^https://www.reddit.com/user/([^/]+)/comments/([^/]+)/([^/]+/)?$",
"variables": {
"USERNAME": 1,
"PROOF": 2
},
"profile": "https://www.reddit.com/user/{USERNAME}",
"proof": "https://www.reddit.com/user/{USERNAME}/comments/{PROOF}.json",
"username": "{USERNAME}",
"service": "Reddit",
"checks": [
{
"relation": "eq",
"proof": [
0,
"data",
"children",
0,
"data",
"author"
],
"claim": "{USERNAME}"
},
{
"relation": "contains",
"proof": [
0,
"data",
"children",
0,
"data",
"selftext"
],
"claim": "Verifying my OpenPGP key: openpgp4fpr:{FINGERPRINT}"
}
]
},
{
"matcher": "^https://([^/]+)/@([A-Za-z0-9_-]+)$",
"variables": {
"INSTANCE": 1,
"USERNAME": 2,
"PROFILE": 0
},
"profile": "{PROFILE}",
"proof": "{PROFILE}",
"username": "@{USERNAME}@{INSTANCE}",
"service": "Mastodon",
"checks": [
{
"relation": "oneOf",
"proof": [
"attachment",
"value"
],
"claim": "{FINGERPRINT}"
}
]
},
{
"matcher": "^https://([^?]+)/\\.well-known/host-meta\\.json$",
"variables": {
"HOSTMETA": 0,
"DOMAIN": 1
},
"profile": "https://{DOMAIN}",
"proof": "{HOSTMETA}",
"username": "{DOMAIN}",
"service": "https",
"checks": [
{
"relation": "oneOf",
"proof": [
"links",
"href"
],
"claim": "openpgp4fpr:{FINGERPRINT}"
}
]
}
]
}

View File

@ -1,59 +0,0 @@
{{#> layout }}
<div class="about">
<center><h2><a href="/about">About</a> | <a href="/about/news">News</a> | <a href="/about/usage">Usage</a> | <a href="/about/faq">FAQ</a> | <a href="/about/stats">Stats</a> | <a href="/about/privacy">Privacy</a></h2></center>
<h3>Profile pages</h3>
<p>
<span class="brand">keys.openpgp.org</span> displays profile pages for verified e-mail addresses.
The profile page displays all verified e-mails as well as name taken from the
key as well as the key fingerprint.
</p>
<p>Each profile page is reachable via <b>https://keys.openpgp.org/<i>email</i></b>
links e.g. <b>https://keys.openpgp.org/john@example.com</b></p>
<h3>Social proofs</h3>
<p>
Additionally to e-mail addresses and basic key info <span class="brand">keys.openpgp.org</span> displays
and verifies Social Proofs embedded in keys.
</p>
<h3>Adding social proofs</h3>
<p>
The process is two step: first creating or modifying an entry on social site (e.g. Gist on Github
or profile info on Mastodon) then adding the entry URL to key as a notation.
The notation uses <code>proof@keys.openpgp.org</code> key (note that due to compatibility reasons
<code>proof@metacode.biz</code> notation keys are also supported).
</p>
<h3>Supported proofs</h3>
<ul>
<li>Github: Gist containing text <code>[Verifying my OpenPGP key: openpgp4fpr:FINGERPRINT]</code>
within an <code>openpgp.md</code> file. See <a href="https://gist.github.com/wiktor-k/389d589dd19250e1f9a42bc3d5d40c16">example</a>.</li>
<li>Mastodon: Add a property in user profile settings that contains your key fingerprint.
See <a href="https://fosstodon.org/@yarmo">example</a>.</li>
<li>HackerNews: Add <code>[Verifying my OpenPGP key: openpgp4fpr:FINGERPRINT]</code> to your HackerNews profile.
See <a href="https://news.ycombinator.com/user?id=wiktor-k">example</a>. Note that you need at least 2 points of
karma (this is HackerNews API limitation).</li>
<li>Reddit: Add new post containing <code>[Verifying my OpenPGP key: openpgp4fpr:FINGERPRINT]</code> on your
own user page space. See <a href="https://www.reddit.com/user/wiktor-k/comments/bo5oih/test/">example</a>.
Note that currently Reddit proofs fail to verify in Firefox due to a
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1235978">bug in Tracking Protection</a>.</li>
</ul>
<h3>Adding proof to OpenPGP key</h3>
<p>Edit your key with <code>gpg --edit-key FINGERPRINT</code> and enter <code>notation</code> command.
The notation should be <code>proof@keys.openpgp.org=URL</code> where URL is the link to your proof created in the previous step.</p>
<p>If you have multiple proofs repeat the <code>notation</code> command as necessary.</p>
<p>After saving key changes with <code>save</code> use the following command to update your key:</p>
<blockquote>
gpg --export your_address@example.net | curl -T - {{ base_uri }}
</blockquote>
<p>See <a href="/wiktor&#x40;metacode.biz">this profile page</a> for an example on how proofs are rendered.</p>
</div>
{{/layout}}

View File

@ -1,75 +0,0 @@
<!doctype html>
<html lang="{{lang}}">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="/assets/site.css?v=19" type="text/css"/>
<link rel="alternate" href="/atom.xml" type="application/atom+xml" title="keys.openpgp.org newsfeed" />
<title>keys.openpgp.org</title>
</head>
<body lang="{{lang}}">
<div class="card">
<link rel="stylesheet" href="/assets/proofs.css">
<div class="ui">
<div class="first">
<img class="avatar">
<h2 class="name"></h2>
</div>
<div class="second">
<h3><a href="" class="fpr"><code></code></a></h3>
<ul class="info">
</ul>
<ul class="proofs">
</ul>
</div>
<div class="top_link back_link">
<a href="{{ ../base_uri }}">{{ text "back to keys.openpgp.org" }}</a>
</div>
<div class="top_link proofs_link">
<a href="/about/proofs">{{ text "about profile pages" }}</a>
</div>
</div>
<template id="verification-log">
<div class="balloon" hidden>
<span class="close" title="Close">×</span>
<h3><span data-bind="username:textContent"></span> on <span data-bind="service:textContent"></span></h3>
<p>This identity was verified in your browser with the following steps:</p>
<ol>
<li>Download proof: <a href="" rel="noopener nofollow" target="_blank" data-bind="proofUrl:textContent proofUrl:href"></a></li>
<li>Find verification text: <code data-bind="verificationText:textContent"></code></li>
</ol>
<p data-bind="error:hidden">All steps successful. <span class="verified"> proof verified.</span></p>
<div data-bind="success:hidden">
<p>❌ Proof verification failed.</p>
<p>Error in step <span data-bind="step:textContent"></span>: <code data-bind="error:textContent"></code></p>
<p>Note: Reddit proofs fail to verify in Firefox due to a <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1235978">bug in Tracking Protection</a>.</p>
</div>
</div>
</template>
<span hidden class="text-proof">proof</span>
<span hidden class="text-verified">verified</span>
<!--
Subresource Integrity pins the version of OpenPGP.js to one specifed release.
To check if this page is running the same version as the one that is released run:
$ curl -sSL https://github.com/openpgpjs/openpgpjs/raw/v4.10.4/dist/openpgp.min.js | openssl dgst -sha384 -binary | openssl base64 -A
The output should match the value in the "integrity" attribute (minus the hash name).
-->
<script src="/assets/openpgp.min.js" integrity="sha384-/N1ZJTH7aZFvzCM9Jy9dQmQzroYQpB5L2qrNPgYpg1/tbwVDvaqWwGHfHeFhSpcn" crossorigin="anonymous"></script>
<script src="/assets/js/proofs.js"></script>
<noscript>This page contents are generated and validated by JavaScript code running in your browser.
Please enable JavaScript to see user's profile page.</noscript>
<div class="spacer"></div>
</div>
<div class="attribution">
<p>
<a href="https://gitlab.com/hagrid-keyserver/hagrid/">Hagrid</a>
{{ text "v{{ version }} built from" rerender }}
<a href="https://gitlab.com/hagrid-keyserver/hagrid/commit/{{ commit }}">{{ commit }}</a>
</p>
<p>{{ text "Powered by <a href=\"https://sequoia-pgp.org\">Sequoia-PGP</a> and <a href=\"https://openpgpjs.org\">OpenPGP.js</a>" }}</p>
<p>{{ text "Background image retrieved from <a href=\"https://www.toptal.com/designers/subtlepatterns/subtle-grey/\">Subtle Patterns</a> under CC BY-SA 3.0" }}</p>
</div>
</body>
</html>

View File

@ -223,20 +223,6 @@ location /search {
proxy_pass http://127.0.0.1:8080;
}
location ~ "^/([A-Za-z0-9._-]+)@([A-Za-z0-9._-]+)$" {
limit_req zone=search_email burst=50 nodelay;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
# add_header Content-Security-Policy "default-src 'none'; script-src 'self'; img-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; report-uri https://keysopenpgporg.report-uri.com/r/d/csp/enforce" always;
add_header Content-Security-Policy "default-src 'none'; script-src 'self'; connect-src https:; img-src 'self' https://www.gravatar.com; style-src 'self' 'unsafe-inline'; font-src 'self'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; report-uri https://keysopenpgporg.report-uri.com/r/d/csp/enforce" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Expect-CT 'max-age=31536000, report-uri="https://keysopenpgporg.report-uri.com/r/d/ct/reportOnly"' always;
error_page 429 /errors/429/rate-limit-web;
proxy_pass http://127.0.0.1:8080;
}
location /pks {
proxy_pass http://127.0.0.1:8080;
}

View File

@ -348,11 +348,6 @@ fn stats() -> MyResponse {
MyResponse::ok_bare("about/stats")
}
#[get("/about/proofs")]
fn proofs() -> MyResponse {
MyResponse::ok_bare("about/proofs")
}
#[get("/errors/<code>/<template>")]
fn errors(
i18n: I18n,
@ -391,7 +386,6 @@ fn rocket_factory(mut rocket: rocket::Rocket) -> Result<rocket::Rocket> {
usage,
files,
stats,
proofs,
errors,
// VKSv1
vks_api::vks_v1_by_email,
@ -403,7 +397,6 @@ fn rocket_factory(mut rocket: rocket::Rocket) -> Result<rocket::Rocket> {
vks_api::request_verify_fallback,
// User interaction.
vks_web::search,
vks_web::render_profile,
vks_web::upload,
vks_web::upload_post_form,
vks_web::upload_post_form_data,

View File

@ -233,43 +233,6 @@ pub fn search(
}
}
#[derive(Debug)]
pub struct ParamEmail(crate::database::types::Email);
impl std::fmt::Display for ParamEmail {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.as_str())
}
}
use rocket::request::FromParam;
use rocket::http::RawStr;
impl<'r> FromParam<'r> for ParamEmail {
type Error = &'r RawStr;
fn from_param(param: &'r RawStr) -> std::result::Result<Self, Self::Error> {
use std::str::FromStr;
if let Ok(email) = crate::database::types::Email::from_str(param) {
Ok(ParamEmail(email))
} else {
Err(param)
}
}
}
#[get("/<q>")]
pub fn render_profile(
db: rocket::State<KeyDatabase>,
q: ParamEmail,
) -> MyResponse {
if let Some(_) = db.by_email(&q.0) {
MyResponse::ok_bare("found-profile")
} else {
MyResponse::not_found(None, format!("No key found for given e-mail address: {}", q))
}
}
fn key_to_response(
db: rocket::State<KeyDatabase>,
query_string: String,