From 60c8da7bbb718d75c3f58eea4bafbae2a510f445 Mon Sep 17 00:00:00 2001 From: tlwr Date: Sat, 25 Nov 2017 16:00:33 +0000 Subject: [PATCH] Add operation "Generate PGP Key Pair" Have not yet found a nice way of working with the kbpgp API as it is very callback heavy. Probably just my rusty javascript. --- package.json | 1 + src/core/config/OperationConfig.js | 34 +++++++ src/core/config/modules/PGP.js | 20 ++++ src/core/operations/PGP.js | 156 +++++++++++++++++++++++++++++ 4 files changed, 211 insertions(+) create mode 100644 src/core/config/modules/PGP.js create mode 100755 src/core/operations/PGP.js diff --git a/package.json b/package.json index 31042928..fa55d305 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "jsbn": "^1.1.0", "jsonpath": "^0.2.12", "jsrsasign": "8.0.4", + "kbpgp": "^2.0.76", "lodash": "^4.17.4", "moment": "^2.18.1", "moment-timezone": "^0.5.13", diff --git a/src/core/config/OperationConfig.js b/src/core/config/OperationConfig.js index 06a3a2d8..020a315e 100755 --- a/src/core/config/OperationConfig.js +++ b/src/core/config/OperationConfig.js @@ -26,6 +26,7 @@ import JS from "../operations/JS.js"; import MAC from "../operations/MAC.js"; import MorseCode from "../operations/MorseCode.js"; import NetBIOS from "../operations/NetBIOS.js"; +import PGP from "../operations/PGP.js"; import PublicKey from "../operations/PublicKey.js"; import Punycode from "../operations/Punycode.js"; import Rotate from "../operations/Rotate.js"; @@ -3845,6 +3846,39 @@ const OperationConfig = { } ] }, + "Generate PGP Key Pair": { + module: "PGP", + description: "", + inputType: "string", + outputType: "string", + args: [ + { + name: "Key type", + type: "option", + value: PGP.KEY_TYPES + }, + { + name: "Key size", + type: "option", + value: PGP.KEY_SIZES + }, + { + name: "Password (optional)", + type: "string", + value: "" + }, + { + name: "Name (optional)", + type: "string", + value: "" + }, + { + name: "Email (optional)", + type: "string", + value: "" + }, + ] + }, }; diff --git a/src/core/config/modules/PGP.js b/src/core/config/modules/PGP.js new file mode 100644 index 00000000..3b163ed4 --- /dev/null +++ b/src/core/config/modules/PGP.js @@ -0,0 +1,20 @@ +import PGP from "../../operations/PGP.js"; + + +/** + * PGP module. + * + * Libraries: + * - kbpgp + * + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ +let OpModules = typeof self === "undefined" ? {} : self.OpModules || {}; + +OpModules.PGP = { + "Generate PGP Key Pair": PGP.runGenerateKeyPair, +}; + +export default OpModules; diff --git a/src/core/operations/PGP.js b/src/core/operations/PGP.js new file mode 100755 index 00000000..321519ee --- /dev/null +++ b/src/core/operations/PGP.js @@ -0,0 +1,156 @@ +import * as kbpgp from "kbpgp"; + +const ECC_SIZES = ["256", "384"]; +const RSA_SIZES = ["1024", "2048", "4096"]; +const KEY_SIZES = RSA_SIZES.concat(ECC_SIZES); +const KEY_TYPES = ["RSA", "ECC"]; + +/** + * PGP operations. + * + * @author tlwr [toby@toby.codes] + * @copyright Crown Copyright 2016 + * @license Apache-2.0 + * + * @namespace + */ +const PGP = { + KEY_SIZES: KEY_SIZES, + + /** + * Validate PGP Key Size + * @param {string} keySize + * @returns {Integer} + */ + validateKeySize(keySize, keyType) { + if (KEY_SIZES.indexOf(keySize) < 0) { + throw `Invalid key size ${keySize}, must be in ${JSON.stringify(KEY_SIZES)}`; + } + + if (keyType === "ecc") { + if (ECC_SIZES.indexOf(keySize) >= 0) { + return parseInt(keySize, 10); + } else { + throw `Invalid key size ${keySize}, must be in ${JSON.stringify(ECC_SIZES)} for ECC`; + } + } else { + if (RSA_SIZES.indexOf(keySize) >= 0) { + return parseInt(keySize, 10); + } else { + throw `Invalid key size ${keySize}, must be in ${JSON.stringify(RSA_SIZES)} for RSA`; + } + } + }, + + /** + * Get size of subkey + * @param {Integer} keySize + * @returns {Integer} + */ + getSubkeySize(keySize) { + return { + 1024: 1024, + 2048: 1024, + 4096: 2048, + 256: 256, + 384: 256, + }[keySize] + }, + + + KEY_TYPES: KEY_TYPES, + + /** + * Validate PGP Key Type + * @param {string} keyType + * @returns {string} + */ + validateKeyType(keyType) { + if (KEY_TYPES.indexOf(keyType) >= 0) return keyType.toLowerCase(); + throw `Invalid key type ${keyType}, must be in ${JSON.stringify(KEY_TYPES)}`; + }, + + /** + * Generate PGP Key Pair operation. + * + * @author tlwr [toby@toby.codes] + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + runGenerateKeyPair(input, args) { + let keyType = args[0], + keySize = args[1], + password = args[2], + name = args[3], + email = args[4]; + + keyType = PGP.validateKeyType(keyType); + keySize = PGP.validateKeySize(keySize, keyType); + + let userIdentifier = ""; + if (name) userIdentifier += name; + if (email) userIdentifier += ` <${email}>`; + + let flags = kbpgp.const.openpgp.certify_keys; + flags = flags | kbpgp.const.openpgp.sign_data; + flags = flags | kbpgp.const.openpgp.auth; + flags = flags | kbpgp.const.openpgp.encrypt_comm; + flags = flags | kbpgp.const.openpgp.encrypt_storage; + + let keyGenerationOptions = { + userid: userIdentifier, + ecc: keyType === "ecc", + primary: { + nbits: keySize, + flags: flags, + expire_in: 0 + }, + subkeys: [{ + nbits: PGP.getSubkeySize(keySize), + flags: kbpgp.const.openpgp.sign_data, + expire_in: 86400 * 365 * 8 // 8 years from kbpgp defaults + }, { + nbits: PGP.getSubkeySize(keySize), + flags: kbpgp.const.openpgp.encrypt_comm | kbpgp.const.openpgp.encrypt_storage, + expire_in: 86400 * 365 * 2 // 2 years from kbpgp defaults + }], + }; + + return new Promise((resolve, reject) => { + kbpgp.KeyManager.generate(keyGenerationOptions, (genErr, unsignedKey) => { + if (genErr) { + return reject(`Error from kbpgp whilst generating key: ${genErr}`); + } + + unsignedKey.sign({}, signErr => { + let signedKey = unsignedKey; + if (signErr) { + return reject(`Error from kbpgp whilst signing the generated key: ${signErr}`); + } + + let privateKeyExportOptions = {}; + if (password) privateKeyExportOptions.passphrase = password; + + signedKey.export_pgp_private(privateKeyExportOptions, (privateExportErr, privateKey) => { + if (privateExportErr) { + return reject(`Error from kbpgp whilst exporting the private part of the signed key: ${privateExportErr}`); + } + + signedKey.export_pgp_public({}, (publicExportErr, publicKey) => { + if (publicExportErr) { + return reject(`Error from kbpgp whilst exporting the public part of the signed key: ${publicExportErr}`); + } + + return resolve(privateKey + "\n" + publicKey); + }); + }); + + }); + }) + }); + }, + +}; + +export default PGP;