diff --git a/src/core/operations/PGPDecryptAndVerify.mjs b/src/core/operations/PGPDecryptAndVerify.mjs
index 58c61c25..21612a0f 100644
--- a/src/core/operations/PGPDecryptAndVerify.mjs
+++ b/src/core/operations/PGPDecryptAndVerify.mjs
@@ -93,7 +93,7 @@ class PGPDecryptAndVerify extends Operation {
text += `${signer.username} `;
}
if (signer.comment) {
- text += `${signer.comment} `;
+ text += `(${signer.comment}) `;
}
if (signer.email) {
text += `<${signer.email}>`;
@@ -101,8 +101,9 @@ class PGPDecryptAndVerify extends Operation {
text += "\n";
}
text += [
+ `PGP key ID: ${km.get_pgp_short_key_id()}`,
`PGP fingerprint: ${km.get_pgp_fingerprint().toString("hex")}`,
- `Signed on ${new Date(ds.sig.hashed_subpackets[0].time * 1000).toUTCString()}`,
+ `Signed on ${new Date(ds.sig.when_generated() * 1000).toUTCString()}`,
"----------------------------------\n"
].join("\n");
text += unboxedLiterals.toString();
diff --git a/src/core/operations/PGPVerify.mjs b/src/core/operations/PGPVerify.mjs
new file mode 100644
index 00000000..ad1173b1
--- /dev/null
+++ b/src/core/operations/PGPVerify.mjs
@@ -0,0 +1,111 @@
+/**
+ * @author Matt C [me@mitt.dev]
+ * @copyright Crown Copyright 2019
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation";
+import OperationError from "../errors/OperationError";
+
+import kbpgp from "kbpgp";
+import { ASP, importPublicKey } from "../lib/PGP";
+import * as es6promisify from "es6-promisify";
+const promisify = es6promisify.default ? es6promisify.default.promisify : es6promisify.promisify;
+
+/**
+ * PGP Verify operation
+ */
+class PGPVerify extends Operation {
+
+ /**
+ * PGPVerify constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "PGP Verify";
+ this.module = "PGP";
+ this.description = [
+ "Input: the ASCII-armoured encrypted PGP message you want to verify.",
+ "
",
+ "Argument: the ASCII-armoured PGP public key of the signer",
+ "
",
+ "This operation uses PGP to decrypt a clearsigned message.",
+ "
",
+ "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.",
+ "
",
+ "This function uses the Keybase implementation of PGP.",
+ ].join("\n");
+ this.infoURL = "https://wikipedia.org/wiki/Pretty_Good_Privacy";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ "name": "Public key of signer",
+ "type": "text",
+ "value": ""
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ async run(input, args) {
+ const signedMessage = input,
+ [publicKey] = args,
+ keyring = new kbpgp.keyring.KeyRing();
+ let unboxedLiterals;
+
+ if (!publicKey) throw new OperationError("Enter the public key of the signer.");
+ const pubKey = await importPublicKey(publicKey);
+ keyring.add_key_manager(pubKey);
+
+ try {
+ unboxedLiterals = await promisify(kbpgp.unbox)({
+ armored: signedMessage,
+ keyfetch: keyring,
+ asp: ASP
+ });
+ const ds = unboxedLiterals[0].get_data_signer();
+ if (ds) {
+ const km = ds.get_key_manager();
+ if (km) {
+ const signer = km.get_userids_mark_primary()[0].components;
+ let text = "Signed by ";
+ if (signer.email || signer.username || signer.comment) {
+ if (signer.username) {
+ text += `${signer.username} `;
+ }
+ if (signer.comment) {
+ text += `(${signer.comment}) `;
+ }
+ if (signer.email) {
+ text += `<${signer.email}>`;
+ }
+ text += "\n";
+ }
+ text += [
+ `PGP key ID: ${km.get_pgp_short_key_id()}`,
+ `PGP fingerprint: ${km.get_pgp_fingerprint().toString("hex")}`,
+ `Signed on ${new Date(ds.sig.when_generated() * 1000).toUTCString()}`,
+ "----------------------------------\n"
+ ].join("\n");
+ text += unboxedLiterals.toString();
+ return text.trim();
+ } else {
+ throw new OperationError("Could not identify a key manager.");
+ }
+ } else {
+ throw new OperationError("The data does not appear to be signed.");
+ }
+ } catch (err) {
+ throw new OperationError(`Couldn't verify message: ${err}`);
+ }
+ }
+
+}
+
+export default PGPVerify;
diff --git a/tests/operations/tests/PGP.mjs b/tests/operations/tests/PGP.mjs
index baf76fb8..8449add4 100644
--- a/tests/operations/tests/PGP.mjs
+++ b/tests/operations/tests/PGP.mjs
@@ -248,7 +248,8 @@ IOE1W/Zqmqzq+4frwnzWwYv9/U1RwIs/qlFVnzliREOzW+om8EncSSd7fQ==
=fEAT
-----END PGP MESSAGE-----
`,
- expectedOutput: `Signed by PGP fingerprint: e94e06dd0b3744a0e970de9d84246548df98e485
+ expectedOutput: `Signed by PGP key ID: DF98E485
+PGP fingerprint: e94e06dd0b3744a0e970de9d84246548df98e485
Signed on Tue, 29 May 2018 15:44:52 GMT
----------------------------------
${UTF8_TEXT}`,
@@ -282,4 +283,30 @@ H2qMY1O7hezH3fp+EZzCAccJMtK7VPk13WAgMRH22HirG4aK1i75IVOtjBgObzDh
}
]
},
+ {
+ name: "PGP Verify: ASCII, Alice",
+ input: `-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA256
+
+A common mistake that people make when trying to design something completely foolproof is to underestimate the ingenuity of complete fools.
+-----BEGIN PGP SIGNATURE-----
+
+iLMEAQEIAB0WIQRLbJy6MLpYOr9qojE+2VNAUiMLOgUCXRTsvwAKCRA+2VNAUiML
+OuaHBADMMNtsuN92Fb+UrDimsv6TDQpbJhDkwp9kZdKYP5HAmSYAhXBG7N+YCMw+
+v2FSpUu9jJiPBm1K1SEwLufQVexoRv6RsBNolRFB07sArau0s0DnIXUchCZWvyTP
+1KsjBnDr84U2b11H58g4DlTT4gQrz30rFuHz9AGmPAtDHbSXIA==
+=vnk/
+-----END PGP SIGNATURE-----`,
+ expectedOutput: `Signed by PGP key ID: DF98E485
+PGP fingerprint: e94e06dd0b3744a0e970de9d84246548df98e485
+Signed on Thu, 27 Jun 2019 16:20:15 GMT
+----------------------------------
+A common mistake that people make when trying to design something completely foolproof is to underestimate the ingenuity of complete fools.`,
+ recipeConfig: [
+ {
+ "op": "PGP Verify",
+ "args": [ALICE_PUBLIC]
+ }
+ ]
+ }
]);