From 01ba4a3a544b72775f2fcd66887a7b49b9c9b397 Mon Sep 17 00:00:00 2001 From: Yarmo Mackenbach Date: Mon, 3 May 2021 17:09:10 +0200 Subject: [PATCH] Fix support for signature profiles --- package.json | 1 + routes/profile.js | 23 +++++---------- server/index.js | 25 ++++++++++++++++ server/keys.js | 62 ++++++++++++++++++++++++++++++++++------ views/profile-failed.pug | 9 ------ views/profile.pug | 46 ++++++++++++++++++----------- yarn.lock | 2 +- 7 files changed, 118 insertions(+), 50 deletions(-) delete mode 100644 views/profile-failed.pug diff --git a/package.json b/package.json index f80c322..e2e2e0b 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "main": "index.js", "dependencies": { "bent": "^7.3.12", + "body-parser": "^1.19.0", "dialog-polyfill": "^0.5.6", "doipjs": "^0.12.4", "dotenv": "^8.2.0", diff --git a/routes/profile.js b/routes/profile.js index 7794d0b..080f19c 100644 --- a/routes/profile.js +++ b/routes/profile.js @@ -28,41 +28,35 @@ 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 . */ const router = require('express').Router() +const bodyParser = require('body-parser').urlencoded({ extended: false }) const kx = require('../server') router.get('/sig', (req, res) => { - res.render('profile', { mode: 'sig' }) + res.render('profile', { isSignature: true, signature: null }) +}) + +router.post('/sig', bodyParser, async (req, res) => { + const data = await kx.generateSignatureProfile(req.body.signature) + res.render('profile', { data: data, isSignature: true, signature: req.body.signature }) }) router.get('/wkd/:id', async (req, res) => { const data = await kx.generateWKDProfile(req.params.id) - if (data.errors.length > 0) { - return res.render('profile-failed', { data: data }) - } res.render('profile', { data: data }) }) router.get('/hkp/:id', async (req, res) => { const data = await kx.generateHKPProfile(req.params.id) - if (data.errors.length > 0) { - return res.render('profile-failed', { data: data }) - } res.render('profile', { data: data }) }) router.get('/hkp/:server/:id', async (req, res) => { const data = await kx.generateHKPProfile(req.params.id, req.params.server) - if (data.errors.length > 0) { - return res.render('profile-failed', { data: data }) - } res.render('profile', { data: data }) }) router.get('/keybase/:username/:fingerprint', async (req, res) => { const data = await kx.generateKeybaseProfile(req.params.username, req.params.fingerprint) - if (data.errors.length > 0) { - return res.render('profile-failed', { data: data }) - } res.render('profile', { data: data }) }) @@ -73,9 +67,6 @@ router.get('/:id', async (req, res) => { } else { data = await kx.generateHKPProfile(req.params.id) } - if (data.errors.length > 0) { - return res.render('profile-failed', { data: data }) - } res.render('profile', { data: data }) }) diff --git a/server/index.js b/server/index.js index 89966be..4015959 100644 --- a/server/index.js +++ b/server/index.js @@ -81,6 +81,30 @@ const generateHKPProfile = async (id, keyserverDomain) => { }) } +const generateSignatureProfile = async (signature) => { + return keys.fetchSignature(signature) + .then(async key => { + let keyData = key.keyData + delete key.keyData + keyData = processKeyData(keyData) + + return { + key: key, + keyData: keyData, + extra: await computeExtraData(key, keyData), + errors: [] + } + }) + .catch(err => { + return { + key: null, + keyData: null, + extra: null, + errors: [err.message] + } + }) +} + const generateKeybaseProfile = async (username, fingerprint) => { return keys.fetchKeybase(id, keyserverDomain) .then(async key => { @@ -146,3 +170,4 @@ const computeExtraData = async (key, keyData) => { exports.generateWKDProfile = generateWKDProfile exports.generateHKPProfile = generateHKPProfile exports.generateKeybaseProfile = generateKeybaseProfile +exports.generateSignatureProfile = generateSignatureProfile diff --git a/server/keys.js b/server/keys.js index 4330f01..699600e 100644 --- a/server/keys.js +++ b/server/keys.js @@ -65,22 +65,22 @@ const fetchWKD = (id) => { } }) } catch (error) { - reject(new Error("No public keys could be fetched using WKD")) + reject(new Error(`No public keys could be fetched using WKD`)) } } if (!plaintext) { - reject(new Error("No public keys could be fetched using WKD")) + reject(new Error(`No public keys could be fetched using WKD`)) } try { output.publicKey = (await openpgp.key.read(plaintext)).keys[0] } catch(error) { - reject(new Error("No public keys could be read from the data fetched using WKD")) + reject(new Error(`No public keys could be read from the data fetched using WKD`)) } if (!output.publicKey) { - reject(new Error("No public keys could be read from the data fetched using WKD")) + reject(new Error(`No public keys could be read from the data fetched using WKD`)) } resolve(output) @@ -107,11 +107,56 @@ const fetchHKP = (id, keyserverDomain) => { output.publicKey = await doip.keys.fetchHKP(id, keyserverDomain) output.fetchURL = `https://${keyserverDomain}/pks/lookup?op=get&options=mr&search=${query}` } catch(error) { - reject(new Error("No public keys could be fetched using HKP")) + reject(new Error(`No public keys could be fetched using HKP`)) } if (!output.publicKey) { - reject(new Error("No public keys could be fetched using HKP")) + reject(new Error(`No public keys could be fetched using HKP`)) + } + + resolve(output) + }) +} + +const fetchSignature = (signature) => { + return new Promise(async (resolve, reject) => { + let output = { + publicKey: null, + fetchURL: null, + keyData: null + } + + // Check validity of signature + let signatureData + try { + signatureData = await openpgp.cleartext.readArmored(signature) + } catch (error) { + reject(new Error(`Signature could not be properly read (${error.message})`)) + } + + // Process the signature + try { + output.keyData = await doip.signatures.process(signature) + output.publicKey = output.keyData.key.data + // TODO Find the URL to the key + output.fetchURL = null + } catch(error) { + reject(new Error(`Signature could not be properly read (${error.message})`)) + } + + // Check if a key was fetched + if (!output.publicKey) { + reject(new Error(`No public keys could be fetched`)) + } + + // Check validity of signature + const verified = await openpgp.verify({ + message: signatureData, + publicKeys: output.publicKey + }) + const { valid } = verified.signatures[0] + if (!valid) { + reject(new Error('Signature was invalid')) } resolve(output) @@ -129,11 +174,11 @@ const fetchKeybase = (username, fingerprint) => { output.publicKey = await doip.keys.fetchKeybase(username, fingerprint) output.fetchURL = `https://keybase.io/${username}/pgp_keys.asc?fingerprint=${fingerprint}` } catch(error) { - reject(new Error("No public keys could be fetched from Keybase")) + reject(new Error(`No public keys could be fetched from Keybase`)) } if (!output.publicKey) { - reject(new Error("No public keys could be fetched from Keybase")) + reject(new Error(`No public keys could be fetched from Keybase`)) } resolve(output) @@ -142,4 +187,5 @@ const fetchKeybase = (username, fingerprint) => { exports.fetchWKD = fetchWKD exports.fetchHKP = fetchHKP +exports.fetchSignature = fetchSignature exports.fetchKeybase = fetchKeybase diff --git a/views/profile-failed.pug b/views/profile-failed.pug deleted file mode 100644 index 268d181..0000000 --- a/views/profile-failed.pug +++ /dev/null @@ -1,9 +0,0 @@ -extends templates/base.pug - -block content - section.profile.narrow - h2 Something went wrong when generating the profile - - ul - each error in data.errors - li= error diff --git a/views/profile.pug b/views/profile.pug index df9685e..2fad42a 100644 --- a/views/profile.pug +++ b/views/profile.pug @@ -24,7 +24,7 @@ block content script. kx = { key: { - url: "!{data.key.fetchURL}", + url: "!{data && data.key && data.key.fetchURL ? data.key.fetchURL : null}", object: null } } @@ -57,21 +57,35 @@ block content p a#qr--altLink - #profileHeader.card.card--profileHeader - a.avatar(href="#") - img#profileAvatar(src=data.extra.avatarURL alt="avatar") + if (isSignature) + #profileSigInput.card.card--form + form#formGenerateSignatureProfile(method='post') + label(for="signature") Please enter the raw profile signature below and press "Generate profile". + textarea#signature(name='signature')= signature + input(type='submit', name='submit', value='Generate profile') - p#profileName= data.keyData.users[data.keyData.primaryUserIndex].userData.name - .buttons - button(onClick="document.querySelector('#dialog--encryptMessage').showModal();") Encrypt message - button(onClick="document.querySelector('#dialog--verifySignature').showModal();") Verify signature - #profileProofs.card - h2 Key - kx-key(data-keydata=data.keyData) + if (data && 'errors' in data && data.errors.length > 0) + h2 Something went wrong while generating the profile + ul + each error in data.errors + li= error + else + unless (isSignature && !signature) + #profileHeader.card.card--profileHeader + a.avatar(href="#") + img#profileAvatar(src=data.extra.avatarURL alt="avatar") - +generateUser(data.keyData.users[data.keyData.primaryUserIndex], true) - each user, index in data.keyData.users - unless index == data.keyData.primaryUserIndex - +generateUser(user, false) - + p#profileName= data.keyData.users[data.keyData.primaryUserIndex].userData.name + .buttons + button(onClick="document.querySelector('#dialog--encryptMessage').showModal();") Encrypt message + button(onClick="document.querySelector('#dialog--verifySignature').showModal();") Verify signature + + #profileProofs.card + h2 Key + kx-key(data-keydata=data.keyData) + + +generateUser(data.keyData.users[data.keyData.primaryUserIndex], true) + each user, index in data.keyData.users + unless index == data.keyData.primaryUserIndex + +generateUser(user, false) diff --git a/yarn.lock b/yarn.lock index c2b8ff2..5247cbc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -874,7 +874,7 @@ bn.js@^4.0.0: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== -body-parser@1.19.0: +body-parser@1.19.0, body-parser@^1.19.0: version "1.19.0" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==