From 8559f5c8eab3ba09565ff4be051b9c6c8c5f431d Mon Sep 17 00:00:00 2001 From: GCHQ 77703 Date: Sun, 26 Aug 2018 23:16:13 +0100 Subject: [PATCH 1/3] Add JWT Verify, Decode and Sign --- package.json | 1 + src/core/config/Categories.json | 5 +- src/core/operations/JWTDecode.mjs | 46 +++++++++++++++ src/core/operations/JWTSign.mjs | 94 +++++++++++++++++++++++++++++++ src/core/operations/JWTVerify.mjs | 53 +++++++++++++++++ 5 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 src/core/operations/JWTDecode.mjs create mode 100644 src/core/operations/JWTSign.mjs create mode 100644 src/core/operations/JWTVerify.mjs diff --git a/package.json b/package.json index a978f9470a..6fa99dd17e 100644 --- a/package.json +++ b/package.json @@ -99,6 +99,7 @@ "jsbn": "^1.1.0", "jsesc": "^2.5.1", "jsonpath": "^1.0.0", + "jsonwebtoken": "^8.3.0", "jsrsasign": "8.0.12", "kbpgp": "^2.0.77", "lodash": "^4.17.10", diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 66663f4a7f..5d32aef2db 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -89,7 +89,10 @@ "Derive EVP key", "Bcrypt", "Scrypt", - "Pseudo-Random Number Generator" + "Pseudo-Random Number Generator", + "JWT Sign", + "JWT Verify", + "JWT Decode" ] }, { diff --git a/src/core/operations/JWTDecode.mjs b/src/core/operations/JWTDecode.mjs new file mode 100644 index 0000000000..cf7945ace4 --- /dev/null +++ b/src/core/operations/JWTDecode.mjs @@ -0,0 +1,46 @@ +/** + * @author gchq77703 [] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import jwt from "jsonwebtoken"; + +/** + * JWT Decode operation + */ +class JWTDecode extends Operation { + + /** + * JWTDecode constructor + */ + constructor() { + super(); + + this.name = "JWT Decode"; + this.module = "Crypto"; + this.description = "Decodes a JSON Web Token without checking whether the provided secret / private key is valid."; + this.infoURL = "https://jwt.io"; + this.inputType = "string"; + this.outputType = "JSON"; + this.args = [ + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {JSON} + */ + run(input, args) { + try { + return jwt.decode(input); + } catch (err) { + return err; + } + } + +} + +export default JWTDecode; diff --git a/src/core/operations/JWTSign.mjs b/src/core/operations/JWTSign.mjs new file mode 100644 index 0000000000..7bf6230866 --- /dev/null +++ b/src/core/operations/JWTSign.mjs @@ -0,0 +1,94 @@ +/** + * @author gchq77703 [] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import jwt from "jsonwebtoken"; + +/** + * JWT Sign operation + */ +class JWTSign extends Operation { + + /** + * JWTSign constructor + */ + constructor() { + super(); + + this.name = "JWT Sign"; + this.module = "Crypto"; + this.description = "Signs a JSON object as a JSON Web Token using a provided secret / private key."; + this.infoURL = "https://jwt.io/"; + this.inputType = "JSON"; + this.outputType = "string"; + this.args = [ + { + name: "Private / Secret Key", + type: "shortString", + value: "secret_cat" + }, + { + name: "Signing Algorithm", + type: "populateOption", + value: [ + { + name: "HS256", + value: "HS256" + }, + { + name: "HS384", + value: "HS384", + }, + { + name: "HS512", + value: "HS512", + }, + { + name: "RS256", + value: "RS256", + }, + { + name: "RS384", + value: "RS384", + }, + { + name: "RS512", + value: "RS512", + }, + { + name: "ES256", + value: "ES256", + }, + { + name: "ES384", + value: "ES384", + }, + { + name: "ES512", + value: "ES512", + }, + { + name: "None", + value: "none", + }, + ] + } + ]; + } + + /** + * @param {JSON} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [key, algorithm] = args; + return jwt.sign(input, key, { algorithm: algorithm === "None" ? "none" : algorithm }); + } + +} + +export default JWTSign; diff --git a/src/core/operations/JWTVerify.mjs b/src/core/operations/JWTVerify.mjs new file mode 100644 index 0000000000..cd1df74d79 --- /dev/null +++ b/src/core/operations/JWTVerify.mjs @@ -0,0 +1,53 @@ +/** + * @author gchq77703 [] + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import jwt from "jsonwebtoken"; + +/** + * JWT Verify operation + */ +class JWTVerify extends Operation { + + /** + * JWTVerify constructor + */ + constructor() { + super(); + + this.name = "JWT Verify"; + this.module = "Crypto"; + this.description = "Verifies that a JSON Web Token is valid and has been signed with the provided secret / private key."; + this.infoURL = "https://jwt.io/"; + this.inputType = "string"; + this.outputType = "JSON"; + this.args = [ + { + name: "Private / Secret Key", + type: "shortString", + value: "secret_cat" + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [key] = args; + + try { + return jwt.verify(input, key); + } catch (err) { + return err; + } + } + +} + +export default JWTVerify; From a95f43aa4dc336d9a155308b720e798156247f22 Mon Sep 17 00:00:00 2001 From: GCHQ 77703 Date: Wed, 29 Aug 2018 22:43:10 +0100 Subject: [PATCH 2/3] Implement tests, fix options argument --- src/core/operations/JWTSign.mjs | 54 ++------- src/core/operations/JWTVerify.mjs | 9 +- test/index.mjs | 3 + test/tests/operations/JWTDecode.mjs | 51 +++++++++ test/tests/operations/JWTSign.mjs | 163 ++++++++++++++++++++++++++++ test/tests/operations/JWTVerify.mjs | 78 +++++++++++++ 6 files changed, 314 insertions(+), 44 deletions(-) create mode 100644 test/tests/operations/JWTDecode.mjs create mode 100644 test/tests/operations/JWTSign.mjs create mode 100644 test/tests/operations/JWTVerify.mjs diff --git a/src/core/operations/JWTSign.mjs b/src/core/operations/JWTSign.mjs index 7bf6230866..d9eb757442 100644 --- a/src/core/operations/JWTSign.mjs +++ b/src/core/operations/JWTSign.mjs @@ -27,53 +27,23 @@ class JWTSign extends Operation { this.args = [ { name: "Private / Secret Key", - type: "shortString", + type: "text", value: "secret_cat" }, { name: "Signing Algorithm", - type: "populateOption", + type: "option", value: [ - { - name: "HS256", - value: "HS256" - }, - { - name: "HS384", - value: "HS384", - }, - { - name: "HS512", - value: "HS512", - }, - { - name: "RS256", - value: "RS256", - }, - { - name: "RS384", - value: "RS384", - }, - { - name: "RS512", - value: "RS512", - }, - { - name: "ES256", - value: "ES256", - }, - { - name: "ES384", - value: "ES384", - }, - { - name: "ES512", - value: "ES512", - }, - { - name: "None", - value: "none", - }, + "HS256", + "HS384", + "HS512", + "RS256", + "RS384", + "RS512", + "ES256", + "ES384", + "ES512", + "None" ] } ]; diff --git a/src/core/operations/JWTVerify.mjs b/src/core/operations/JWTVerify.mjs index cd1df74d79..bbacdce14f 100644 --- a/src/core/operations/JWTVerify.mjs +++ b/src/core/operations/JWTVerify.mjs @@ -27,7 +27,7 @@ class JWTVerify extends Operation { this.args = [ { name: "Private / Secret Key", - type: "shortString", + type: "text", value: "secret_cat" }, ]; @@ -42,7 +42,12 @@ class JWTVerify extends Operation { const [key] = args; try { - return jwt.verify(input, key); + return jwt.verify(input, key, { algorithms: [ + "HS256", + "HS384", + "HS512", + "none" + ]}); } catch (err) { return err; } diff --git a/test/index.mjs b/test/index.mjs index 8cf69732ad..0812952b5f 100644 --- a/test/index.mjs +++ b/test/index.mjs @@ -64,6 +64,9 @@ import "./tests/operations/SetUnion"; import "./tests/operations/SymmetricDifference"; import "./tests/operations/TranslateDateTimeFormat"; import "./tests/operations/Magic"; +import "./tests/operations/JWTSign"; +import "./tests/operations/JWTDecode"; +import "./tests/operations/JWTVerify"; let allTestsPassing = true; const testStatusCounts = { diff --git a/test/tests/operations/JWTDecode.mjs b/test/tests/operations/JWTDecode.mjs new file mode 100644 index 0000000000..d355b83246 --- /dev/null +++ b/test/tests/operations/JWTDecode.mjs @@ -0,0 +1,51 @@ +/** + * JWT Decode tests + * + * @author gchq77703 [] + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ +import TestRegister from "../../TestRegister"; + +const outputObject = JSON.stringify({ + String: "SomeString", + Number: 42, + iat: 1 +}); + +TestRegister.addTests([ + { + name: "JSON Decode: HS", + input: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.0ha6-j4FwvEIKPVZ-hf3S_R9Hy_UtXzq4dnedXcUrXk", + expectedOutput: outputObject, + recipeConfig: [ + { + op: "JWT Decode", + args: [], + } + ], + }, + { + name: "JSON Decode: RS", + input: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.MjEJhtZk2nXzigi24piMzANmrj3mILHJcDl0xOjl5a8EgdKVL1oaMEjTkMQp5RA8YrqeRBFaX-BGGCKOXn5zPY1DJwWsBUyN9C-wGR2Qye0eogH_3b4M9EW00TPCUPXm2rx8URFj7Wg9VlsmrGzLV2oKkPgkVxuFSxnpO3yjn1Y", + expectedOutput: outputObject, + recipeConfig: [ + { + op: "JWT Decode", + args: [], + } + ], + }, + { + name: "JSON Decode: ES", + input: "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.WkECT51jSfpRkcpQ4x0h5Dwe7CFBI6u6Et2gWp91HC7mpN_qCFadRpsvJLtKubm6cJTLa68xtei0YrDD8fxIUA", + expectedOutput: outputObject, + recipeConfig: [ + { + op: "JWT Decode", + args: [], + } + ], + } +]); diff --git a/test/tests/operations/JWTSign.mjs b/test/tests/operations/JWTSign.mjs new file mode 100644 index 0000000000..f0432cbfe9 --- /dev/null +++ b/test/tests/operations/JWTSign.mjs @@ -0,0 +1,163 @@ +/** + * JWT Sign tests + * + * @author gchq77703 [] + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ +import TestRegister from "../../TestRegister"; + +const inputObject = JSON.stringify({ + String: "SomeString", + Number: 42, + iat: 1 +}); + +const hsKey = "secret_cat"; +const rsKey = `-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw +33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW ++jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQAB +AoGAD+onAtVye4ic7VR7V50DF9bOnwRwNXrARcDhq9LWNRrRGElESYYTQ6EbatXS +3MCyjjX2eMhu/aF5YhXBwkppwxg+EOmXeh+MzL7Zh284OuPbkglAaGhV9bb6/5Cp +uGb1esyPbYW+Ty2PC0GSZfIXkXs76jXAu9TOBvD0ybc2YlkCQQDywg2R/7t3Q2OE +2+yo382CLJdrlSLVROWKwb4tb2PjhY4XAwV8d1vy0RenxTB+K5Mu57uVSTHtrMK0 +GAtFr833AkEA6avx20OHo61Yela/4k5kQDtjEf1N0LfI+BcWZtxsS3jDM3i1Hp0K +Su5rsCPb8acJo5RO26gGVrfAsDcIXKC+bQJAZZ2XIpsitLyPpuiMOvBbzPavd4gY +6Z8KWrfYzJoI/Q9FuBo6rKwl4BFoToD7WIUS+hpkagwWiz+6zLoX1dbOZwJACmH5 +fSSjAkLRi54PKJ8TFUeOP15h9sQzydI8zJU+upvDEKZsZc/UhT/SySDOxQ4G/523 +Y0sz/OZtSWcol/UMgQJALesy++GdvoIDLfJX5GBQpuFgFenRiRDabxrE9MNUZ2aP +FaFp+DyAe+b4nDwuJaW2LURbr8AEZga7oQj0uYxcYw== +-----END RSA PRIVATE KEY-----`; +const esKey = `-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2 +OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r +1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G +-----END PRIVATE KEY-----`; + +TestRegister.addTests([ + { + name: "JSON Sign: HS256", + input: inputObject, + expectedOutput: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.0ha6-j4FwvEIKPVZ-hf3S_R9Hy_UtXzq4dnedXcUrXk", + recipeConfig: [ + { + op: "JWT Sign", + args: [hsKey, "HS256"], + } + ], + }, + { + name: "JSON Sign: HS384", + input: inputObject, + expectedOutput: "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ._bPK-Y3mIACConbJqkGFMQ_L3vbxgKXy9gSxtL9hA5XTganozTSXxD0vX0N1yT5s", + recipeConfig: [ + { + op: "JWT Sign", + args: [hsKey, "HS384"], + } + ], + }, + { + name: "JSON Sign: HS512", + input: inputObject, + expectedOutput: "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.vZIJU4XYMFt3FLE1V_RZOxEetmV4RvxtPZQGzJthK_d47pjwlEb6pQE23YxHFmOj8H5RLEdqqLPw4jNsOyHRzA", + recipeConfig: [ + { + op: "JWT Sign", + args: [hsKey, "HS512"], + } + ], + }, + { + name: "JSON Sign: ES256", + input: inputObject, + expectedOutput: inputObject, + recipeConfig: [ + { + op: "JWT Sign", + args: [esKey, "ES256"], + }, + { + op: "JWT Decode", + args: [] + } + ], + }, + { + name: "JSON Sign: ES384", + input: inputObject, + expectedOutput: inputObject, + recipeConfig: [ + { + op: "JWT Sign", + args: [esKey, "ES384"], + }, + { + op: "JWT Decode", + args: [] + } + ], + }, + { + name: "JSON Sign: ES512", + input: inputObject, + expectedOutput: inputObject, + recipeConfig: [ + { + op: "JWT Sign", + args: [esKey, "ES512"], + }, + { + op: "JWT Decode", + args: [] + } + ], + }, + { + name: "JSON Sign: RS256", + input: inputObject, + expectedOutput: inputObject, + recipeConfig: [ + { + op: "JWT Sign", + args: [rsKey, "RS256"], + }, + { + op: "JWT Decode", + args: [] + } + ], + }, + { + name: "JSON Sign: RS384", + input: inputObject, + expectedOutput: inputObject, + recipeConfig: [ + { + op: "JWT Sign", + args: [rsKey, "RS384"], + }, + { + op: "JWT Decode", + args: [] + } + ], + }, + { + name: "JSON Sign: RS512", + input: inputObject, + expectedOutput: inputObject, + recipeConfig: [ + { + op: "JWT Sign", + args: [esKey, "RS512"], + }, + { + op: "JWT Decode", + args: [] + } + ], + } +]); diff --git a/test/tests/operations/JWTVerify.mjs b/test/tests/operations/JWTVerify.mjs new file mode 100644 index 0000000000..94e1074bf6 --- /dev/null +++ b/test/tests/operations/JWTVerify.mjs @@ -0,0 +1,78 @@ +/** + * JWT Verify tests + * + * @author gchq77703 [] + * + * @copyright Crown Copyright 2018 + * @license Apache-2.0 + */ +import TestRegister from "../../TestRegister"; + +const outputObject = JSON.stringify({ + String: "SomeString", + Number: 42, + iat: 1 +}); + +const invalidAlgorithm = JSON.stringify({ + name: "JsonWebTokenError", + message: "invalid algorithm" +}); + +const hsKey = "secret_cat"; +const rsKey = `-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw +33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW ++jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQAB +AoGAD+onAtVye4ic7VR7V50DF9bOnwRwNXrARcDhq9LWNRrRGElESYYTQ6EbatXS +3MCyjjX2eMhu/aF5YhXBwkppwxg+EOmXeh+MzL7Zh284OuPbkglAaGhV9bb6/5Cp +uGb1esyPbYW+Ty2PC0GSZfIXkXs76jXAu9TOBvD0ybc2YlkCQQDywg2R/7t3Q2OE +2+yo382CLJdrlSLVROWKwb4tb2PjhY4XAwV8d1vy0RenxTB+K5Mu57uVSTHtrMK0 +GAtFr833AkEA6avx20OHo61Yela/4k5kQDtjEf1N0LfI+BcWZtxsS3jDM3i1Hp0K +Su5rsCPb8acJo5RO26gGVrfAsDcIXKC+bQJAZZ2XIpsitLyPpuiMOvBbzPavd4gY +6Z8KWrfYzJoI/Q9FuBo6rKwl4BFoToD7WIUS+hpkagwWiz+6zLoX1dbOZwJACmH5 +fSSjAkLRi54PKJ8TFUeOP15h9sQzydI8zJU+upvDEKZsZc/UhT/SySDOxQ4G/523 +Y0sz/OZtSWcol/UMgQJALesy++GdvoIDLfJX5GBQpuFgFenRiRDabxrE9MNUZ2aP +FaFp+DyAe+b4nDwuJaW2LURbr8AEZga7oQj0uYxcYw== +-----END RSA PRIVATE KEY-----`; +const esKey = `-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2 +OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r +1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G +-----END PRIVATE KEY-----`; + +TestRegister.addTests([ + { + name: "JSON Verify: HS", + input: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.0ha6-j4FwvEIKPVZ-hf3S_R9Hy_UtXzq4dnedXcUrXk", + expectedOutput: outputObject, + recipeConfig: [ + { + op: "JWT Verify", + args: [hsKey], + } + ], + }, + { + name: "JSON Verify: RS", + input: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.MjEJhtZk2nXzigi24piMzANmrj3mILHJcDl0xOjl5a8EgdKVL1oaMEjTkMQp5RA8YrqeRBFaX-BGGCKOXn5zPY1DJwWsBUyN9C-wGR2Qye0eogH_3b4M9EW00TPCUPXm2rx8URFj7Wg9VlsmrGzLV2oKkPgkVxuFSxnpO3yjn1Y", + expectedOutput: invalidAlgorithm, + recipeConfig: [ + { + op: "JWT Verify", + args: [rsKey], + } + ], + }, + { + name: "JSON Verify: ES", + input: "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.WkECT51jSfpRkcpQ4x0h5Dwe7CFBI6u6Et2gWp91HC7mpN_qCFadRpsvJLtKubm6cJTLa68xtei0YrDD8fxIUA", + expectedOutput: invalidAlgorithm, + recipeConfig: [ + { + op: "JWT Verify", + args: [esKey], + } + ], + } +]); From be14d56eae447fd01fcf70bfde8a58e4a853f88a Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 31 Aug 2018 13:58:06 +0000 Subject: [PATCH 3/3] Tidied up JWT operations --- Gruntfile.js | 6 +- package-lock.json | 89 +++++++++++++++++++++++++++-- src/core/config/Categories.json | 4 +- src/core/operations/JWTDecode.mjs | 17 ++++-- src/core/operations/JWTSign.mjs | 22 +++++-- src/core/operations/JWTVerify.mjs | 19 ++++-- test/tests/operations/JWTDecode.mjs | 8 +-- test/tests/operations/JWTSign.mjs | 20 +++---- test/tests/operations/JWTVerify.mjs | 13 ++--- 9 files changed, 147 insertions(+), 51 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 65ea03392d..e041d08000 100755 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -382,13 +382,13 @@ module.exports = function (grunt) { "mkdir -p src/core/config/modules", "echo 'export default {};\n' > src/core/config/modules/OpModules.mjs", "echo '[]\n' > src/core/config/OperationConfig.json", - "node --experimental-modules src/core/config/scripts/generateOpsIndex.mjs", - "node --experimental-modules src/core/config/scripts/generateConfig.mjs", + "node --experimental-modules --no-warnings --no-deprecation src/core/config/scripts/generateOpsIndex.mjs", + "node --experimental-modules --no-warnings --no-deprecation src/core/config/scripts/generateConfig.mjs", "echo '--- Config scripts finished. ---\n'" ].join(";") }, tests: { - command: "node --experimental-modules test/index.mjs" + command: "node --experimental-modules --no-warnings --no-deprecation test/index.mjs" } }, }); diff --git a/package-lock.json b/package-lock.json index d98140938e..d6b6b73353 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1811,6 +1811,11 @@ "isarray": "^1.0.0" } }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", @@ -2994,6 +2999,14 @@ } } }, + "ecdsa-sig-formatter": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz", + "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -6493,6 +6506,29 @@ } } }, + "jsonwebtoken": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.3.0.tgz", + "integrity": "sha512-oge/hvlmeJCH+iIz1DwcO7vKPkNGJHhgkspk8OH3VKlw+mbi42WtD4ig1+VXRln765vxptAv+xT26Fd3cteqag==", + "requires": { + "jws": "^3.1.5", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -6510,6 +6546,25 @@ "resolved": "https://registry.npmjs.org/jsrsasign/-/jsrsasign-8.0.12.tgz", "integrity": "sha1-Iqu5ZW00owuVMENnIINeicLlwxY=" }, + "jwa": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz", + "integrity": "sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.10", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.5.tgz", + "integrity": "sha512-GsCSexFADNQUr8T5HPJvayTjvPIfoyJPtLQBwn5a4WZQchcrPMPMAWcC1AzJVRDKyD6ZPROPAxgv6rfHViO4uQ==", + "requires": { + "jwa": "^1.1.5", + "safe-buffer": "^5.0.1" + } + }, "kbpgp": { "version": "2.0.77", "resolved": "https://registry.npmjs.org/kbpgp/-/kbpgp-2.0.77.tgz", @@ -6724,17 +6779,35 @@ "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=", "dev": true }, + "lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" + }, + "lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" + }, + "lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" + }, + "lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" + }, "lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", - "dev": true + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" }, "lodash.isstring": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=", - "dev": true + "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" }, "lodash.mergewith": { "version": "4.6.1", @@ -6742,6 +6815,11 @@ "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==", "dev": true }, + "lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" + }, "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", @@ -9118,8 +9196,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-json-parse": { "version": "1.0.1", diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 87fa50d251..ca762f1df9 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -89,10 +89,10 @@ "Derive EVP key", "Bcrypt", "Scrypt", - "Pseudo-Random Number Generator", "JWT Sign", "JWT Verify", - "JWT Decode" + "JWT Decode", + "Pseudo-Random Number Generator" ] }, { diff --git a/src/core/operations/JWTDecode.mjs b/src/core/operations/JWTDecode.mjs index cf7945ace4..2166a447de 100644 --- a/src/core/operations/JWTDecode.mjs +++ b/src/core/operations/JWTDecode.mjs @@ -6,6 +6,7 @@ import Operation from "../Operation"; import jwt from "jsonwebtoken"; +import OperationError from "../errors/OperationError"; /** * JWT Decode operation @@ -20,12 +21,11 @@ class JWTDecode extends Operation { this.name = "JWT Decode"; this.module = "Crypto"; - this.description = "Decodes a JSON Web Token without checking whether the provided secret / private key is valid."; - this.infoURL = "https://jwt.io"; + this.description = "Decodes a JSON Web Token without checking whether the provided secret / private key is valid. Use 'JWT Verify' to check if the signature is valid as well."; + this.infoURL = "https://wikipedia.org/wiki/JSON_Web_Token"; this.inputType = "string"; this.outputType = "JSON"; - this.args = [ - ]; + this.args = []; } /** @@ -35,9 +35,14 @@ class JWTDecode extends Operation { */ run(input, args) { try { - return jwt.decode(input); + const decoded = jwt.decode(input, { + json: true, + complete: true + }); + + return decoded.payload; } catch (err) { - return err; + throw new OperationError(err); } } diff --git a/src/core/operations/JWTSign.mjs b/src/core/operations/JWTSign.mjs index d9eb757442..b3f79b5784 100644 --- a/src/core/operations/JWTSign.mjs +++ b/src/core/operations/JWTSign.mjs @@ -6,6 +6,7 @@ import Operation from "../Operation"; import jwt from "jsonwebtoken"; +import OperationError from "../errors/OperationError"; /** * JWT Sign operation @@ -20,18 +21,18 @@ class JWTSign extends Operation { this.name = "JWT Sign"; this.module = "Crypto"; - this.description = "Signs a JSON object as a JSON Web Token using a provided secret / private key."; - this.infoURL = "https://jwt.io/"; + this.description = "Signs a JSON object as a JSON Web Token using a provided secret / private key.

The key should be either the secret for HMAC algorithms or the PEM-encoded private key for RSA and ECDSA."; + this.infoURL = "https://wikipedia.org/wiki/JSON_Web_Token"; this.inputType = "JSON"; this.outputType = "string"; this.args = [ { - name: "Private / Secret Key", + name: "Private/Secret Key", type: "text", - value: "secret_cat" + value: "secret" }, { - name: "Signing Algorithm", + name: "Signing algorithm", type: "option", value: [ "HS256", @@ -56,7 +57,16 @@ class JWTSign extends Operation { */ run(input, args) { const [key, algorithm] = args; - return jwt.sign(input, key, { algorithm: algorithm === "None" ? "none" : algorithm }); + + try { + return jwt.sign(input, key, { + algorithm: algorithm === "None" ? "none" : algorithm + }); + } catch (err) { + throw new OperationError(`Error: Have you entered the key correctly? The key should be either the secret for HMAC algorithms or the PEM-encoded private key for RSA and ECDSA. + +${err}`); + } } } diff --git a/src/core/operations/JWTVerify.mjs b/src/core/operations/JWTVerify.mjs index bbacdce14f..651e766251 100644 --- a/src/core/operations/JWTVerify.mjs +++ b/src/core/operations/JWTVerify.mjs @@ -6,6 +6,7 @@ import Operation from "../Operation"; import jwt from "jsonwebtoken"; +import OperationError from "../errors/OperationError"; /** * JWT Verify operation @@ -20,15 +21,15 @@ class JWTVerify extends Operation { this.name = "JWT Verify"; this.module = "Crypto"; - this.description = "Verifies that a JSON Web Token is valid and has been signed with the provided secret / private key."; - this.infoURL = "https://jwt.io/"; + this.description = "Verifies that a JSON Web Token is valid and has been signed with the provided secret / private key.

The key should be either the secret for HMAC algorithms or the PEM-encoded private key for RSA and ECDSA."; + this.infoURL = "https://wikipedia.org/wiki/JSON_Web_Token"; this.inputType = "string"; this.outputType = "JSON"; this.args = [ { - name: "Private / Secret Key", + name: "Private/Secret Key", type: "text", - value: "secret_cat" + value: "secret" }, ]; } @@ -42,14 +43,20 @@ class JWTVerify extends Operation { const [key] = args; try { - return jwt.verify(input, key, { algorithms: [ + const verified = jwt.verify(input, key, { algorithms: [ "HS256", "HS384", "HS512", "none" ]}); + + if (verified.hasOwnProperty("name") && verified.name === "JsonWebTokenError") { + throw new OperationError(verified.message); + } + + return verified; } catch (err) { - return err; + throw new OperationError(err); } } diff --git a/test/tests/operations/JWTDecode.mjs b/test/tests/operations/JWTDecode.mjs index d355b83246..834fd5f242 100644 --- a/test/tests/operations/JWTDecode.mjs +++ b/test/tests/operations/JWTDecode.mjs @@ -12,11 +12,11 @@ const outputObject = JSON.stringify({ String: "SomeString", Number: 42, iat: 1 -}); +}, null, 4); TestRegister.addTests([ { - name: "JSON Decode: HS", + name: "JWT Decode: HS", input: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.0ha6-j4FwvEIKPVZ-hf3S_R9Hy_UtXzq4dnedXcUrXk", expectedOutput: outputObject, recipeConfig: [ @@ -27,7 +27,7 @@ TestRegister.addTests([ ], }, { - name: "JSON Decode: RS", + name: "JWT Decode: RS", input: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.MjEJhtZk2nXzigi24piMzANmrj3mILHJcDl0xOjl5a8EgdKVL1oaMEjTkMQp5RA8YrqeRBFaX-BGGCKOXn5zPY1DJwWsBUyN9C-wGR2Qye0eogH_3b4M9EW00TPCUPXm2rx8URFj7Wg9VlsmrGzLV2oKkPgkVxuFSxnpO3yjn1Y", expectedOutput: outputObject, recipeConfig: [ @@ -38,7 +38,7 @@ TestRegister.addTests([ ], }, { - name: "JSON Decode: ES", + name: "JWT Decode: ES", input: "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.WkECT51jSfpRkcpQ4x0h5Dwe7CFBI6u6Et2gWp91HC7mpN_qCFadRpsvJLtKubm6cJTLa68xtei0YrDD8fxIUA", expectedOutput: outputObject, recipeConfig: [ diff --git a/test/tests/operations/JWTSign.mjs b/test/tests/operations/JWTSign.mjs index f0432cbfe9..36eff88831 100644 --- a/test/tests/operations/JWTSign.mjs +++ b/test/tests/operations/JWTSign.mjs @@ -12,7 +12,7 @@ const inputObject = JSON.stringify({ String: "SomeString", Number: 42, iat: 1 -}); +}, null, 4); const hsKey = "secret_cat"; const rsKey = `-----BEGIN RSA PRIVATE KEY----- @@ -38,7 +38,7 @@ OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r TestRegister.addTests([ { - name: "JSON Sign: HS256", + name: "JWT Sign: HS256", input: inputObject, expectedOutput: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.0ha6-j4FwvEIKPVZ-hf3S_R9Hy_UtXzq4dnedXcUrXk", recipeConfig: [ @@ -49,7 +49,7 @@ TestRegister.addTests([ ], }, { - name: "JSON Sign: HS384", + name: "JWT Sign: HS384", input: inputObject, expectedOutput: "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ._bPK-Y3mIACConbJqkGFMQ_L3vbxgKXy9gSxtL9hA5XTganozTSXxD0vX0N1yT5s", recipeConfig: [ @@ -60,7 +60,7 @@ TestRegister.addTests([ ], }, { - name: "JSON Sign: HS512", + name: "JWT Sign: HS512", input: inputObject, expectedOutput: "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.vZIJU4XYMFt3FLE1V_RZOxEetmV4RvxtPZQGzJthK_d47pjwlEb6pQE23YxHFmOj8H5RLEdqqLPw4jNsOyHRzA", recipeConfig: [ @@ -71,7 +71,7 @@ TestRegister.addTests([ ], }, { - name: "JSON Sign: ES256", + name: "JWT Sign: ES256", input: inputObject, expectedOutput: inputObject, recipeConfig: [ @@ -86,7 +86,7 @@ TestRegister.addTests([ ], }, { - name: "JSON Sign: ES384", + name: "JWT Sign: ES384", input: inputObject, expectedOutput: inputObject, recipeConfig: [ @@ -101,7 +101,7 @@ TestRegister.addTests([ ], }, { - name: "JSON Sign: ES512", + name: "JWT Sign: ES512", input: inputObject, expectedOutput: inputObject, recipeConfig: [ @@ -116,7 +116,7 @@ TestRegister.addTests([ ], }, { - name: "JSON Sign: RS256", + name: "JWT Sign: RS256", input: inputObject, expectedOutput: inputObject, recipeConfig: [ @@ -131,7 +131,7 @@ TestRegister.addTests([ ], }, { - name: "JSON Sign: RS384", + name: "JWT Sign: RS384", input: inputObject, expectedOutput: inputObject, recipeConfig: [ @@ -146,7 +146,7 @@ TestRegister.addTests([ ], }, { - name: "JSON Sign: RS512", + name: "JWT Sign: RS512", input: inputObject, expectedOutput: inputObject, recipeConfig: [ diff --git a/test/tests/operations/JWTVerify.mjs b/test/tests/operations/JWTVerify.mjs index 94e1074bf6..bdf2c84346 100644 --- a/test/tests/operations/JWTVerify.mjs +++ b/test/tests/operations/JWTVerify.mjs @@ -12,12 +12,9 @@ const outputObject = JSON.stringify({ String: "SomeString", Number: 42, iat: 1 -}); +}, null, 4); -const invalidAlgorithm = JSON.stringify({ - name: "JsonWebTokenError", - message: "invalid algorithm" -}); +const invalidAlgorithm = "JsonWebTokenError: invalid algorithm"; const hsKey = "secret_cat"; const rsKey = `-----BEGIN RSA PRIVATE KEY----- @@ -43,7 +40,7 @@ OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r TestRegister.addTests([ { - name: "JSON Verify: HS", + name: "JWT Verify: HS", input: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.0ha6-j4FwvEIKPVZ-hf3S_R9Hy_UtXzq4dnedXcUrXk", expectedOutput: outputObject, recipeConfig: [ @@ -54,7 +51,7 @@ TestRegister.addTests([ ], }, { - name: "JSON Verify: RS", + name: "JWT Verify: RS", input: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.MjEJhtZk2nXzigi24piMzANmrj3mILHJcDl0xOjl5a8EgdKVL1oaMEjTkMQp5RA8YrqeRBFaX-BGGCKOXn5zPY1DJwWsBUyN9C-wGR2Qye0eogH_3b4M9EW00TPCUPXm2rx8URFj7Wg9VlsmrGzLV2oKkPgkVxuFSxnpO3yjn1Y", expectedOutput: invalidAlgorithm, recipeConfig: [ @@ -65,7 +62,7 @@ TestRegister.addTests([ ], }, { - name: "JSON Verify: ES", + name: "JWT Verify: ES", input: "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJTdHJpbmciOiJTb21lU3RyaW5nIiwiTnVtYmVyIjo0MiwiaWF0IjoxfQ.WkECT51jSfpRkcpQ4x0h5Dwe7CFBI6u6Et2gWp91HC7mpN_qCFadRpsvJLtKubm6cJTLa68xtei0YrDD8fxIUA", expectedOutput: invalidAlgorithm, recipeConfig: [