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] + } + ] + } ]);