From 437b6d1efe47bab237ff711d042358b299e42c61 Mon Sep 17 00:00:00 2001 From: Yarmo Mackenbach Date: Sun, 3 Jan 2021 18:38:56 +0100 Subject: [PATCH 1/5] Improve script tags --- views/profile.pug | 4 ++-- views/template.base.pug | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/views/profile.pug b/views/profile.pug index 4973923..d714f7e 100644 --- a/views/profile.pug +++ b/views/profile.pug @@ -23,6 +23,6 @@ main.container.container--profile a(href="https://codeberg.org/keyoxide/web/releases")= settings.keyoxide_version | ). -script(src='/static/openpgp.min.js') -script(src='/static/doip.js') +script(type='application/javascript' src='/static/openpgp.min.js' charset='utf-8') +script(type='application/javascript' src='/static/doip.js' charset='utf-8') script(type='application/javascript' src='/static/scripts.js' charset='utf-8') diff --git a/views/template.base.pug b/views/template.base.pug index 24b8f48..80fcebe 100644 --- a/views/template.base.pug +++ b/views/template.base.pug @@ -36,6 +36,6 @@ main.container a(href='https://fosstodon.org/@keyoxide') Mastodon p © 2020 Keyoxide contributors -script(src='/static/openpgp.min.js') -script(src='/static/qrcode.min.js') +script(type='application/javascript' src='/static/openpgp.min.js' charset='utf-8') +script(type='application/javascript' src='/static/qrcode.min.js' charset='utf-8') script(type='application/javascript' src='/static/scripts.js' charset='utf-8') From 224891035e10b10f4768ed41d77ad3ff8d5301d6 Mon Sep 17 00:00:00 2001 From: Yarmo Mackenbach Date: Tue, 5 Jan 2021 13:19:26 +0100 Subject: [PATCH 2/5] Update deps --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 3bf5184..0fec124 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "dependencies": { "bent": "^7.3.12", - "doipjs": "^0.8.4", + "doipjs": "^0.8.5", "dotenv": "^8.2.0", "express": "^4.17.1", "express-validator": "^6.8.0", diff --git a/yarn.lock b/yarn.lock index a95342d..e819b8c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -703,10 +703,10 @@ doctypes@^1.1.0: resolved "https://registry.yarnpkg.com/doctypes/-/doctypes-1.1.0.tgz#ea80b106a87538774e8a3a4a5afe293de489e0a9" integrity sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk= -doipjs@^0.8.4: - version "0.8.4" - resolved "https://registry.yarnpkg.com/doipjs/-/doipjs-0.8.4.tgz#41046de8b9c69afd633d452e09f92a80930f2e0a" - integrity sha512-eJdrwClJ5fU3D4oIl+cGMSLEaHJ2AJnF4KvlO095aq+ztJQWi5WYwnqM6j5ASj9pMUlA3OAyYKhIPdZSlUF5uA== +doipjs@^0.8.5: + version "0.8.5" + resolved "https://registry.yarnpkg.com/doipjs/-/doipjs-0.8.5.tgz#f27770551304314db2921dd019a983367c9258e6" + integrity sha512-zTIwAHUKn8t/EL36ZUIJ1Oyp+S0B2ifthPUJw7hC+8XBrHVxf9Q8koCQ92TF1CWY6eKT3eAlEEkR0BwqLpwWhw== dependencies: bent "^7.3.12" browserify "^17.0.0" From fe423b54e01d49afa9fcc491ed1fd6c1090225fc Mon Sep 17 00:00:00 2001 From: Yarmo Mackenbach Date: Tue, 5 Jan 2021 13:20:06 +0100 Subject: [PATCH 3/5] Allow setting of custom HKP server --- routes/profile.js | 4 ++++ static/scripts.js | 19 ++++++++++++++++--- views/profile.pug | 1 + 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/routes/profile.js b/routes/profile.js index ab50bb3..37f969f 100644 --- a/routes/profile.js +++ b/routes/profile.js @@ -37,6 +37,10 @@ router.get('/hkp/:input', function(req, res) { res.render('profile', { mode: "hkp", uid: req.params.input }) }); +router.get('/hkp/:server/:input', function(req, res) { + res.render('profile', { mode: "hkp", uid: req.params.input, server: req.params.server }) +}); + router.get('/keybase/:username/:fingerprint', function(req, res) { res.render('profile', { mode: "keybase", uid: `${req.params.username}/${req.params.fingerprint}` }) }); diff --git a/static/scripts.js b/static/scripts.js index a740527..4b6be52 100644 --- a/static/scripts.js +++ b/static/scripts.js @@ -235,9 +235,14 @@ async function displayProfile(opts) { let icon_qr = ''; try { - keyData = await doip.keys.fetch.uri(`${opts.mode}:${opts.input}`); + 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(); - // keyData = await fetchKeys(opts); } catch (e) { feedback += `

There was a problem fetching the keys.

`; feedback += `${e}`; @@ -1026,6 +1031,7 @@ let elFormVerify = document.body.querySelector("#form-verify"), elFormProofs = document.body.querySelector("#form-proofs"), elProfileUid = document.body.querySelector("#profileUid"), elProfileMode = document.body.querySelector("#profileMode"), + elProfileServer = document.body.querySelector("#profileServer"), elModeSelect = document.body.querySelector("#modeSelect"), elUtilWKD = document.body.querySelector("#form-util-wkd"), elUtilQRFP = document.body.querySelector("#form-util-qrfp"), @@ -1171,7 +1177,7 @@ if (elFormProofs) { } if (elProfileUid) { - let match, opts, profileUid = elProfileUid.innerHTML; + let opts, profileUid = elProfileUid.innerHTML; switch (elProfileMode.innerHTML) { default: case "auto": @@ -1191,6 +1197,13 @@ if (elProfileUid) { break; case "hkp": + opts = { + input: profileUid, + server: elProfileServer.innerHTML, + mode: elProfileMode.innerHTML + } + break; + case "wkd": opts = { input: profileUid, diff --git a/views/profile.pug b/views/profile.pug index d714f7e..1b1c81d 100644 --- a/views/profile.pug +++ b/views/profile.pug @@ -9,6 +9,7 @@ head main.container.container--profile .content span#profileUid(style='display: none;') #{uid} + span#profileServer(style='display: none;') #{server} span#profileMode(style='display: none;') #{mode} #profileHeader img#profileAvatar(src='/static/img/avatar_placeholder.png' alt='avatar' style='display: none') From c654b5646ce5a59468b8aa2df62fd1fb4599f5a6 Mon Sep 17 00:00:00 2001 From: Yarmo Mackenbach Date: Thu, 7 Jan 2021 16:44:33 +0100 Subject: [PATCH 4/5] Add support f or signature profiles --- CHANGELOG.md | 2 + package.json | 2 +- routes/profile.js | 14 ++- static/scripts.js | 278 ++++++++++++++++++++++++++++++++-------------- static/styles.css | 5 + views/profile.pug | 11 +- yarn.lock | 8 +- 7 files changed, 224 insertions(+), 96 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac1f5a9..83cb651 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed +- Allow setting of custom HKP server ## [2.3.4] - 2021-01-02 ### Fixed diff --git a/package.json b/package.json index 0fec124..d2e9b06 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "dependencies": { "bent": "^7.3.12", - "doipjs": "^0.8.5", + "doipjs": "^0.9.0", "dotenv": "^8.2.0", "express": "^4.17.1", "express-validator": "^6.8.0", diff --git a/routes/profile.js b/routes/profile.js index 37f969f..855f9c2 100644 --- a/routes/profile.js +++ b/routes/profile.js @@ -29,24 +29,28 @@ more information on this, and how to apply and follow the GNU AGPL, see 0) { + throw(sigVerification.errors.join(', ')) + } + + keyData = sigVerification.publicKey + fingerprint = sigVerification.fingerprint + + const sigData = await openpgp.cleartext.readArmored(opts.input); + const sigText = sigData.getText(); + let sigKeys = []; + sigText.split('\n').forEach((line, i) => { + const match = line.match(/^(.*)\=(.*)$/i); + if (!match || !match[1]) { + return; + } + switch (match[1].toLowerCase()) { + case 'key': + sigKeys.push(match[2]); + break; + + default: + break; + } + }); + + if (sigKeys.length === 0) { + throw('No key URI found'); + } + + sigKeyUri = sigKeys[0]; + } catch (e) { + feedback += `

There was a problem reading the signature.

`; + feedback += `${e}`; + 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 += `

There was a problem fetching the keys.

`; + feedback += `${e}`; + document.body.querySelector('#profileData').innerHTML = feedback; + document.body.querySelector('#profileName').innerHTML = "Could not load profile"; + return; } - keyData = await doip.keys.fetch.uri(keyURI); - fingerprint = keyData.keyPacket.getFingerprint(); - } catch (e) { - feedback += `

There was a problem fetching the keys.

`; - feedback += `${e}`; - document.body.querySelector('#profileData').innerHTML = feedback; - document.body.querySelector('#profileName').innerHTML = "Could not load profile"; - return; } const userPrimary = await keyData.getPrimaryUser(); @@ -259,9 +306,17 @@ async function displayProfile(opts) { let imgUri = null; // Determine WKD or HKP link - switch (opts.mode) { + let keyUriMode = opts.mode; + let keyUriId = opts.input; + if (opts.mode === 'sig') { + const keyUriMatch = sigKeyUri.match(/(.*):(.*)/); + keyUriMode = keyUriMatch[1]; + keyUriId = keyUriMatch[2]; + } + + switch (keyUriMode) { case "wkd": - const [, localPart, domain] = /(.*)@(.*)/.exec(opts.input); + const [, localPart, domain] = /(.*)@(.*)/.exec(keyUriId); 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}`; @@ -337,18 +392,22 @@ async function displayProfile(opts) { feedback += ``; feedback += `
`; feedback += `
`; - feedback += ``; + feedback += ``; feedback += `
`; feedback += `
`; feedback += `
`; - feedback += ``; + feedback += ``; feedback += `
`; // Display feedback document.body.querySelector('#profileData').innerHTML = feedback; try { - verifications = await doip.claims.verify(keyData, fingerprint, {'proxyPolicy':'adaptive'}) + if (sigVerification) { + verifications = sigVerification.claims + } else { + verifications = await doip.claims.verify(keyData, fingerprint, {'proxyPolicy':'adaptive'}) + } } catch (e) { feedback += `

There was a problem verifying the claims.

`; feedback += `${e}`; @@ -362,25 +421,102 @@ async function displayProfile(opts) { return; } - let primaryClaims - feedback = ""; - if (userMail) { - verifications.forEach((userId, i) => { - if (!keyData.users[i].userId) { - keyData.users[i].userId = { - email: 'email not specified' - } - } - if (keyData.users[i].userId.email != userMail) { + if (opts.mode === 'sig') { + feedback += `
`; + feedback += `
`; + feedback += `
proofs
`; + feedback += `
`; + + 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 += `
`; + feedback += `
${claimData.serviceprovider.name}
`; + feedback += `
`; + feedback += `${claimData.profile.display}`; + if (claim.isVerified) { + feedback += `verified ✔`; + } else { + feedback += `unverified`; + } + if (claim.isVerified && claimData.profile.qr) { + feedback += `${icon_qr}`; + } + feedback += `
`; + feedback += `
`; + }); + } else { + let primaryClaims; + + if (userMail) { + verifications.forEach((userId, i) => { + if (!keyData.users[i].userId) { + keyData.users[i].userId = { + email: 'email not specified' + } + } + + if (keyData.users[i].userId.email != userMail) { + return; + } + + feedback += `
`; + feedback += `
`; + // feedback += ``; + feedback += `
${keyData.users[i].userId.email} (primary)
`; + feedback += `
`; + + if (userId.length == 0) { + feedback += `
`; + feedback += `
`; + feedback += `
No claims associated
`; + feedback += `
`; + return; + } + + 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 += `
`; + feedback += `
${claimData.serviceprovider.name}
`; + feedback += `
`; + feedback += `${claimData.profile.display}`; + if (claim.isVerified) { + feedback += `verified ✔`; + } else { + feedback += `unverified`; + } + if (claim.isVerified && claimData.profile.qr) { + feedback += `${icon_qr}`; + } + feedback += `
`; + feedback += `
`; + }); + }); + } + + verifications.forEach((userId, i) => { + if (userMail && keyData.users[i].userId.email == userMail) { return; } feedback += `
`; feedback += `
`; - // feedback += ``; - feedback += `
${keyData.users[i].userId.email} (primary)
`; + feedback += `
${keyData.users[i].userId.email}
`; feedback += `
`; if (userId.length == 0) { @@ -394,7 +530,13 @@ async function displayProfile(opts) { 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 + if (primaryClaims && primaryClaims.toString() == userId.toString()) { + feedback += `
`; + feedback += `
`; + feedback += `
Identical to primary
`; + feedback += `
`; + return; + } userId.forEach((claim, i) => { const claimData = claim.serviceproviderData; @@ -419,57 +561,6 @@ async function displayProfile(opts) { }); } - verifications.forEach((userId, i) => { - if (userMail && keyData.users[i].userId.email == userMail) { - return; - } - - feedback += `
`; - feedback += `
`; - feedback += `
${keyData.users[i].userId.email}
`; - feedback += `
`; - - if (userId.length == 0) { - feedback += `
`; - feedback += `
`; - feedback += `
No claims associated
`; - feedback += `
`; - return; - } - - 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)); - - if (primaryClaims && primaryClaims.toString() == userId.toString()) { - feedback += `
`; - feedback += `
`; - feedback += `
Identical to primary
`; - feedback += `
`; - return; - } - - userId.forEach((claim, i) => { - const claimData = claim.serviceproviderData; - if (!claimData.serviceprovider.name) { - return; - } - feedback += `
`; - feedback += `
${claimData.serviceprovider.name}
`; - feedback += `
`; - feedback += `${claimData.profile.display}`; - if (claim.isVerified) { - feedback += `verified ✔`; - } else { - feedback += `unverified`; - } - if (claim.isVerified && claimData.profile.qr) { - feedback += `${icon_qr}`; - } - feedback += `
`; - feedback += `
`; - }); - }); - // Display feedback document.body.querySelector('#profileProofs').innerHTML = feedback; } @@ -1029,6 +1120,7 @@ async function fetchWithTimeout(url, timeout = 3000) { let elFormVerify = document.body.querySelector("#form-verify"), elFormEncrypt = document.body.querySelector("#form-encrypt"), elFormProofs = document.body.querySelector("#form-proofs"), + elFormSignatureProfile = document.body.querySelector("#form-generate-signature-profile"), elProfileUid = document.body.querySelector("#profileUid"), elProfileMode = document.body.querySelector("#profileMode"), elProfileServer = document.body.querySelector("#profileServer"), @@ -1180,6 +1272,19 @@ if (elProfileUid) { let opts, profileUid = elProfileUid.innerHTML; switch (elProfileMode.innerHTML) { default: + case "sig": + elFormSignatureProfile.onsubmit = function (evt) { + evt.preventDefault(); + + opts = { + input: document.body.querySelector("#plaintext_input").value, + mode: elProfileMode.innerHTML + } + + displayProfile(opts) + } + break; + case "auto": if (/.*@.*/.test(profileUid)) { // Match email for wkd @@ -1220,7 +1325,10 @@ if (elProfileUid) { } break; } - displayProfile(opts); + + if (elProfileMode.innerHTML !== 'sig') { + displayProfile(opts); + } } if (elUtilWKD) { diff --git a/static/styles.css b/static/styles.css index 45fb0f4..815bca4 100644 --- a/static/styles.css +++ b/static/styles.css @@ -347,6 +347,11 @@ a.proofQR:hover { background-color: #6abb5a; } +#form-generate-signature-profile { + margin-bottom: 2em; + font-size: 0.9rem; +} + #qrcode { display: flex; justify-content: center; diff --git a/views/profile.pug b/views/profile.pug index 1b1c81d..a8546d2 100644 --- a/views/profile.pug +++ b/views/profile.pug @@ -11,11 +11,20 @@ main.container.container--profile span#profileUid(style='display: none;') #{uid} span#profileServer(style='display: none;') #{server} span#profileMode(style='display: none;') #{mode} + if (mode == 'sig') + #profileSigInput + form#form-generate-signature-profile(method='post') + p Please enter the raw profile signature below and press "Generate profile". + textarea#plaintext_input(name='plaintext_input') + input(type='submit', name='submit', value='Generate profile').bigBtn #profileHeader img#profileAvatar(src='/static/img/avatar_placeholder.png' alt='avatar' style='display: none') p#profileName #profileData - p Loading keys & verifying proofs… + if (mode == 'sig') + p Waiting for input… + else + p Loading keys & verifying proofs… footer p | Generated by diff --git a/yarn.lock b/yarn.lock index e819b8c..b077b4b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -703,10 +703,10 @@ doctypes@^1.1.0: resolved "https://registry.yarnpkg.com/doctypes/-/doctypes-1.1.0.tgz#ea80b106a87538774e8a3a4a5afe293de489e0a9" integrity sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk= -doipjs@^0.8.5: - version "0.8.5" - resolved "https://registry.yarnpkg.com/doipjs/-/doipjs-0.8.5.tgz#f27770551304314db2921dd019a983367c9258e6" - integrity sha512-zTIwAHUKn8t/EL36ZUIJ1Oyp+S0B2ifthPUJw7hC+8XBrHVxf9Q8koCQ92TF1CWY6eKT3eAlEEkR0BwqLpwWhw== +doipjs@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/doipjs/-/doipjs-0.9.0.tgz#628af8316ea40904d8695ef372e9f0d0211c25d2" + integrity sha512-Tw9Ep9vyWNFx4cBmNNtkE/gLakBY32+A09WLosBpAdtvm573h0N/Jww/IL5cr0gu5947pbqxUCnwkQySRB3N1A== dependencies: bent "^7.3.12" browserify "^17.0.0" From a85d9f310cc17fab6a10ed7290bfc3861783f52d Mon Sep 17 00:00:00 2001 From: Yarmo Mackenbach Date: Thu, 7 Jan 2021 16:46:42 +0100 Subject: [PATCH 5/5] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83cb651..88e5eae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- Support for signature profiles ### Changed - Allow setting of custom HKP server