OPSV has been integrated in Keyoxide and is no longer updated https://codeberg.org/yarmo/keyoxide
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

266 lines
11 KiB

<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Open PGP Signature Verification</title>
<script async defer data-domain="opsv.foss.guru" src="https://plausible.io/js/plausible.js"></script>
<script src="openpgp.min.js"></script>
<style media="screen">
* {
box-sizing: border-box;
}
body {
background-color: #777;
}
.container {
max-width: 640px;
width: 100%;
margin: 32px auto;
padding: 16px 32px 32px;
background-color: #fff;
border-radius: 5px;
border: 3px solid #333;
}
footer {
color: #777;
margin: 64px 0 0 0;
}
a {
color: #3f9acc;
}
textarea {
width: 100%;
height: 128px;
resize: vertical;
}
input[type="radio"] {
vertical-align: sub;
}
input[type="submit"] {
width: 100%;
padding: 8px;
font-size: 1.2em;
text-transform: uppercase;
background-color: #c3eaff;
border: 1px solid #3f9acc;
border-radius: 3px;
border-bottom-width: 3px;
cursor: pointer;
}
input[type="submit"]:hover {
background-color: #9dd3f0;
}
.green {
color: green;
}
.red {
color: red;
}
.label {
display: inline-block;
margin: 0 0 8px;
}
</style>
</head>
<body>
<div class="container">
<h1>Open PGP Signature Verification</h1>
<form id="form" method="post">
<h3>Signature</h3>
<textarea name="message" id="message"></textarea>
<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 address / key id / fingerprint">
<h3>Result</h3>
<p id="result">Click on the button below.</p>
<p id="resultContent"></p>
<input type="submit" name="submit" value="VERIFY SIGNATURE">
</form>
<footer>
<p>
<a href="/">OPSV</a> is a FOSS solution for easy PGP signature verification (<a href="https://codeberg.org/yarmo/opsv/src/branch/master/README.md">how to use</a>).
<br>
Made by <a href="https://yarmo.eu">Yarmo Mackenbach</a>.
<br>
Code hosted on <a href="https://codeberg.org/yarmo/opsv">Codeberg</a> (<a href="https://drone.private.foss.best/yarmo/opsv/">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/opsv.foss.guru">Plausible.io</a>.
</p>
</footer>
</div>
</body>
<script type="text/javascript">
async function verifySignature(opts) {
const elRes = document.body.querySelector("#result");
const elResContent = document.body.querySelector("#resultContent");
let feedback, message, verified, publicKey, fp, lookupOpts, wkd, hkp, sig, userId, keyId, sigContent;
try {
switch (opts.mode) {
case "plaintext":
publicKey = (await openpgp.key.readArmored(opts.input)).keys;
break;
case "wkd":
wkd = new openpgp.WKD();
lookupOpts = {
email: opts.input
};
publicKey = (await wkd.lookup(lookupOpts)).keys;
break;
case "hkp":
if (!opts.server) {opts.server = "https://keys.openpgp.org/"};
hkp = new openpgp.HKP(opts.server);
lookupOpts = {
query: opts.input
};
publicKey = await hkp.lookup(lookupOpts);
publicKey = (await openpgp.key.readArmored(publicKey)).keys;
break;
default:
sig = (await openpgp.signature.readArmored(opts.message));
if ('compressed' in sig.packets[0]) {
sig = sig.packets[0];
sigContent = (await openpgp.stream.readToEnd(await sig.packets[1].getText()));
};
keyId = (await sig.packets[0].issuerKeyId.toHex());
userId = sig.packets[0].signersUserId;
if (!keyId && !userId) {
elRes.innerHTML = "The signature does not contain a valid keyId or userId.";
elRes.classList.remove('green');
elRes.classList.add('red');
return;
}
if (!opts.server) {opts.server = "https://keys.openpgp.org/"};
hkp = new openpgp.HKP(opts.server);
lookupOpts = {
query: userId ? userId : keyId
};
publicKey = await hkp.lookup(lookupOpts);
publicKey = (await openpgp.key.readArmored(publicKey)).keys;
break;
}
if (opts.message == null) {
elRes.innerHTML = "No message was provided.";
elRes.classList.remove('green');
elRes.classList.add('red');
return;
}
let readError = null;
try {
message = await openpgp.message.readArmored(opts.message);
} catch(e) {
readError = e;
}
try {
message = await openpgp.cleartext.readArmored(opts.message);
} catch(e) {
readError = e;
}
if (message == null) {throw(readError)};
fp = publicKey[0].getFingerprint();
verified = await openpgp.verify({
message: message,
publicKeys: publicKey
});
} catch (e) {
console.error(e);
elRes.innerHTML = e;
elRes.classList.remove('green');
elRes.classList.add('red');
return;
}
feedback = '';
const valid = verified.signatures[0];
if (sigContent) {
elResContent.innerHTML = "<strong>Signature content:</strong><br><span style=\"white-space: pre-line\">"+sigContent+"</span>";
}
if (userId) {
if (valid) {
feedback += "The message was signed by the userId extracted from the signature.<br>";
feedback += 'UserId: '+userId+'<br>';
feedback += "Fingerprint: "+fp+"<br>";
elRes.classList.remove('red');
elRes.classList.add('green');
} else {
feedback += "The message's signature COULD NOT BE verified using the userId extracted from the signature.<br>";
elRes.classList.remove('green');
elRes.classList.add('red');
}
} else if (keyId) {
if (valid) {
feedback += "The message was signed by the keyId extracted from the signature.<br>";
feedback += 'KeyID: '+keyId+'<br>';
feedback += "Fingerprint: "+fp+"<br><br>";
feedback += "!!! You should manually verify the fingerprint to confirm the signer's identity !!!";
elRes.classList.remove('red');
elRes.classList.add('green');
} else {
feedback += "The message's signature COULD NOT BE verified using the keyId extracted from the signature.<br>";
elRes.classList.remove('green');
elRes.classList.add('red');
}
} else {
if (valid) {
feedback += "The message was signed by the provided key ("+opts.mode+").<br>";
feedback += "Fingerprint: "+fp+"<br>";
elRes.classList.remove('red');
elRes.classList.add('green');
} else {
feedback += "The message's signature COULD NOT BE verified using the provided key ("+opts.mode+").<br>";
elRes.classList.remove('green');
elRes.classList.add('red');
}
}
elRes.innerHTML = feedback;
};
document.body.querySelector("#form").onsubmit = function (evt) {
evt.preventDefault();
let opts = {
message: null,
mode: null,
input: null,
server: null,
};
opts.message = document.body.querySelector("#message").value;
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";
}
verifySignature(opts);
};
</script>
</html>