diff --git a/index.js b/index.js index 3435401..d8659a3 100644 --- a/index.js +++ b/index.js @@ -67,6 +67,7 @@ app.use('/robots.txt', express.static('robots.txt')) app.use('/', require('./routes/main')) app.use('/static', require('./routes/static')) +app.use('/util', require('./routes/util')) app.use('/', require('./routes/profile')) app.listen(app.get('port'), () => { diff --git a/static/scripts.js b/static/scripts.js index b300225..1345b0b 100644 --- a/static/scripts.js +++ b/static/scripts.js @@ -207,218 +207,3 @@ const showQR = function(input, type) { qrContext.clearRect(0, 0, qrTarget.width, qrTarget.height); } } - -// let elFormSignatureProfile = document.body.querySelector("#formGenerateSignatureProfile"), -// 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"), -// elUtilQR = document.body.querySelector("#form-util-qr"), -// elUtilProfileURL = document.body.querySelector("#form-util-profile-url"); - -// 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")); -// } - -// 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 -// opts = { -// input: profileUid, -// mode: "wkd" -// } -// } else { -// // Match fingerprint for hkp -// opts = { -// input: profileUid, -// mode: "hkp" -// } -// } -// break; - -// case "hkp": -// opts = { -// input: profileUid, -// server: elProfileServer.innerHTML, -// mode: elProfileMode.innerHTML -// } -// break; - -// case "wkd": -// opts = { -// input: profileUid, -// mode: elProfileMode.innerHTML -// } -// break; - -// case "keybase": -// let match = profileUid.match(/(.*)\/(.*)/); -// opts = { -// username: match[1], -// fingerprint: match[2], -// mode: elProfileMode.innerHTML -// } -// break; -// } - -// if (elProfileMode.innerHTML !== 'sig') { -// keyoxide.displayProfile(opts); -// } -// } - -// if (elUtilWKD) { -// elUtilWKD.onsubmit = function (evt) { -// evt.preventDefault(); -// } - -// const elInput = document.body.querySelector("#input"); -// const elOutput = document.body.querySelector("#output"); -// const elOutputDirect = document.body.querySelector("#output_url_direct"); -// const elOutputAdvanced = document.body.querySelector("#output_url_advanced"); -// let match; - -// elInput.addEventListener("input", async function(evt) { -// if (evt.target.value) { -// 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"; -// } -// } else { -// elOutput.innerText = "Waiting for input"; -// elOutputDirect.innerText = "Waiting for input"; -// elOutputAdvanced.innerText = "Waiting for input"; -// } -// }); - -// elInput.dispatchEvent(new Event("input")); -// } - -// if (elUtilQRFP) { -// elUtilQRFP.onsubmit = function (evt) { -// evt.preventDefault(); -// } - -// const qrTarget = document.getElementById('qrcode'); -// const qrContext = qrTarget.getContext('2d'); -// const qrOpts = { -// errorCorrectionLevel: 'H', -// margin: 1, -// width: 256, -// height: 256 -// }; - -// const elInput = document.body.querySelector("#input"); - -// elInput.addEventListener("input", async function(evt) { -// if (evt.target.value) { -// QRCode.toCanvas(qrTarget, evt.target.value, qrOpts, function (error) { -// if (error) { -// qrContext.clearRect(0, 0, qrTarget.width, qrTarget.height); -// console.error(error); -// } -// }); -// } else { -// qrContext.clearRect(0, 0, qrTarget.width, qrTarget.height); -// } -// }); - -// elInput.dispatchEvent(new Event("input")); -// } - -// if (elUtilQR) { -// elUtilQR.onsubmit = function (evt) { -// evt.preventDefault(); -// } - -// const qrTarget = document.getElementById('qrcode'); -// const qrContext = qrTarget.getContext('2d'); -// const qrOpts = { -// errorCorrectionLevel: 'L', -// margin: 1, -// width: 256, -// height: 256 -// }; - -// const elInput = document.body.querySelector("#input"); - -// if (elInput.innerText) { -// elInput.innerText = decodeURIComponent(elInput.innerText); - -// 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; -// } -// }); -// } else { -// qrContext.clearRect(0, 0, qrTarget.width, qrTarget.height); -// } -// } - -// 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")); -// } diff --git a/static/scripts.util.js b/static/scripts.util.js new file mode 100644 index 0000000..8a1ed48 --- /dev/null +++ b/static/scripts.util.js @@ -0,0 +1,276 @@ +/* +Copyright (C) 2021 Yarmo Mackenbach + +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 . + +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 . +*/ +async function computeWKDLocalPart(message) { + const data = openpgp.util.str_to_Uint8Array(message.toLowerCase()); + const hash = await openpgp.crypto.hash.sha1(data); + return openpgp.util.encodeZBase32(hash); +} +async function generateProfileURL(data) { + let hostname = window.location.hostname; + + if (data.input == "") { + return "Waiting for input..."; + } + switch (data.source) { + case "wkd": + return `https://${hostname}/${data.input}`; + break; + case "hkp": + if (/.*@.*\..*/.test(data.input)) { + return `https://${hostname}/hkp/${data.input}`; + } else { + return `https://${hostname}/${data.input}`; + } + 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); + return `https://${hostname}/keybase/${match[1]}/${match[2]}`; + break; + } +} + +let elFormSignatureProfile = document.body.querySelector("#formGenerateSignatureProfile"), + 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"), + elUtilQR = document.body.querySelector("#form-util-qr"), + elUtilProfileURL = document.body.querySelector("#form-util-profile-url"); + +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")); +} + +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 + opts = { + input: profileUid, + mode: "wkd" + } + } else { + // Match fingerprint for hkp + opts = { + input: profileUid, + mode: "hkp" + } + } + break; + + case "hkp": + opts = { + input: profileUid, + server: elProfileServer.innerHTML, + mode: elProfileMode.innerHTML + } + break; + + case "wkd": + opts = { + input: profileUid, + mode: elProfileMode.innerHTML + } + break; + + case "keybase": + let match = profileUid.match(/(.*)\/(.*)/); + opts = { + username: match[1], + fingerprint: match[2], + mode: elProfileMode.innerHTML + } + break; + } + + if (elProfileMode.innerHTML !== 'sig') { + keyoxide.displayProfile(opts); + } +} + +if (elUtilWKD) { + elUtilWKD.onsubmit = function (evt) { + evt.preventDefault(); + } + + const elInput = document.body.querySelector("#input"); + const elOutput = document.body.querySelector("#output"); + const elOutputDirect = document.body.querySelector("#output_url_direct"); + const elOutputAdvanced = document.body.querySelector("#output_url_advanced"); + let match; + + elInput.addEventListener("input", async function(evt) { + if (evt.target.value) { + 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"; + } + } else { + elOutput.innerText = "Waiting for input"; + elOutputDirect.innerText = "Waiting for input"; + elOutputAdvanced.innerText = "Waiting for input"; + } + }); + + elInput.dispatchEvent(new Event("input")); +} + +if (elUtilQRFP) { + elUtilQRFP.onsubmit = function (evt) { + evt.preventDefault(); + } + + const qrTarget = document.getElementById('qrcode'); + const qrContext = qrTarget.getContext('2d'); + const qrOpts = { + errorCorrectionLevel: 'H', + margin: 1, + width: 256, + height: 256 + }; + + const elInput = document.body.querySelector("#input"); + + elInput.addEventListener("input", async function(evt) { + if (evt.target.value) { + QRCode.toCanvas(qrTarget, evt.target.value, qrOpts, function (error) { + if (error) { + qrContext.clearRect(0, 0, qrTarget.width, qrTarget.height); + console.error(error); + } + }); + } else { + qrContext.clearRect(0, 0, qrTarget.width, qrTarget.height); + } + }); + + elInput.dispatchEvent(new Event("input")); +} + +if (elUtilQR) { + elUtilQR.onsubmit = function (evt) { + evt.preventDefault(); + } + + const qrTarget = document.getElementById('qrcode'); + const qrContext = qrTarget.getContext('2d'); + const qrOpts = { + errorCorrectionLevel: 'L', + margin: 1, + width: 256, + height: 256 + }; + + const elInput = document.body.querySelector("#input"); + + if (elInput.innerText) { + elInput.innerText = decodeURIComponent(elInput.innerText); + + 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; + } + }); + } else { + qrContext.clearRect(0, 0, qrTarget.width, qrTarget.height); + } +} + +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")); +} diff --git a/views/util/profile-url.pug b/views/util/profile-url.pug index 845e40e..dd54d9d 100644 --- a/views/util/profile-url.pug +++ b/views/util/profile-url.pug @@ -2,6 +2,7 @@ extends ../templates/base.pug block js script(type='application/javascript' src='/static/scripts.js' charset='utf-8') + script(type='application/javascript' src='/static/scripts.util.js' charset='utf-8') block content section.narrow @@ -9,7 +10,7 @@ block content form#form-util-profile-url(method='post') p This tool generates an URL for your Keyoxide profile page. h3 Public key - label(for='source') Source: + label(for='source') Source: select#source.source(name='source') option(value='wkd') Web Key Directory option(value='hkp') keys.openpgp.org diff --git a/views/util/qr.pug b/views/util/qr.pug index 684ba03..bf6b60e 100644 --- a/views/util/qr.pug +++ b/views/util/qr.pug @@ -3,6 +3,7 @@ extends ../templates/base.pug block js script(type='application/javascript' src='/static/qrcode.min.js' charset='utf-8') script(type='application/javascript' src='/static/scripts.js' charset='utf-8') + script(type='application/javascript' src='/static/scripts.util.js' charset='utf-8') block content section.narrow diff --git a/views/util/qrfp.pug b/views/util/qrfp.pug index 2b5b9c5..4047bad 100644 --- a/views/util/qrfp.pug +++ b/views/util/qrfp.pug @@ -3,6 +3,7 @@ extends ../templates/base.pug block js script(type='application/javascript' src='/static/qrcode.min.js' charset='utf-8') script(type='application/javascript' src='/static/scripts.js' charset='utf-8') + script(type='application/javascript' src='/static/scripts.util.js' charset='utf-8') block content section.narrow @@ -11,7 +12,7 @@ block content p | This tool generates a QR code containing the fingerprint of your public key ( a(href='https://github.com/open-keychain/open-keychain/wiki/QR-Codes') format - | ). This QR code can be scanned by apps like + | ). This QR code can be scanned by apps like a(href='https://www.openkeychain.org/') OpenKeyChain | . h3 Fingerprint diff --git a/views/util/wkd.pug b/views/util/wkd.pug index ded13e7..fc04213 100644 --- a/views/util/wkd.pug +++ b/views/util/wkd.pug @@ -3,6 +3,7 @@ extends ../templates/base.pug block js script(type='application/javascript' src='/static/openpgp.min.js' charset='utf-8') script(type='application/javascript' src='/static/scripts.js' charset='utf-8') + script(type='application/javascript' src='/static/scripts.util.js' charset='utf-8') block content section.narrow