From 7554cbda728205d8fdaca9cc6029ca407a66640b Mon Sep 17 00:00:00 2001 From: Matt C Date: Fri, 12 Jan 2018 16:52:15 +0000 Subject: [PATCH] Added PGP Sign/Verify operations --- src/core/config/OperationConfig.js | 96 ++++++++++++++- src/core/config/modules/PGP.js | 3 + src/core/operations/PGP.js | 190 +++++++++++++++++++++++++---- 3 files changed, 257 insertions(+), 32 deletions(-) diff --git a/src/core/config/OperationConfig.js b/src/core/config/OperationConfig.js index 9d51ff52..779f91cf 100644 --- a/src/core/config/OperationConfig.js +++ b/src/core/config/OperationConfig.js @@ -3951,12 +3951,20 @@ const OperationConfig = { "PGP Encrypt": { module: "PGP", manualBake: true, - description: "", + description: [ + "Input: the message you want to encrypt.", + "

", + "Arguments: the ASCII-armoured PGP public key of the recipient.", + "

", + "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.", + "

", + "This function relies on kbpgp.js for the implementation of PGP.", + ].join("\n"), inputType: "string", outputType: "string", args: [ { - name: "Public key", + name: "Public key of recipient", type: "text", value: "" }, @@ -3965,20 +3973,98 @@ const OperationConfig = { "PGP Decrypt": { module: "PGP", manualBake: true, - description: "", + description: [ + "Input: the ASCII-armoured PGP message you want to decrypt.", + "

", + "Arguments: the ASCII-armoured PGP private key of the recipient, ", + "(and the private key password if necessary).", + "

", + "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.", + "

", + "This function relies on kbpgp.js for the implementation of PGP.", + ].join("\n"), inputType: "string", outputType: "string", args: [ { - name: "Private key", + name: "Private key of recipient", type: "text", value: "" }, { - name: "Passphrase", + name: "Private key passphrase", + type: "string", + value: "" + }, + ] + }, + "PGP Sign": { + module: "PGP", + manualBake: true, + description: [ + "Input: the cleartext you want to sign.", + "

", + "Arguments: the ASCII-armoured private key of the signer (plus the private key password if necessary)", + "and the ASCII-armoured PGP public key of the recipient.", + "

", + "This operation uses PGP to produce an encrypted digital signature.", + "

", + "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.", + "

", + "This function relies on kbpgp.js for the implementation of PGP.", + ].join("\n"), + inputType: "string", + outputType: "string", + args: [ + { + name: "Private key of signer", type: "text", value: "" }, + { + name: "Private key passphrase", + type: "string", + value: "" + }, + { + name: "Public key of recipient", + type: "text", + value: "" + }, + ] + }, + "PGP Verify": { + module: "PGP", + description: [ + "Input: the ASCII-armoured encrypted PGP message you want to verify.", + "

", + "Arguments: the ASCII-armoured PGP public key of the signer, ", + "the ASCII-armoured private key of the recipient (and the private key password if necessary).", + "

", + "This operation uses PGP to decrypt and verify an encrypted digital signature.", + "

", + "Pretty Good Privacy is an encryption standard (OpenPGP) used for encrypting, decrypting, and signing messages.", + "

", + "This function relies on kbpgp.js for the implementation of PGP.", + ].join("\n"), + inputType: "string", + outputType: "string", + args: [ + { + name: "Public key of signer", + type: "text", + value: "", + }, + { + name: "Private key of recipient", + type: "text", + value: "", + }, + { + name: "Private key password", + type: "string", + value: "", + }, ] }, }; diff --git a/src/core/config/modules/PGP.js b/src/core/config/modules/PGP.js index 1e74b73a..702141d3 100644 --- a/src/core/config/modules/PGP.js +++ b/src/core/config/modules/PGP.js @@ -8,6 +8,7 @@ import PGP from "../../operations/PGP.js"; * - kbpgp * * @author tlwr [toby@toby.codes] + * @author Matt C [matt@artemisbot.uk] * @copyright Crown Copyright 2017 * @license Apache-2.0 */ @@ -17,6 +18,8 @@ OpModules.PGP = { "Generate PGP Key Pair": PGP.runGenerateKeyPair, "PGP Encrypt": PGP.runEncrypt, "PGP Decrypt": PGP.runDecrypt, + "PGP Sign": PGP.runSign, + "PGP Verify": PGP.runVerify, }; export default OpModules; diff --git a/src/core/operations/PGP.js b/src/core/operations/PGP.js index dd8acf09..87ee53d9 100755 --- a/src/core/operations/PGP.js +++ b/src/core/operations/PGP.js @@ -11,6 +11,7 @@ const KEY_TYPES = ["RSA", "ECC"]; * PGP operations. * * @author tlwr [toby@toby.codes] + * @author Matt C [matt@artemisbot.uk] * @copyright Crown Copyright 2016 * @license Apache-2.0 * @@ -21,10 +22,12 @@ const PGP = { /** * Validate PGP Key Size + * + * @private * @param {string} keySize * @returns {Integer} */ - validateKeySize(keySize, keyType) { + _validateKeySize(keySize, keyType) { if (KEY_SIZES.indexOf(keySize) < 0) { throw `Invalid key size ${keySize}, must be in ${JSON.stringify(KEY_SIZES)}`; } @@ -46,10 +49,12 @@ const PGP = { /** * Get size of subkey + * + * @private * @param {Integer} keySize * @returns {Integer} */ - getSubkeySize(keySize) { + _getSubkeySize(keySize) { return { 1024: 1024, 2048: 1024, @@ -64,18 +69,65 @@ const PGP = { /** * Validate PGP Key Type + * + * @private * @param {string} keyType * @returns {string} */ - validateKeyType(keyType) { + _validateKeyType(keyType) { if (KEY_TYPES.indexOf(keyType) >= 0) return keyType.toLowerCase(); throw `Invalid key type ${keyType}, must be in ${JSON.stringify(KEY_TYPES)}`; }, + /** + * Import private key and unlock if necessary + * + * @private + * @param {string} privateKey + * @param {string} [passphrase] + * @returns {Object} + */ + async _importPrivateKey (privateKey, passphrase) { + try { + const key = await promisify(kbpgp.KeyManager.import_from_armored_pgp)({ + armored: privateKey, + }); + if (key.is_pgp_locked() && passphrase) { + if (passphrase) { + await promisify(key.unlock_pgp, key)({ + passphrase + }); + } else if (!passphrase) { + throw "Did not provide passphrase with locked private key."; + } + } + return key; + } catch (err) { + throw `Could not import private key: ${err}`; + } + }, + + /** + * Import public key + * + * @private + * @param {string} publicKey + * @returns {Object} + */ + async _importPublicKey (publicKey) { + try { + const key = await promisify(kbpgp.KeyManager.import_from_armored_pgp)({ + armored: publicKey, + }); + return key; + } catch (err) { + throw `Could not import public key: ${err}`; + } + }, + /** * Generate PGP Key Pair operation. * - * @author tlwr [toby@toby.codes] * @param {string} input * @param {Object[]} args * @returns {string} @@ -87,8 +139,8 @@ const PGP = { name = args[3], email = args[4]; - keyType = PGP.validateKeyType(keyType); - keySize = PGP.validateKeySize(keySize, keyType); + keyType = PGP._validateKeyType(keyType); + keySize = PGP._validateKeySize(keySize, keyType); let userIdentifier = ""; if (name) userIdentifier += name; @@ -109,11 +161,11 @@ const PGP = { expire_in: 0 }, subkeys: [{ - nbits: PGP.getSubkeySize(keySize), + nbits: PGP._getSubkeySize(keySize), flags: kbpgp.const.openpgp.sign_data, expire_in: 86400 * 365 * 8 }, { - nbits: PGP.getSubkeySize(keySize), + nbits: PGP._getSubkeySize(keySize), flags: kbpgp.const.openpgp.encrypt_comm | kbpgp.const.openpgp.encrypt_storage, expire_in: 86400 * 365 * 2 }], @@ -134,6 +186,13 @@ const PGP = { }); }, + /** + * PGP Encrypt operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ async runEncrypt(input, args) { let plaintextMessage = input, plainPubKey = args[0]; @@ -160,31 +219,21 @@ const PGP = { return encryptedMessage.toString(); }, + /** + * PGP Decrypt operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ async runDecrypt(input, args) { let encryptedMessage = input, privateKey = args[0], passphrase = args[1], keyring = new kbpgp.keyring.KeyRing(); - let key, plaintextMessage; - - try { - key = await promisify(kbpgp.KeyManager.import_from_armored_pgp)({ - armored: privateKey, - }); - if (key.is_pgp_locked() && passphrase) { - if (passphrase) { - await promisify(key.unlock_pgp, key)({ - passphrase - }); - } else if (!passphrase) { - throw "Did not provide passphrase with locked private key."; - } - } - } catch (err) { - throw `Could not import private key: ${err}`; - } - + let plaintextMessage; + const key = await PGP._importPrivateKey(privateKey, passphrase); keyring.add_key_manager(key); try { @@ -198,6 +247,93 @@ const PGP = { return plaintextMessage.toString(); }, + + /** + * PGP Sign Message operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async runSign(input, args) { + let message = input, + privateKey = args[0], + passphrase = args[1], + publicKey = args[2]; + + let signedMessage; + const privKey = await PGP._importPrivateKey(privateKey, passphrase); + const pubKey = await PGP._importPublicKey(publicKey); + + try { + signedMessage = await promisify(kbpgp.box)({ + msg: message, + encrypt_for: pubKey, + sign_with: privKey + }); + } catch (err) { + throw `Couldn't sign message: ${err}`; + } + + return signedMessage; + }, + + /** + * PGP Verify Message operation. + * + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async runVerify(input, args) { + let signedMessage = input, + publicKey = args[0], + privateKey = args[1], + passphrase = args[2], + keyring = new kbpgp.keyring.KeyRing(); + + let unboxedLiterals; + const privKey = await PGP._importPrivateKey(privateKey, passphrase); + const pubKey = await PGP._importPublicKey(publicKey); + keyring.add_key_manager(privKey); + keyring.add_key_manager(pubKey); + + try { + unboxedLiterals = await promisify(kbpgp.unbox)({ + armored: signedMessage, + keyfetch: keyring + }); + 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 fingerprint: ${km.get_pgp_fingerprint().toString("hex")}`, + `Signed on ${new Date(ds.sig.hashed_subpackets[0].time * 1000).toUTCString()}`, + "----------------------------------" + ].join("\n"); + text += unboxedLiterals.toString(); + return text.trim(); + } + } + } catch (err) { + throw `Couldn't verify message: ${err}`; + } + }, }; export default PGP;