From f81ca3ba605687ccb1b2b76d700a03f7dc4d370f Mon Sep 17 00:00:00 2001 From: GCHQ 77703 Date: Thu, 30 Aug 2018 22:38:01 +0100 Subject: [PATCH 01/11] Implement RSA generation and signing of messages --- src/core/config/Categories.json | 2 + src/core/operations/GenerateRSAKeyPair.mjs | 82 ++++++++++++++++++++++ src/core/operations/RSASign.mjs | 60 ++++++++++++++++ 3 files changed, 144 insertions(+) create mode 100644 src/core/operations/GenerateRSAKeyPair.mjs create mode 100644 src/core/operations/RSASign.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index ab3bc486..68e17294 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -89,6 +89,8 @@ "Derive EVP key", "Bcrypt", "Scrypt", + "Generate RSA Key Pair", + "RSA Sign", "Pseudo-Random Number Generator" ] }, diff --git a/src/core/operations/GenerateRSAKeyPair.mjs b/src/core/operations/GenerateRSAKeyPair.mjs new file mode 100644 index 00000000..9d7a88d8 --- /dev/null +++ b/src/core/operations/GenerateRSAKeyPair.mjs @@ -0,0 +1,82 @@ +/** + * @author gchq77703 [] + * @copyright Crown Copyright 2018 + ` * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import forge from "node-forge/dist/forge.min.js"; + +/** + * Generate RSA Key Pair operation + */ +class GenerateRSAKeyPair extends Operation { + + /** + * GenerateRSAKeyPair constructor + */ + constructor() { + super(); + + this.name = "Generate RSA Key Pair"; + this.module = "Ciphers"; + this.description = "Generate an RSA key pair with a given number of bits"; + this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "RSA Key Length", + type: "option", + value: [ + "1024", + "2048", + "4096" + ] + }, + { + name: "Output Format", + type: "option", + value: [ + "PEM", + "JSON", + "DER" + ] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [keyLength, outputFormat] = args + + return new Promise((resolve, reject) => { + forge.pki.rsa.generateKeyPair({ bits: Number(keyLength), workers: 2 }, (err, keypair) => { + if (err) return reject(err) + + let result; + + switch(outputFormat) { + case "PEM": + result = forge.pki.publicKeyToPem(keypair.publicKey) + "\n" + forge.pki.privateKeyToPem(keypair.privateKey); + break; + case "JSON": + result = JSON.stringify(keypair); + break; + case "DER": + result = forge.asn1.toDer(forge.pki.privateKeyToAsn1(keypair.privateKey)).getBytes(); + break; + }; + + resolve(result); + }) + }) + } + +} + +export default GenerateRSAKeyPair; diff --git a/src/core/operations/RSASign.mjs b/src/core/operations/RSASign.mjs new file mode 100644 index 00000000..fb3e4bbd --- /dev/null +++ b/src/core/operations/RSASign.mjs @@ -0,0 +1,60 @@ +/** + * @author gchq77703 [] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import forge from "node-forge/dist/forge.min.js"; + +/** + * RSA Sign operation + */ +class RSASign extends Operation { + + /** + * RSASign constructor + */ + constructor() { + super(); + + this.name = "RSA Sign"; + this.module = "Ciphers"; + this.description = "Sign a plaintext message with a PEM encoded RSA key."; + this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + name: "RSA Private Key (PEM)", + type: "text", + value: "-----BEGIN RSA PRIVATE KEY-----" + }, + { + name: "Password", + type: "text", + value: "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [key, password] = args; + + const privateKey = forge.pki.decryptRsaPrivateKey(key, password); + + const md = forge.md.sha1.create(); + md.update(input, 'utf8'); + const signature = privateKey.sign(md); + + return signature.split('').map(char => char.charCodeAt()); + } + +} + +export default RSASign; From 31e758ca45667125feb52707762ead91d7969a08 Mon Sep 17 00:00:00 2001 From: Matt C Date: Fri, 31 Aug 2018 11:25:05 +0100 Subject: [PATCH 02/11] Attempt to make RSA key generation functional --- package-lock.json | 6 ++++++ package.json | 1 + src/core/operations/GenerateRSAKeyPair.mjs | 19 ++++++++++++------- src/core/operations/RSASign.mjs | 4 ++-- webpack.config.js | 4 ++++ 5 files changed, 25 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index d9814093..e7ce7f62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8573,6 +8573,12 @@ } } }, + "raw-loader": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-0.5.1.tgz", + "integrity": "sha1-DD0L6u2KAclm2Xh793goElKpeao=", + "dev": true + }, "rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", diff --git a/package.json b/package.json index 5ff508f6..6805c522 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "postcss-import": "^12.0.0", "postcss-loader": "^2.1.6", "prompt": "^1.0.0", + "raw-loader": "^0.5.1", "sass-loader": "^7.1.0", "sitemap": "^1.13.0", "style-loader": "^0.21.0", diff --git a/src/core/operations/GenerateRSAKeyPair.mjs b/src/core/operations/GenerateRSAKeyPair.mjs index 9d7a88d8..64ff29d2 100644 --- a/src/core/operations/GenerateRSAKeyPair.mjs +++ b/src/core/operations/GenerateRSAKeyPair.mjs @@ -6,6 +6,7 @@ import Operation from "../Operation"; import forge from "node-forge/dist/forge.min.js"; +import PrimeWorker from "node-forge/dist/prime.worker.min.js"; /** * Generate RSA Key Pair operation @@ -52,15 +53,19 @@ class GenerateRSAKeyPair extends Operation { * @returns {string} */ async run(input, args) { - const [keyLength, outputFormat] = args + const [keyLength, outputFormat] = args; + let workerScript; return new Promise((resolve, reject) => { - forge.pki.rsa.generateKeyPair({ bits: Number(keyLength), workers: 2 }, (err, keypair) => { - if (err) return reject(err) + if (ENVIRONMENT_IS_WORKER || window) { + workerScript = ENVIRONMENT_IS_WORKER() ? self.URL.createObjectURL(new Blob([PrimeWorker])) : window.URL.createObjectURL(new Blob([PrimeWorker])); + } + forge.pki.rsa.generateKeyPair({ bits: Number(keyLength), workers: 2, workerScript}, (err, keypair) => { + if (err) return reject(err); let result; - switch(outputFormat) { + switch (outputFormat) { case "PEM": result = forge.pki.publicKeyToPem(keypair.publicKey) + "\n" + forge.pki.privateKeyToPem(keypair.privateKey); break; @@ -70,11 +75,11 @@ class GenerateRSAKeyPair extends Operation { case "DER": result = forge.asn1.toDer(forge.pki.privateKeyToAsn1(keypair.privateKey)).getBytes(); break; - }; + } resolve(result); - }) - }) + }); + }); } } diff --git a/src/core/operations/RSASign.mjs b/src/core/operations/RSASign.mjs index fb3e4bbd..c0b4ccdf 100644 --- a/src/core/operations/RSASign.mjs +++ b/src/core/operations/RSASign.mjs @@ -49,10 +49,10 @@ class RSASign extends Operation { const privateKey = forge.pki.decryptRsaPrivateKey(key, password); const md = forge.md.sha1.create(); - md.update(input, 'utf8'); + md.update(input, "utf8"); const signature = privateKey.sign(md); - return signature.split('').map(char => char.charCodeAt()); + return signature.split("").map(char => char.charCodeAt()); } } diff --git a/webpack.config.js b/webpack.config.js index 89a36f69..1a4f5a2d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -61,6 +61,10 @@ module.exports = { test: /forge.min.js$/, loader: "imports-loader?jQuery=>null" }, + { + test: /prime.worker.min.js$/, + use: "raw-loader" + }, { test: /bootstrap-material-design/, loader: "imports-loader?Popper=popper.js/dist/umd/popper.js" From 4d7988b78ebe0d8ab3238c3f30efb673e80ca4fb Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 30 Sep 2019 13:12:10 +0100 Subject: [PATCH 03/11] Fixed RSA key generation --- src/core/operations/GenerateRSAKeyPair.mjs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/core/operations/GenerateRSAKeyPair.mjs b/src/core/operations/GenerateRSAKeyPair.mjs index 64ff29d2..951c2667 100644 --- a/src/core/operations/GenerateRSAKeyPair.mjs +++ b/src/core/operations/GenerateRSAKeyPair.mjs @@ -1,12 +1,11 @@ /** * @author gchq77703 [] * @copyright Crown Copyright 2018 - ` * @license Apache-2.0 + * @license Apache-2.0 */ import Operation from "../Operation"; import forge from "node-forge/dist/forge.min.js"; -import PrimeWorker from "node-forge/dist/prime.worker.min.js"; /** * Generate RSA Key Pair operation @@ -54,13 +53,9 @@ class GenerateRSAKeyPair extends Operation { */ async run(input, args) { const [keyLength, outputFormat] = args; - let workerScript; return new Promise((resolve, reject) => { - if (ENVIRONMENT_IS_WORKER || window) { - workerScript = ENVIRONMENT_IS_WORKER() ? self.URL.createObjectURL(new Blob([PrimeWorker])) : window.URL.createObjectURL(new Blob([PrimeWorker])); - } - forge.pki.rsa.generateKeyPair({ bits: Number(keyLength), workers: 2, workerScript}, (err, keypair) => { + forge.pki.rsa.generateKeyPair({ bits: Number(keyLength), workers: -1}, (err, keypair) => { if (err) return reject(err); let result; From e0f000b913d2c092d7744e2f4f52476ce11056b6 Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 6 Apr 2020 13:35:14 +0100 Subject: [PATCH 04/11] Fixed RSA generation and added digest option to verify --- src/core/lib/RSA.mjs | 9 +++++++++ src/core/operations/GenerateRSAKeyPair.mjs | 3 ++- src/core/operations/RSASign.mjs | 16 +++++++++++----- webpack.config.js | 5 +++++ 4 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 src/core/lib/RSA.mjs diff --git a/src/core/lib/RSA.mjs b/src/core/lib/RSA.mjs new file mode 100644 index 00000000..0dab67a3 --- /dev/null +++ b/src/core/lib/RSA.mjs @@ -0,0 +1,9 @@ +import forge from "node-forge/dist/forge.min.js"; + +export const MD_ALGORITHMS = { + "SHA-1": forge.md.sha1, + "MD5": forge.md.md5, + "SHA-256": forge.md.sha256, + "SHA-384": forge.md.sha384, + "SHA-512": forge.md.sha512, +}; diff --git a/src/core/operations/GenerateRSAKeyPair.mjs b/src/core/operations/GenerateRSAKeyPair.mjs index 951c2667..a96948f2 100644 --- a/src/core/operations/GenerateRSAKeyPair.mjs +++ b/src/core/operations/GenerateRSAKeyPair.mjs @@ -1,4 +1,5 @@ /** + * @author Matt C [me@mitt.dev] * @author gchq77703 [] * @copyright Crown Copyright 2018 * @license Apache-2.0 @@ -55,7 +56,7 @@ class GenerateRSAKeyPair extends Operation { const [keyLength, outputFormat] = args; return new Promise((resolve, reject) => { - forge.pki.rsa.generateKeyPair({ bits: Number(keyLength), workers: -1}, (err, keypair) => { + forge.pki.rsa.generateKeyPair({ bits: Number(keyLength), workers: -1, workerScript: "./assets/forge/prime.worker.min.js"}, (err, keypair) => { if (err) return reject(err); let result; diff --git a/src/core/operations/RSASign.mjs b/src/core/operations/RSASign.mjs index c0b4ccdf..980d59b3 100644 --- a/src/core/operations/RSASign.mjs +++ b/src/core/operations/RSASign.mjs @@ -1,11 +1,13 @@ /** + * @author Matt C [me@mitt.dev] * @author gchq77703 [] - * @copyright Crown Copyright 2018 + * @copyright Crown Copyright 2020 * @license Apache-2.0 */ import Operation from "../Operation"; import forge from "node-forge/dist/forge.min.js"; +import { MD_ALGORITHMS } from "../lib/RSA.mjs"; /** * RSA Sign operation @@ -31,9 +33,14 @@ class RSASign extends Operation { value: "-----BEGIN RSA PRIVATE KEY-----" }, { - name: "Password", + name: "Key Password", type: "text", value: "" + }, + { + name: "Message Digest Algorithm", + type: "option", + value: Object.keys(MD_ALGORITHMS) } ]; } @@ -44,11 +51,10 @@ class RSASign extends Operation { * @returns {string} */ run(input, args) { - const [key, password] = args; + const [key, password, mdAlgo] = args; const privateKey = forge.pki.decryptRsaPrivateKey(key, password); - - const md = forge.md.sha1.create(); + const md = MD_ALGORITHMS[mdAlgo].create(); md.update(input, "utf8"); const signature = privateKey.sign(md); diff --git a/webpack.config.js b/webpack.config.js index a14d8e7e..c4ba416c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -56,6 +56,11 @@ module.exports = { context: "src/core/vendor/", from: "tesseract/**/*", to: "assets/" + }, + { + context: "node_modules/node-forge/dist", + from: "prime.worker.min.js", + to: "assets/forge/" } ]) ], From 2233b9a0949f50bdd974a2dcb4d76a54f9605d75 Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 6 Apr 2020 15:24:06 +0100 Subject: [PATCH 05/11] Comment and add error handling to generate and sign --- src/core/operations/GenerateRSAKeyPair.mjs | 2 +- src/core/operations/RSASign.mjs | 23 +++++++++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/core/operations/GenerateRSAKeyPair.mjs b/src/core/operations/GenerateRSAKeyPair.mjs index a96948f2..57cfcdd0 100644 --- a/src/core/operations/GenerateRSAKeyPair.mjs +++ b/src/core/operations/GenerateRSAKeyPair.mjs @@ -56,7 +56,7 @@ class GenerateRSAKeyPair extends Operation { const [keyLength, outputFormat] = args; return new Promise((resolve, reject) => { - forge.pki.rsa.generateKeyPair({ bits: Number(keyLength), workers: -1, workerScript: "./assets/forge/prime.worker.min.js"}, (err, keypair) => { + forge.pki.rsa.generateKeyPair({ bits: Number(keyLength), workers: -1, workerScript: "assets/forge/prime.worker.min.js"}, (err, keypair) => { if (err) return reject(err); let result; diff --git a/src/core/operations/RSASign.mjs b/src/core/operations/RSASign.mjs index 980d59b3..812de813 100644 --- a/src/core/operations/RSASign.mjs +++ b/src/core/operations/RSASign.mjs @@ -6,6 +6,7 @@ */ import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; import forge from "node-forge/dist/forge.min.js"; import { MD_ALGORITHMS } from "../lib/RSA.mjs"; @@ -52,13 +53,21 @@ class RSASign extends Operation { */ run(input, args) { const [key, password, mdAlgo] = args; - - const privateKey = forge.pki.decryptRsaPrivateKey(key, password); - const md = MD_ALGORITHMS[mdAlgo].create(); - md.update(input, "utf8"); - const signature = privateKey.sign(md); - - return signature.split("").map(char => char.charCodeAt()); + if (key.replace("-----BEGIN RSA PRIVATE KEY-----", "").length === 0) { + throw new OperationError("Please enter a private key."); + } + try { + const privateKey = forge.pki.decryptRsaPrivateKey(key, password); + // Generate message hash + const md = MD_ALGORITHMS[mdAlgo].create(); + md.update(input, "utf8"); + // Convert signature UTF-16 string to byteArray + const encoder = new TextEncoder(); + const signature = encoder.encode(privateKey.sign(md)); + return signature; + } catch (err) { + throw new OperationError(err); + } } } From 18c6b9bc09e07ea2dba33b98d556a7902b6bd4dd Mon Sep 17 00:00:00 2001 From: Matt Date: Mon, 6 Apr 2020 15:24:22 +0100 Subject: [PATCH 06/11] Add RSA Verify operation --- src/core/operations/RSAVerify.mjs | 77 +++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/core/operations/RSAVerify.mjs diff --git a/src/core/operations/RSAVerify.mjs b/src/core/operations/RSAVerify.mjs new file mode 100644 index 00000000..2041e341 --- /dev/null +++ b/src/core/operations/RSAVerify.mjs @@ -0,0 +1,77 @@ +/** + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import forge from "node-forge/dist/forge.min.js"; +import { MD_ALGORITHMS } from "../lib/RSA.mjs"; + +/** + * RSA Verify operation + */ +class RSAVerify extends Operation { + + /** + * RSAVerify constructor + */ + constructor() { + super(); + + this.name = "RSA Verify"; + this.module = "Ciphers"; + this.description = "Verify a message against a signature and a public PEM encoded RSA key."; + this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)"; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + name: "RSA Public Key (PEM)", + type: "text", + value: "-----BEGIN RSA PUBLIC KEY-----" + }, + { + name: "Message", + type: "text", + value: "" + }, + { + name: "Message Digest Algorithm", + type: "option", + value: Object.keys(MD_ALGORITHMS) + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [pemKey, message, mdAlgo] = args; + if (pemKey.replace("-----BEGIN RSA PUBLIC KEY-----", "").length === 0) { + throw new OperationError("Please enter a public key."); + } + try { + // Load public key + const pubKey = forge.pki.publicKeyFromPem(pemKey); + // Generate message digest + const md = MD_ALGORITHMS[mdAlgo].create(); + md.update(message, "utf8"); + // Compare signed message digest and generated message digest + const result = pubKey.verify(md.digest().bytes(), input); + return result ? "Verified OK" : "Verification Failure"; + } catch (err) { + if (err.message === "Encrypted message length is invalid.") { + throw new OperationError(`Signature length (${err.length}) does not match expected length based on key (${err.expected}).`); + } + throw new OperationError(err); + } + } + +} + +export default RSAVerify; From 1c0ecd29c24b85bf2f37cfb097ddb46f5a95fc69 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 7 Apr 2020 11:45:54 +0100 Subject: [PATCH 07/11] Fix RSA operations --- src/core/operations/RSASign.mjs | 10 +++++----- src/core/operations/RSAVerify.mjs | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/core/operations/RSASign.mjs b/src/core/operations/RSASign.mjs index 812de813..1c2696e3 100644 --- a/src/core/operations/RSASign.mjs +++ b/src/core/operations/RSASign.mjs @@ -8,6 +8,7 @@ import Operation from "../Operation"; import OperationError from "../errors/OperationError"; import forge from "node-forge/dist/forge.min.js"; +import Utils from "../Utils.mjs"; import { MD_ALGORITHMS } from "../lib/RSA.mjs"; /** @@ -26,7 +27,7 @@ class RSASign extends Operation { this.description = "Sign a plaintext message with a PEM encoded RSA key."; this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)"; this.inputType = "string"; - this.outputType = "byteArray"; + this.outputType = "string"; this.args = [ { name: "RSA Private Key (PEM)", @@ -61,10 +62,9 @@ class RSASign extends Operation { // Generate message hash const md = MD_ALGORITHMS[mdAlgo].create(); md.update(input, "utf8"); - // Convert signature UTF-16 string to byteArray - const encoder = new TextEncoder(); - const signature = encoder.encode(privateKey.sign(md)); - return signature; + // Sign message hash + const sig = privateKey.sign(md); + return sig; } catch (err) { throw new OperationError(err); } diff --git a/src/core/operations/RSAVerify.mjs b/src/core/operations/RSAVerify.mjs index 2041e341..94ca4220 100644 --- a/src/core/operations/RSAVerify.mjs +++ b/src/core/operations/RSAVerify.mjs @@ -6,6 +6,7 @@ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; import forge from "node-forge/dist/forge.min.js"; import { MD_ALGORITHMS } from "../lib/RSA.mjs"; @@ -24,7 +25,7 @@ class RSAVerify extends Operation { this.module = "Ciphers"; this.description = "Verify a message against a signature and a public PEM encoded RSA key."; this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)"; - this.inputType = "byteArray"; + this.inputType = "string"; this.outputType = "string"; this.args = [ { From e7b5c0e37c96f449efd7d1021bc8bd8e967c83ec Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 7 Apr 2020 13:31:17 +0100 Subject: [PATCH 08/11] Add RSA Encrypt Operation --- src/core/operations/RSAEncrypt.mjs | 84 ++++++++++++++++++++++++++++++ src/core/operations/RSASign.mjs | 1 - src/core/operations/RSAVerify.mjs | 3 +- 3 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 src/core/operations/RSAEncrypt.mjs diff --git a/src/core/operations/RSAEncrypt.mjs b/src/core/operations/RSAEncrypt.mjs new file mode 100644 index 00000000..b7d5026b --- /dev/null +++ b/src/core/operations/RSAEncrypt.mjs @@ -0,0 +1,84 @@ +/** + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import forge from "node-forge/dist/forge.min.js"; +import { MD_ALGORITHMS } from "../lib/RSA.mjs"; + +/** + * RSA Encrypt operation + */ +class RSAEncrypt extends Operation { + + /** + * RSAEncrypt constructor + */ + constructor() { + super(); + + this.name = "RSA Encrypt"; + this.module = "Ciphers"; + this.description = "Encrypt a message with a PEM encoded RSA public key."; + this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "RSA Public Key (PEM)", + type: "text", + value: "-----BEGIN RSA PUBLIC KEY-----" + }, + { + name: "Encryption Scheme", + type: "argSelector", + value: [ + { + name: "RSA-OAEP", + on: [2] + }, + { + name: "RSAES-PKCS1-V1_5", + off: [2] + }, + { + name: "RAW", + off: [2] + }] + }, + { + name: "Message Digest Algorithm", + type: "option", + value: Object.keys(MD_ALGORITHMS) + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [pemKey, scheme, md] = args; + + if (pemKey.replace("-----BEGIN RSA PUBLIC KEY-----", "").length === 0) { + throw new OperationError("Please enter a public key."); + } + try { + // Load public key + const pubKey = forge.pki.publicKeyFromPem(pemKey); + // Encrypt message + const eMsg = pubKey.encrypt(input, scheme, {md: MD_ALGORITHMS[md].create()}); + return eMsg; + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default RSAEncrypt; diff --git a/src/core/operations/RSASign.mjs b/src/core/operations/RSASign.mjs index 1c2696e3..6161c5fe 100644 --- a/src/core/operations/RSASign.mjs +++ b/src/core/operations/RSASign.mjs @@ -8,7 +8,6 @@ import Operation from "../Operation"; import OperationError from "../errors/OperationError"; import forge from "node-forge/dist/forge.min.js"; -import Utils from "../Utils.mjs"; import { MD_ALGORITHMS } from "../lib/RSA.mjs"; /** diff --git a/src/core/operations/RSAVerify.mjs b/src/core/operations/RSAVerify.mjs index 94ca4220..e1a8b0cb 100644 --- a/src/core/operations/RSAVerify.mjs +++ b/src/core/operations/RSAVerify.mjs @@ -6,7 +6,6 @@ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; -import Utils from "../Utils.mjs"; import forge from "node-forge/dist/forge.min.js"; import { MD_ALGORITHMS } from "../lib/RSA.mjs"; @@ -47,7 +46,7 @@ class RSAVerify extends Operation { } /** - * @param {byteArray} input + * @param {string} input * @param {Object[]} args * @returns {string} */ From 7ad3992bd14e3f063a0b19fc7bba8ad2baf59c56 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 7 Apr 2020 13:31:33 +0100 Subject: [PATCH 09/11] Add RSA Decrypt Operation --- src/core/operations/RSADecrypt.mjs | 86 ++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 src/core/operations/RSADecrypt.mjs diff --git a/src/core/operations/RSADecrypt.mjs b/src/core/operations/RSADecrypt.mjs new file mode 100644 index 00000000..0b0670e6 --- /dev/null +++ b/src/core/operations/RSADecrypt.mjs @@ -0,0 +1,86 @@ +/** + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import forge from "node-forge/dist/forge.min.js"; +import { MD_ALGORITHMS } from "../lib/RSA.mjs"; + +/** + * RSA Decrypt operation + */ +class RSADecrypt extends Operation { + + /** + * RSADecrypt constructor + */ + constructor() { + super(); + + this.name = "RSA Decrypt"; + this.module = "Ciphers"; + this.description = "Decrypt an RSA encrypted message with a PEM encoded private key."; + this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "RSA Private Key (PEM)", + type: "text", + value: "-----BEGIN RSA PRIVATE KEY-----" + }, + { + name: "Key Password", + type: "text", + value: "" + }, + { + name: "Encryption Scheme", + type: "argSelector", + value: [ + { + name: "RSA-OAEP", + on: [3] + }, + { + name: "RSAES-PKCS1-V1_5", + off: [3] + }, + { + name: "RAW", + off: [3] + }] + }, + { + name: "Message Digest Algorithm", + type: "option", + value: Object.keys(MD_ALGORITHMS) + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [pemKey, password, scheme, md] = args; + if (pemKey.replace("-----BEGIN RSA PRIVATE KEY-----", "").length === 0) { + throw new OperationError("Please enter a private key."); + } + try { + const privKey = forge.pki.decryptRsaPrivateKey(pemKey, password); + const dMsg = privKey.decrypt(input, scheme, {md: MD_ALGORITHMS[md].create()}); + return dMsg; + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default RSADecrypt; From fad163e0eb009ce5159b1ea68ca649679ace29a4 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 7 Apr 2020 21:16:29 +0100 Subject: [PATCH 10/11] Added tests (that can't be run) --- src/core/config/Categories.json | 3 + src/core/operations/RSADecrypt.mjs | 5 +- src/core/operations/RSAEncrypt.mjs | 8 +- tests/operations/index.mjs | 3 +- tests/operations/samples/Ciphers.mjs | 22 ++ tests/operations/tests/PGP.mjs | 25 +- tests/operations/tests/RSA.mjs | 350 +++++++++++++++++++++++++++ 7 files changed, 387 insertions(+), 29 deletions(-) create mode 100644 tests/operations/samples/Ciphers.mjs create mode 100644 tests/operations/tests/RSA.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 3b4d2c80..393a88a3 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -107,6 +107,9 @@ "Scrypt", "Generate RSA Key Pair", "RSA Sign", + "RSA Verify", + "RSA Encrypt", + "RSA Decrypt", "JWT Sign", "JWT Verify", "JWT Decode", diff --git a/src/core/operations/RSADecrypt.mjs b/src/core/operations/RSADecrypt.mjs index 0b0670e6..eb24aeab 100644 --- a/src/core/operations/RSADecrypt.mjs +++ b/src/core/operations/RSADecrypt.mjs @@ -6,6 +6,7 @@ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; import forge from "node-forge/dist/forge.min.js"; import { MD_ALGORITHMS } from "../lib/RSA.mjs"; @@ -24,7 +25,7 @@ class RSADecrypt extends Operation { this.module = "Ciphers"; this.description = "Decrypt an RSA encrypted message with a PEM encoded private key."; this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)"; - this.inputType = "string"; + this.inputType = "ArrayBuffer"; this.outputType = "string"; this.args = [ { @@ -74,7 +75,7 @@ class RSADecrypt extends Operation { } try { const privKey = forge.pki.decryptRsaPrivateKey(pemKey, password); - const dMsg = privKey.decrypt(input, scheme, {md: MD_ALGORITHMS[md].create()}); + const dMsg = privKey.decrypt(Utils.arrayBufferToStr(input), scheme, {md: MD_ALGORITHMS[md].create()}); return dMsg; } catch (err) { throw new OperationError(err); diff --git a/src/core/operations/RSAEncrypt.mjs b/src/core/operations/RSAEncrypt.mjs index b7d5026b..e788a668 100644 --- a/src/core/operations/RSAEncrypt.mjs +++ b/src/core/operations/RSAEncrypt.mjs @@ -6,6 +6,7 @@ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; import forge from "node-forge/dist/forge.min.js"; import { MD_ALGORITHMS } from "../lib/RSA.mjs"; @@ -25,7 +26,7 @@ class RSAEncrypt extends Operation { this.description = "Encrypt a message with a PEM encoded RSA public key."; this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)"; this.inputType = "string"; - this.outputType = "string"; + this.outputType = "ArrayBuffer"; this.args = [ { name: "RSA Public Key (PEM)", @@ -73,8 +74,11 @@ class RSAEncrypt extends Operation { const pubKey = forge.pki.publicKeyFromPem(pemKey); // Encrypt message const eMsg = pubKey.encrypt(input, scheme, {md: MD_ALGORITHMS[md].create()}); - return eMsg; + return Utils.strToArrayBuffer(eMsg); } catch (err) { + if (err.message === "RSAES-OAEP input message length is too long.") { + throw new OperationError(`RSAES-OAEP input message length (${err.length}) is longer than the maximum allowed length (${err.maxLength}).`); + } throw new OperationError(err); } } diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 8d3cd623..18d12032 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -105,7 +105,8 @@ import "./tests/ParseObjectIDTimestamp.mjs"; // Cannot test operations that use the File type yet // import "./tests/SplitColourChannels.mjs"; - +// Cannot test as minified forge does not support node +// import "./tests/RSA.mjs"; const testStatus = { allTestsPassing: true, counts: { diff --git a/tests/operations/samples/Ciphers.mjs b/tests/operations/samples/Ciphers.mjs new file mode 100644 index 00000000..a1363d83 --- /dev/null +++ b/tests/operations/samples/Ciphers.mjs @@ -0,0 +1,22 @@ +export const ASCII_TEXT = "A common mistake that people make when trying to design something completely foolproof is to underestimate the ingenuity of complete fools."; + +export const UTF8_TEXT = "Шанцы на высвятленне таго, што адбываецца на самай справе ў сусвеце настолькі выдаленыя, адзінае, што трэба зрабіць, гэта павесіць пачуццё яго і трымаць сябе занятымі."; + +export const ALL_BYTES = [ + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", + "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f", + "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f", + "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f", + "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f", + "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f", + "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f", + "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f", + "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f", + "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf", + "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf", + "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf", + "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf", + "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef", + "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff", +].join(""); diff --git a/tests/operations/tests/PGP.mjs b/tests/operations/tests/PGP.mjs index 18e2e7ed..9748f937 100644 --- a/tests/operations/tests/PGP.mjs +++ b/tests/operations/tests/PGP.mjs @@ -6,30 +6,7 @@ * @license Apache-2.0 */ import TestRegister from "../../lib/TestRegister.mjs"; - -const ASCII_TEXT = "A common mistake that people make when trying to design something completely foolproof is to underestimate the ingenuity of complete fools."; - -const UTF8_TEXT = "Шанцы на высвятленне таго, што адбываецца на самай справе ў сусвеце настолькі выдаленыя, адзінае, што трэба зрабіць, гэта павесіць пачуццё яго і трымаць сябе занятымі."; - -const ALL_BYTES = [ - "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", - "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f", - "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f", - "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f", - "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f", - "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f", - "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f", - "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f", - "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f", - "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f", - "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf", - "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf", - "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf", - "\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf", - "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef", - "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff", -].join(""); - +import {ASCII_TEXT, UTF8_TEXT, ALL_BYTES} from "../samples/Ciphers.mjs"; // RSA-1024 const ALICE_PRIVATE = `-----BEGIN PGP PRIVATE KEY BLOCK----- diff --git a/tests/operations/tests/RSA.mjs b/tests/operations/tests/RSA.mjs new file mode 100644 index 00000000..2ade5647 --- /dev/null +++ b/tests/operations/tests/RSA.mjs @@ -0,0 +1,350 @@ +/** + * RSA tests. + * + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; +import {ASCII_TEXT, UTF8_TEXT, ALL_BYTES} from "../samples/Ciphers.mjs"; + +const PEM_PRIV_2048 = `-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAwfaUOpUEutKyU3wkCv6kYunz4MqxzSuTSckRz1IxwZtwIiqq ++ejkM6ioXPyGadfFNvG0JVOgr1q4KQglq0vXaYG57HZ8iinXnHgy1vr8i+fWYITB +RMrEDySaQh3sxVj8NudPDoTIxZwUcIUu/N53pUmI08ADxXPA+ZymPyZhZyxrj5Jq +2O2QuRu+R7K44NDweP/rETbGo5+QAPydm6UqBzTky/ohv6EGhjyqnaskTWwLWK6P +dKva8rEMb8nNJvhoTJDLYUfNjB7DFnWxgWuR/KVkXGAHX99J/wh6QTS+bsyJ2/Mw +Df6NWdh3iP7msLNl/GqL+HunhHjrthvvWlODDwIDAQABAoIBAApKwLvJC3q0UmUO +qcTxlRxwiJHNf5jA7qxUIH9NP7mju1P8ypy/KFi7Ys+oUKOOIPdU5Pe0E8sqN6pp +tcH8oL4G9awf72TPapLxZ9UzdTIhR6VQdgbl8XhSO2M1vkoMejmZlX7SOesOaKE9 +1+vwDA43tCx0PF7+UOeN0d549WMphvw3VkSInO/MYpobCGra4YdrhYOhFMyLEGgA +zCyVUOxi538tyyFtK2EEQdcMtvVA6SECjF4xD/qrme0LelIj/L1Uhiu+SOzYt4y+ +QLHL6zhJVfOejWxjeI7BhodkTV2D53n4svfizRgyYEb6iLPW3nlMYIlAksYaxxB9 +nR3sMHECgYEA9RU+8J5A8RnBcwnlc2X1xEW2PN7+A1MeWPQwFqRwIokgvGbCtwjG +PwwNUYJCTBhfGhsISeCBOSYrDGTHsNH+tqFW2zlq61BolYl56jb1KgWzMOX8dak4 +sgXIuBbvyuFNk08VMIzwcA76ka/Iuu/nN9ZOM2UYpdpGG+CTOoIFULECgYEAyppm +I+yAtrUn/BFmwmC8va4vqXlBFjvdkfX/71ywCpHIouLucMV7bILJu0nSCpmL1A7R +DT6qo0p5g+Dxl/+O2VyC5D89PBvcuT1+HtEZGLOoKZnojbSrwDApGbzQi57GoQR6 +/SRjsdAmoelY8PFz2s2ZLJ4NkrZXYvkT1Tu8/78CgYEA4MAvC/HUlEWORbTZmk3y +Z5+WU5QbVWkv91tXjiwWOVWPk7aY8ck2JDMlM45ExgvDiuknXLhpSMNbzu3MwraQ +42JpiHjLOChxAFEmYEct5O99OGZwcmZQ+9CaFVfTZzXeMizfvbpB9EGIP3n4lpXS +cD4zUKZxSAc3K/FyksERpsECgYEAhQPXeVBltQ68oKaAE6/VWqcIjbiY/dLyBkk+ +7dSpk1bhJefdadaN0NERRtARgXoLrn7Hy21QNILJwsaldwiGrbgqC1Zlipg0Ur3H +ls3rLyeMiTuNzbNHa5dy9H3dYT0t5Tr+0EHa3jvtkTGVfiLX0FhZb0yZVrA2MTmc +RsvAqxsCgYAgXy4qytgfzo5/bBt306NbtMEW3dWBWF77HAz4N1LynKZRUrAAK4rz +BVmXFUaNQOg0q8WJG+iFF79u2UnL8iZ5GoPMcpvifsZgef1OHnQnFrfyXSr0fXIm +xq8eZS0DpLvKGffCW03B9VDRHanE37Tng8lbgOtaufuVzFa1bCuLUA== +-----END RSA PRIVATE KEY-----`; + +const PEM_PUB_2048 = `-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwfaUOpUEutKyU3wkCv6k +Yunz4MqxzSuTSckRz1IxwZtwIiqq+ejkM6ioXPyGadfFNvG0JVOgr1q4KQglq0vX +aYG57HZ8iinXnHgy1vr8i+fWYITBRMrEDySaQh3sxVj8NudPDoTIxZwUcIUu/N53 +pUmI08ADxXPA+ZymPyZhZyxrj5Jq2O2QuRu+R7K44NDweP/rETbGo5+QAPydm6Uq +BzTky/ohv6EGhjyqnaskTWwLWK6PdKva8rEMb8nNJvhoTJDLYUfNjB7DFnWxgWuR +/KVkXGAHX99J/wh6QTS+bsyJ2/MwDf6NWdh3iP7msLNl/GqL+HunhHjrthvvWlOD +DwIDAQAB +-----END PUBLIC KEY-----`; + +TestRegister.addTests([ + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-1, nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + "op": "RSA Encrypt", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-1"] + }, + { + "op": "RSA Decrypt", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-1"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-1, ASCII", + input: ASCII_TEXT, + expectedOutput: ASCII_TEXT, + recipeConfig: [ + { + "op": "RSA Encrypt", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-1"] + }, + { + "op": "RSA Decrypt", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-1"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-1, UTF-8", + input: UTF8_TEXT, + expectedOutput: UTF8_TEXT, + recipeConfig: [ + { + "op": "RSA Encrypt", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-1"] + }, + { + "op": "RSA Decrypt", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-1"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-1, All bytes", + input: ALL_BYTES, + expectedOutput: ALL_BYTES, + recipeConfig: [ + { + "op": "RSA Encrypt", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-1"] + }, + { + "op": "RSA Decrypt", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-1"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/MD5, nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + "op": "RSA Encrypt", + "args": [PEM_PUB_2048, "RSA-OAEP", "MD5"] + }, + { + "op": "RSA Decrypt", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "MD5"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/MD5, ASCII", + input: ASCII_TEXT, + expectedOutput: ASCII_TEXT, + recipeConfig: [ + { + "op": "RSA Encrypt", + "args": [PEM_PUB_2048, "RSA-OAEP", "MD5"] + }, + { + "op": "RSA Decrypt", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "MD5"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/MD5, UTF-8", + input: UTF8_TEXT, + expectedOutput: UTF8_TEXT, + recipeConfig: [ + { + "op": "RSA Encrypt", + "args": [PEM_PUB_2048, "RSA-OAEP", "MD5"] + }, + { + "op": "RSA Decrypt", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "MD5"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/MD5, All bytes", + input: ALL_BYTES, + expectedOutput: ALL_BYTES, + recipeConfig: [ + { + "op": "RSA Encrypt", + "args": [PEM_PUB_2048, "RSA-OAEP", "MD5"] + }, + { + "op": "RSA Decrypt", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "MD5"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-256, nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + "op": "RSA Encrypt", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-256"] + }, + { + "op": "RSA Decrypt", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-256"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-256, ASCII", + input: ASCII_TEXT, + expectedOutput: ASCII_TEXT, + recipeConfig: [ + { + "op": "RSA Encrypt", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-256"] + }, + { + "op": "RSA Decrypt", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-256"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-256, UTF-8", + input: UTF8_TEXT, + expectedOutput: UTF8_TEXT, + recipeConfig: [ + { + "op": "RSA Encrypt", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-256"] + }, + { + "op": "RSA Decrypt", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-256"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-256, All bytes", + input: ALL_BYTES, + expectedOutput: ALL_BYTES, + recipeConfig: [ + { + "op": "RSA Encrypt", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-256"] + }, + { + "op": "RSA Decrypt", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-256"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-384, nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + "op": "RSA Encrypt", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-384"] + }, + { + "op": "RSA Decrypt", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-384"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-384, ASCII", + input: ASCII_TEXT, + expectedOutput: ASCII_TEXT, + recipeConfig: [ + { + "op": "RSA Encrypt", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-384"] + }, + { + "op": "RSA Decrypt", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-384"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-384, UTF-8", + input: UTF8_TEXT, + expectedOutput: UTF8_TEXT, + recipeConfig: [ + { + "op": "RSA Encrypt", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-384"] + }, + { + "op": "RSA Decrypt", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-384"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-384, All bytes", + input: ALL_BYTES, + expectedOutput: ALL_BYTES, + recipeConfig: [ + { + "op": "RSA Encrypt", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-384"] + }, + { + "op": "RSA Decrypt", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-384"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-512, nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + "op": "RSA Encrypt", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-512"] + }, + { + "op": "RSA Decrypt", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-512"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-512, ASCII", + input: ASCII_TEXT, + expectedOutput: ASCII_TEXT, + recipeConfig: [ + { + "op": "RSA Encrypt", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-512"] + }, + { + "op": "RSA Decrypt", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-512"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-512, UTF-8", + input: UTF8_TEXT, + expectedOutput: UTF8_TEXT, + recipeConfig: [ + { + "op": "RSA Encrypt", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-512"] + }, + { + "op": "RSA Decrypt", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-512"] + } + ] + }, + { + name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-512, All bytes", + input: ALL_BYTES, + expectedOutput: ALL_BYTES, + recipeConfig: [ + { + "op": "RSA Encrypt", + "args": [PEM_PUB_2048, "RSA-OAEP", "SHA-512"] + }, + { + "op": "RSA Decrypt", + "args": [PEM_PRIV_2048, "", "RSA-OAEP", "SHA-512"] + } + ] + }, +]); From 74ae77f17ad92ff1790d2737a0e2400d45985b54 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Mon, 1 Feb 2021 19:15:32 +0000 Subject: [PATCH 11/11] Tidied up and added tests for RSA operations --- src/core/config/Categories.json | 10 ++--- src/core/lib/RSA.mjs | 10 ++++- src/core/operations/GeneratePGPKeyPair.mjs | 2 +- src/core/operations/GenerateRSAKeyPair.mjs | 12 ++++-- src/core/operations/RSADecrypt.mjs | 9 ++--- src/core/operations/RSAEncrypt.mjs | 11 +++--- src/core/operations/RSASign.mjs | 6 +-- src/core/operations/RSAVerify.mjs | 2 +- tests/operations/index.mjs | 5 +-- tests/operations/samples/Ciphers.mjs | 9 +++++ tests/operations/tests/RSA.mjs | 44 +++++++++++----------- 11 files changed, 70 insertions(+), 50 deletions(-) diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 70c63b33..1afa6dcb 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -105,11 +105,6 @@ "Derive EVP key", "Bcrypt", "Scrypt", - "Generate RSA Key Pair", - "RSA Sign", - "RSA Verify", - "RSA Encrypt", - "RSA Decrypt", "JWT Sign", "JWT Verify", "JWT Decode", @@ -139,6 +134,11 @@ "PGP Verify", "PGP Encrypt and Sign", "PGP Decrypt and Verify", + "Generate RSA Key Pair", + "RSA Sign", + "RSA Verify", + "RSA Encrypt", + "RSA Decrypt", "Parse SSH Host Key" ] }, diff --git a/src/core/lib/RSA.mjs b/src/core/lib/RSA.mjs index 0dab67a3..9037379c 100644 --- a/src/core/lib/RSA.mjs +++ b/src/core/lib/RSA.mjs @@ -1,4 +1,12 @@ -import forge from "node-forge/dist/forge.min.js"; +/** + * RSA resources. + * + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + */ + +import forge from "node-forge"; export const MD_ALGORITHMS = { "SHA-1": forge.md.sha1, diff --git a/src/core/operations/GeneratePGPKeyPair.mjs b/src/core/operations/GeneratePGPKeyPair.mjs index bb366b6f..5943ce31 100644 --- a/src/core/operations/GeneratePGPKeyPair.mjs +++ b/src/core/operations/GeneratePGPKeyPair.mjs @@ -25,7 +25,7 @@ class GeneratePGPKeyPair extends Operation { this.name = "Generate PGP Key Pair"; this.module = "PGP"; - this.description = "Generates a new public/private PGP key pair. Supports RSA and Eliptic Curve (EC) keys."; + this.description = "Generates a new public/private PGP key pair. Supports RSA and Eliptic Curve (EC) keys.

WARNING: Cryptographic operations in CyberChef should not be relied upon to provide security in any situation. No guarantee is offered for their correctness. We advise you not to use keys generated from CyberChef in operational contexts."; this.infoURL = "https://wikipedia.org/wiki/Pretty_Good_Privacy"; this.inputType = "string"; this.outputType = "string"; diff --git a/src/core/operations/GenerateRSAKeyPair.mjs b/src/core/operations/GenerateRSAKeyPair.mjs index 57cfcdd0..f263eb23 100644 --- a/src/core/operations/GenerateRSAKeyPair.mjs +++ b/src/core/operations/GenerateRSAKeyPair.mjs @@ -5,8 +5,8 @@ * @license Apache-2.0 */ -import Operation from "../Operation"; -import forge from "node-forge/dist/forge.min.js"; +import Operation from "../Operation.mjs"; +import forge from "node-forge"; /** * Generate RSA Key Pair operation @@ -21,7 +21,7 @@ class GenerateRSAKeyPair extends Operation { this.name = "Generate RSA Key Pair"; this.module = "Ciphers"; - this.description = "Generate an RSA key pair with a given number of bits"; + this.description = "Generate an RSA key pair with a given number of bits.

WARNING: Cryptographic operations in CyberChef should not be relied upon to provide security in any situation. No guarantee is offered for their correctness. We advise you not to use keys generated from CyberChef in operational contexts."; this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)"; this.inputType = "string"; this.outputType = "string"; @@ -56,7 +56,11 @@ class GenerateRSAKeyPair extends Operation { const [keyLength, outputFormat] = args; return new Promise((resolve, reject) => { - forge.pki.rsa.generateKeyPair({ bits: Number(keyLength), workers: -1, workerScript: "assets/forge/prime.worker.min.js"}, (err, keypair) => { + forge.pki.rsa.generateKeyPair({ + bits: Number(keyLength), + workers: -1, + workerScript: "assets/forge/prime.worker.min.js" + }, (err, keypair) => { if (err) return reject(err); let result; diff --git a/src/core/operations/RSADecrypt.mjs b/src/core/operations/RSADecrypt.mjs index eb24aeab..5b32b790 100644 --- a/src/core/operations/RSADecrypt.mjs +++ b/src/core/operations/RSADecrypt.mjs @@ -6,8 +6,7 @@ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; -import Utils from "../Utils.mjs"; -import forge from "node-forge/dist/forge.min.js"; +import forge from "node-forge"; import { MD_ALGORITHMS } from "../lib/RSA.mjs"; /** @@ -25,7 +24,7 @@ class RSADecrypt extends Operation { this.module = "Ciphers"; this.description = "Decrypt an RSA encrypted message with a PEM encoded private key."; this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)"; - this.inputType = "ArrayBuffer"; + this.inputType = "string"; this.outputType = "string"; this.args = [ { @@ -75,8 +74,8 @@ class RSADecrypt extends Operation { } try { const privKey = forge.pki.decryptRsaPrivateKey(pemKey, password); - const dMsg = privKey.decrypt(Utils.arrayBufferToStr(input), scheme, {md: MD_ALGORITHMS[md].create()}); - return dMsg; + const dMsg = privKey.decrypt(input, scheme, {md: MD_ALGORITHMS[md].create()}); + return forge.util.decodeUtf8(dMsg); } catch (err) { throw new OperationError(err); } diff --git a/src/core/operations/RSAEncrypt.mjs b/src/core/operations/RSAEncrypt.mjs index e788a668..859ce132 100644 --- a/src/core/operations/RSAEncrypt.mjs +++ b/src/core/operations/RSAEncrypt.mjs @@ -6,8 +6,7 @@ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; -import Utils from "../Utils.mjs"; -import forge from "node-forge/dist/forge.min.js"; +import forge from "node-forge"; import { MD_ALGORITHMS } from "../lib/RSA.mjs"; /** @@ -26,7 +25,7 @@ class RSAEncrypt extends Operation { this.description = "Encrypt a message with a PEM encoded RSA public key."; this.infoURL = "https://wikipedia.org/wiki/RSA_(cryptosystem)"; this.inputType = "string"; - this.outputType = "ArrayBuffer"; + this.outputType = "string"; this.args = [ { name: "RSA Public Key (PEM)", @@ -72,9 +71,11 @@ class RSAEncrypt extends Operation { try { // Load public key const pubKey = forge.pki.publicKeyFromPem(pemKey); + // https://github.com/digitalbazaar/forge/issues/465#issuecomment-271097600 + const plaintextBytes = forge.util.encodeUtf8(input); // Encrypt message - const eMsg = pubKey.encrypt(input, scheme, {md: MD_ALGORITHMS[md].create()}); - return Utils.strToArrayBuffer(eMsg); + const eMsg = pubKey.encrypt(plaintextBytes, scheme, {md: MD_ALGORITHMS[md].create()}); + return eMsg; } catch (err) { if (err.message === "RSAES-OAEP input message length is too long.") { throw new OperationError(`RSAES-OAEP input message length (${err.length}) is longer than the maximum allowed length (${err.maxLength}).`); diff --git a/src/core/operations/RSASign.mjs b/src/core/operations/RSASign.mjs index 6161c5fe..25160f53 100644 --- a/src/core/operations/RSASign.mjs +++ b/src/core/operations/RSASign.mjs @@ -5,9 +5,9 @@ * @license Apache-2.0 */ -import Operation from "../Operation"; -import OperationError from "../errors/OperationError"; -import forge from "node-forge/dist/forge.min.js"; +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import forge from "node-forge"; import { MD_ALGORITHMS } from "../lib/RSA.mjs"; /** diff --git a/src/core/operations/RSAVerify.mjs b/src/core/operations/RSAVerify.mjs index e1a8b0cb..89b7d81f 100644 --- a/src/core/operations/RSAVerify.mjs +++ b/src/core/operations/RSAVerify.mjs @@ -6,7 +6,7 @@ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; -import forge from "node-forge/dist/forge.min.js"; +import forge from "node-forge"; import { MD_ALGORITHMS } from "../lib/RSA.mjs"; /** diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 50079b82..d20ccc61 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -101,12 +101,11 @@ import "./tests/CipherSaber2.mjs"; import "./tests/Colossus.mjs"; import "./tests/ParseObjectIDTimestamp.mjs"; import "./tests/Unicode.mjs"; - +import "./tests/RSA.mjs"; // Cannot test operations that use the File type yet // import "./tests/SplitColourChannels.mjs"; -// Cannot test as minified forge does not support node -// import "./tests/RSA.mjs"; + const testStatus = { allTestsPassing: true, counts: { diff --git a/tests/operations/samples/Ciphers.mjs b/tests/operations/samples/Ciphers.mjs index a1363d83..2d975131 100644 --- a/tests/operations/samples/Ciphers.mjs +++ b/tests/operations/samples/Ciphers.mjs @@ -1,3 +1,12 @@ +/** + * Various types of input data for use in tests + * + * @author n1474335 [n1474335@gmail.com] + * @author Matt C [me@mitt.dev] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + export const ASCII_TEXT = "A common mistake that people make when trying to design something completely foolproof is to underestimate the ingenuity of complete fools."; export const UTF8_TEXT = "Шанцы на высвятленне таго, што адбываецца на самай справе ў сусвеце настолькі выдаленыя, адзінае, што трэба зрабіць, гэта павесіць пачуццё яго і трымаць сябе занятымі."; diff --git a/tests/operations/tests/RSA.mjs b/tests/operations/tests/RSA.mjs index 2ade5647..33e7139f 100644 --- a/tests/operations/tests/RSA.mjs +++ b/tests/operations/tests/RSA.mjs @@ -79,8 +79,8 @@ TestRegister.addTests([ }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-1, UTF-8", - input: UTF8_TEXT, - expectedOutput: UTF8_TEXT, + input: UTF8_TEXT.substr(0, 100), + expectedOutput: UTF8_TEXT.substr(0, 100), recipeConfig: [ { "op": "RSA Encrypt", @@ -94,8 +94,8 @@ TestRegister.addTests([ }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-1, All bytes", - input: ALL_BYTES, - expectedOutput: ALL_BYTES, + input: ALL_BYTES.substr(0, 100), + expectedOutput: ALL_BYTES.substr(0, 100), recipeConfig: [ { "op": "RSA Encrypt", @@ -139,8 +139,8 @@ TestRegister.addTests([ }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/MD5, UTF-8", - input: UTF8_TEXT, - expectedOutput: UTF8_TEXT, + input: UTF8_TEXT.substr(0, 100), + expectedOutput: UTF8_TEXT.substr(0, 100), recipeConfig: [ { "op": "RSA Encrypt", @@ -154,8 +154,8 @@ TestRegister.addTests([ }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/MD5, All bytes", - input: ALL_BYTES, - expectedOutput: ALL_BYTES, + input: ALL_BYTES.substr(0, 100), + expectedOutput: ALL_BYTES.substr(0, 100), recipeConfig: [ { "op": "RSA Encrypt", @@ -199,8 +199,8 @@ TestRegister.addTests([ }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-256, UTF-8", - input: UTF8_TEXT, - expectedOutput: UTF8_TEXT, + input: UTF8_TEXT.substr(0, 100), + expectedOutput: UTF8_TEXT.substr(0, 100), recipeConfig: [ { "op": "RSA Encrypt", @@ -214,8 +214,8 @@ TestRegister.addTests([ }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-256, All bytes", - input: ALL_BYTES, - expectedOutput: ALL_BYTES, + input: ALL_BYTES.substr(0, 100), + expectedOutput: ALL_BYTES.substr(0, 100), recipeConfig: [ { "op": "RSA Encrypt", @@ -259,8 +259,8 @@ TestRegister.addTests([ }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-384, UTF-8", - input: UTF8_TEXT, - expectedOutput: UTF8_TEXT, + input: UTF8_TEXT.substr(0, 80), + expectedOutput: UTF8_TEXT.substr(0, 80), recipeConfig: [ { "op": "RSA Encrypt", @@ -274,8 +274,8 @@ TestRegister.addTests([ }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-384, All bytes", - input: ALL_BYTES, - expectedOutput: ALL_BYTES, + input: ALL_BYTES.substr(0, 100), + expectedOutput: ALL_BYTES.substr(0, 100), recipeConfig: [ { "op": "RSA Encrypt", @@ -304,8 +304,8 @@ TestRegister.addTests([ }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-512, ASCII", - input: ASCII_TEXT, - expectedOutput: ASCII_TEXT, + input: ASCII_TEXT.substr(0, 100), + expectedOutput: ASCII_TEXT.substr(0, 100), recipeConfig: [ { "op": "RSA Encrypt", @@ -319,8 +319,8 @@ TestRegister.addTests([ }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-512, UTF-8", - input: UTF8_TEXT, - expectedOutput: UTF8_TEXT, + input: UTF8_TEXT.substr(0, 60), + expectedOutput: UTF8_TEXT.substr(0, 60), recipeConfig: [ { "op": "RSA Encrypt", @@ -334,8 +334,8 @@ TestRegister.addTests([ }, { name: "RSA Encrypt/Decrypt: RSA-OAEP/SHA-512, All bytes", - input: ALL_BYTES, - expectedOutput: ALL_BYTES, + input: ALL_BYTES.substr(0, 100), + expectedOutput: ALL_BYTES.substr(0, 100), recipeConfig: [ { "op": "RSA Encrypt",