diff --git a/package-lock.json b/package-lock.json
index d910c19e..28647150 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -52,14 +52,14 @@
"highlight.js": "^11.11.1",
"ieee754": "^1.2.1",
"jimp": "^1.6.0",
- "jq-web": "^0.6.2",
+ "jq-wasm": "^1.1.0-jq-1.8.1",
"jquery": "3.7.1",
"js-sha3": "^0.9.3",
"jsesc": "^3.1.0",
"json5": "^2.2.3",
"jsonata": "^2.1.0",
"jsonpath-plus": "^10.4.0",
- "jsonwebtoken": "8.5.1",
+ "jsonwebtoken": "9.0.0",
"jsqr": "^1.4.0",
"jsrsasign": "^11.1.1",
"kbpgp": "^2.1.17",
@@ -12442,11 +12442,11 @@
"integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==",
"license": "BSD-3-Clause"
},
- "node_modules/jq-web": {
- "version": "0.6.2",
- "resolved": "https://registry.npmjs.org/jq-web/-/jq-web-0.6.2.tgz",
- "integrity": "sha512-+7XvjBYwTx4vP5PYkf6Q6orubO/v+UgMU6By1GritrmShr9QpT3UKa4ANzXWQfhdqtBnQYXsm7ZNbdIHT6tYpQ==",
- "license": "ISC"
+ "node_modules/jq-wasm": {
+ "version": "1.1.0-jq-1.8.1",
+ "resolved": "https://registry.npmjs.org/jq-wasm/-/jq-wasm-1.1.0-jq-1.8.1.tgz",
+ "integrity": "sha512-lWfu34lpDFIygOYcL5TzxhZIApDR9iR5XywcVoyUAZ6jlQrj8HKHOKeCcHgUm2dE9RVdbP3eqNAKGLuj+k4seQ==",
+ "license": "MIT"
},
"node_modules/jquery": {
"version": "3.7.1",
@@ -12627,34 +12627,31 @@
}
},
"node_modules/jsonwebtoken": {
- "version": "8.5.1",
- "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
- "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz",
+ "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==",
"license": "MIT",
"dependencies": {
"jws": "^3.2.2",
- "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",
+ "lodash": "^4.17.21",
"ms": "^2.1.1",
- "semver": "^5.6.0"
+ "semver": "^7.3.8"
},
"engines": {
- "node": ">=4",
- "npm": ">=1.4.28"
+ "node": ">=12",
+ "npm": ">=6"
}
},
"node_modules/jsonwebtoken/node_modules/semver": {
- "version": "5.7.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
- "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"license": "ISC",
"bin": {
- "semver": "bin/semver"
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
}
},
"node_modules/jsqr": {
@@ -12965,18 +12962,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/lodash.includes": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
- "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
- "license": "MIT"
- },
- "node_modules/lodash.isboolean": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
- "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
- "license": "MIT"
- },
"node_modules/lodash.isfinite": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz",
@@ -12984,28 +12969,11 @@
"dev": true,
"license": "MIT"
},
- "node_modules/lodash.isinteger": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
- "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
- "license": "MIT"
- },
- "node_modules/lodash.isnumber": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
- "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
- "license": "MIT"
- },
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
- "license": "MIT"
- },
- "node_modules/lodash.isstring": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
- "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
+ "dev": true,
"license": "MIT"
},
"node_modules/lodash.merge": {
@@ -13015,12 +12983,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/lodash.once": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
- "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
- "license": "MIT"
- },
"node_modules/lodash.union": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
diff --git a/package.json b/package.json
index 537478e3..c7ee8909 100644
--- a/package.json
+++ b/package.json
@@ -134,14 +134,14 @@
"highlight.js": "^11.11.1",
"ieee754": "^1.2.1",
"jimp": "^1.6.0",
- "jq-web": "^0.6.2",
+ "jq-wasm": "^1.1.0-jq-1.8.1",
"jquery": "3.7.1",
"js-sha3": "^0.9.3",
"jsesc": "^3.1.0",
"json5": "^2.2.3",
"jsonata": "^2.1.0",
"jsonpath-plus": "^10.4.0",
- "jsonwebtoken": "8.5.1",
+ "jsonwebtoken": "9.0.0",
"jsqr": "^1.4.0",
"jsrsasign": "^11.1.1",
"kbpgp": "^2.1.17",
diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json
index aac00ca1..cee966b0 100644
--- a/src/core/config/Categories.json
+++ b/src/core/config/Categories.json
@@ -164,7 +164,10 @@
"Typex",
"Lorenz",
"Colossus",
- "SIGABA"
+ "SIGABA",
+ "Flask Session Decode",
+ "Flask Session Sign",
+ "Flask Session Verify"
]
},
{
@@ -222,6 +225,9 @@
"Subtract",
"Multiply",
"Divide",
+ "Modular Exponentiation",
+ "Modular Inverse",
+ "Extended GCD",
"Mean",
"Median",
"Standard Deviation",
@@ -551,6 +557,7 @@
"P-list Viewer",
"Disassemble x86",
"Pseudo-Random Number Generator",
+ "Pseudo-Random Integer Generator",
"Generate De Bruijn Sequence",
"Generate UUID",
"Analyse UUID",
diff --git a/src/core/lib/BigIntUtils.mjs b/src/core/lib/BigIntUtils.mjs
new file mode 100644
index 00000000..5ddd8786
--- /dev/null
+++ b/src/core/lib/BigIntUtils.mjs
@@ -0,0 +1,73 @@
+/**
+ * @author p-leriche [philip.leriche@cantab.net]
+ * @copyright Crown Copyright 2025
+ * @license Apache-2.0
+ */
+
+import OperationError from "../errors/OperationError.mjs";
+
+/**
+ * Number theory utilities used by cryptographic operations.
+ *
+ * Currently provides:
+ * - parseBigInt
+ * - Extended Euclidean Algorithm
+ * - Modular Exponentiation
+ *
+ * Additional algorithms may be added as required.
+ */
+
+/**
+ * parseBigInt helper operation
+ */
+export function parseBigInt(value, param) {
+ const v = (value ?? "").trim();
+ if (/^0x[0-9a-f]+$/i.test(v)) return BigInt(v);
+ if (/^[+-]?[0-9]+$/.test(v)) return BigInt(v);
+ throw new OperationError(param + " must be decimal or hex (0x...)");
+}
+
+/**
+ * Extended Euclidean Algorithm
+ *
+ * Returns [g, x, y] such that:
+ * a*x + b*y = g = gcd(a, b)
+ *
+ * (Uses an iterative algorithm to avoid possible stack overflow)
+ */
+export function egcd(a, b) {
+ let oldR = a, r = b;
+ let oldS = 1n, s = 0n;
+ let oldT = 0n, t = 1n;
+
+ while (r !== 0n) {
+ const quotient = oldR / r;
+
+ [oldR, r] = [r, oldR - quotient * r];
+ [oldS, s] = [s, oldS - quotient * s];
+ [oldT, t] = [t, oldT - quotient * t];
+ }
+
+ // oldR is the gcd
+ // oldS and oldT are the Bézout coefficients
+ return [oldR, oldS, oldT];
+}
+
+/**
+ * Modular exponentiation
+ */
+export function modPow(base, exponent, modulus) {
+ let result = 1n;
+ base %= modulus;
+
+ while (exponent > 0n) {
+ if (exponent & 1n) {
+ result = (result * base) % modulus;
+ }
+ base = (base * base) % modulus;
+ exponent >>= 1n;
+ }
+
+ return result;
+}
+
diff --git a/src/core/operations/FlaskSessionDecode.mjs b/src/core/operations/FlaskSessionDecode.mjs
new file mode 100644
index 00000000..5486357e
--- /dev/null
+++ b/src/core/operations/FlaskSessionDecode.mjs
@@ -0,0 +1,80 @@
+/**
+ * @author ThePlayer372-FR []
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import OperationError from "../errors/OperationError.mjs";
+import { fromBase64 } from "../lib/Base64.mjs";
+
+/**
+ * Flask Session Decode operation
+ */
+class FlaskSessionDecode extends Operation {
+ /**
+ * FlaskSessionDecode constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Flask Session Decode";
+ this.module = "Crypto";
+ this.description = "Decodes the payload of a Flask session cookie (itsdangerous) into JSON.";
+ this.inputType = "string";
+ this.outputType = "JSON";
+ this.args = [
+ {
+ name: "View TimeStamp",
+ type: "boolean",
+ value: false
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {Object[]}
+ */
+ run(input, args) {
+ input = input.trim();
+ const parts = input.split(".");
+ if (parts.length !== 3) {
+ throw new OperationError("Invalid Flask token format. Expected payload.timestamp.signature");
+ }
+
+ const payloadB64 = parts[0];
+ const time = parts[1];
+
+ const timeB64 = time.replace(/-/g, "+").replace(/_/g, "/");
+ const binary = fromBase64(timeB64);
+ const bytes = new Uint8Array(4);
+ for (let i = 0; i < 4; i++) {
+ bytes[i] = binary.charCodeAt(i);
+ }
+ const view = new DataView(bytes.buffer);
+ const timestamp = view.getInt32(0, false);
+
+ const base64 = payloadB64.replace(/-/g, "+").replace(/_/g, "/");
+ const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, "=");
+ let payloadJson;
+ try {
+ payloadJson = fromBase64(padded);
+ } catch (e) {
+ throw new OperationError("Invalid Base64 payload");
+ }
+
+ try {
+ let data = JSON.parse(payloadJson);
+
+ if (args[0]) {
+ data = {payload: data, timestamp: timestamp};
+ }
+ return data;
+ } catch (e) {
+ throw new OperationError("Unable to decode JSON payload: " + e.message);
+ }
+ }
+}
+
+export default FlaskSessionDecode;
diff --git a/src/core/operations/FlaskSessionSign.mjs b/src/core/operations/FlaskSessionSign.mjs
new file mode 100644
index 00000000..01ee8b1d
--- /dev/null
+++ b/src/core/operations/FlaskSessionSign.mjs
@@ -0,0 +1,89 @@
+/**
+ * @author ThePlayer372-FR []
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import CryptoApi from "crypto-api/src/crypto-api.mjs";
+import Utils from "../Utils.mjs";
+import { toBase64 } from "../lib/Base64.mjs";
+import OperationError from "../errors/OperationError.mjs";
+
+/**
+ * Flask Session Sign operation
+ */
+class FlaskSessionSign extends Operation {
+ /**
+ * FlaskSessionSign constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Flask Session Sign";
+ this.module = "Crypto";
+ this.description = "Signs a JSON payload to produce a Flask session cookie (itsdangerous HMAC).";
+ this.inputType = "JSON";
+ this.outputType = "string";
+ this.args = [
+ {
+ name: "Key",
+ type: "toggleString",
+ value: "",
+ toggleValues: ["Hex", "Decimal", "Binary", "Base64", "UTF8", "Latin1"]
+ },
+ {
+ name: "Salt",
+ type: "toggleString",
+ value: "cookie-session",
+ toggleValues: ["UTF8", "Hex", "Decimal", "Binary", "Base64", "Latin1"]
+ },
+ {
+ name: "Algorithm",
+ type: "option",
+ value: ["sha1", "sha256"],
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ if (!args[0].string) {
+ throw new OperationError("Secret key required");
+ }
+ const key = Utils.convertToByteString(args[0].string, args[0].option);
+ const salt = Utils.convertToByteString(args[1].string || "cookie-session", args[1].option);
+ const algorithm = args[2] || "sha1";
+
+ const payloadB64 = toBase64(Utils.strToByteArray(JSON.stringify(input)));
+ const payload = payloadB64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
+
+ const derivedKey = CryptoApi.getHmac(key, CryptoApi.getHasher(algorithm));
+ derivedKey.update(salt);
+
+ const currentTimeStamp = Math.ceil(Date.now() / 1000);
+ const buffer = new ArrayBuffer(4);
+ const view = new DataView(buffer);
+ view.setInt32(0, currentTimeStamp, false);
+ const bytes = new Uint8Array(buffer);
+ let binary = "";
+ bytes.forEach(b => binary += String.fromCharCode(b));
+ const timeB64 = toBase64(Utils.strToByteArray(binary));
+ const time = timeB64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
+
+ const data = Utils.convertToByteString(payload + "." + time, "utf8");
+ const sign = CryptoApi.getHmac(derivedKey.finalize(), CryptoApi.getHasher(algorithm));
+ sign.update(data);
+
+ const signB64 = toBase64(sign.finalize());
+ const sign64 = signB64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
+
+ return payload + "." + time + "." + sign64;
+ }
+}
+
+
+export default FlaskSessionSign;
diff --git a/src/core/operations/FlaskSessionVerify.mjs b/src/core/operations/FlaskSessionVerify.mjs
new file mode 100644
index 00000000..7603ba1f
--- /dev/null
+++ b/src/core/operations/FlaskSessionVerify.mjs
@@ -0,0 +1,136 @@
+/**
+ * @author ThePlayer372-FR []
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import OperationError from "../errors/OperationError.mjs";
+import CryptoApi from "crypto-api/src/crypto-api.mjs";
+import Utils from "../Utils.mjs";
+import { toBase64, fromBase64 } from "../lib/Base64.mjs";
+
+/**
+ * Flask Session Verify operation
+ */
+class FlaskSessionVerify extends Operation {
+ /**
+ * FlaskSessionVerify constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Flask Session Verify";
+ this.module = "Crypto";
+ this.description = "Verifies the HMAC signature of a Flask session cookie (itsdangerous) generated.";
+ this.inputType = "string";
+ this.outputType = "JSON";
+ this.args = [
+ {
+ name: "Key",
+ type: "toggleString",
+ value: "",
+ toggleValues: ["Hex", "Decimal", "Binary", "Base64", "UTF8", "Latin1"]
+ },
+ {
+ name: "Salt",
+ type: "toggleString",
+ value: "cookie-session",
+ toggleValues: ["UTF8", "Hex", "Decimal", "Binary", "Base64", "Latin1"]
+ },
+ {
+ name: "Algorithm",
+ type: "option",
+ value: ["sha1", "sha256"],
+ },
+ {
+ name: "View TimeStamp",
+ type: "boolean",
+ value: true
+ }
+ ];
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+
+ if (!args[0].string) {
+ throw new OperationError("Secret key required");
+ }
+
+ const key = Utils.convertToByteString(args[0].string, args[0].option);
+ const salt = Utils.convertToByteString(args[1].string || "cookie-session", args[1].option);
+ const algorithm = args[2] || "sha1";
+
+ input = input.trim();
+
+ const parts = input.split(".");
+
+ if (parts.length !== 3) {
+ throw new OperationError("Invalid Flask token format. Expected payload.timestamp.signature");
+ }
+
+ const data = Utils.convertToByteString(parts[0] + "." + parts[1], "utf8");
+
+
+ const derivedKey = CryptoApi.getHmac(key, CryptoApi.getHasher(algorithm));
+ derivedKey.update(salt);
+
+ const sign = CryptoApi.getHmac(derivedKey.finalize(), CryptoApi.getHasher(algorithm));
+ sign.update(data);
+
+ const payloadB64 = parts[0];
+ const base64 = payloadB64.replace(/-/g, "+").replace(/_/g, "/");
+ const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, "=");
+
+ const time = parts[1];
+
+ const timeB64 = time.replace(/-/g, "+").replace(/_/g, "/");
+ const binary = fromBase64(timeB64);
+ const bytes = new Uint8Array(4);
+ for (let i = 0; i < 4; i++) {
+ bytes[i] = binary.charCodeAt(i);
+ }
+ const view = new DataView(bytes.buffer);
+ const timestamp = view.getInt32(0, false);
+
+ let payloadJson;
+ try {
+ payloadJson = fromBase64(padded);
+ } catch (e) {
+ throw new OperationError("Invalid Base64 payload");
+ }
+
+ const signB64 = toBase64(sign.finalize());
+ const sign64 = signB64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
+
+ if (sign64 !== parts[2]) {
+ throw new OperationError("Invalid signature!");
+ }
+
+ try {
+ const decoded = JSON.parse(payloadJson);
+ if (!args[3]) {
+ return {
+ valid: true,
+ payload: decoded,
+ };
+ } else {
+ return {
+ valid: true,
+ payload: decoded,
+ timestamp: timestamp
+ };
+ }
+ } catch (e) {
+ throw new OperationError("Unable to decode JSON payload: " + e.message);
+ }
+
+ }
+}
+
+
+export default FlaskSessionVerify;
diff --git a/src/core/operations/Jq.mjs b/src/core/operations/Jq.mjs
index c1e02b34..4584d1a9 100644
--- a/src/core/operations/Jq.mjs
+++ b/src/core/operations/Jq.mjs
@@ -6,7 +6,7 @@
import Operation from "../Operation.mjs";
import OperationError from "../errors/OperationError.mjs";
-import jq from "jq-web";
+import * as jq from "jq-wasm";
/**
* jq operation
@@ -40,16 +40,15 @@ class Jq extends Operation {
* @returns {string}
*/
run(input, args) {
- const [query] = args;
- let result;
-
- try {
- result = jq.json(input, query);
- } catch (err) {
- throw new OperationError(`Invalid jq expression: ${err.message}`);
- }
-
- return JSON.stringify(result);
+ return (async () => {
+ const [query] = args;
+ try {
+ const result = await jq.json(input, query);
+ return JSON.stringify(result);
+ } catch (err) {
+ throw new OperationError(`Invalid jq expression: ${err.message}`);
+ }
+ })();
}
}
diff --git a/src/core/operations/PseudoRandomIntegerGenerator.mjs b/src/core/operations/PseudoRandomIntegerGenerator.mjs
new file mode 100644
index 00000000..a2d83a92
--- /dev/null
+++ b/src/core/operations/PseudoRandomIntegerGenerator.mjs
@@ -0,0 +1,164 @@
+/**
+ * @author cktgh [chankaitung@gmail.com]
+ * @copyright Crown Copyright 2026
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import OperationError from "../errors/OperationError.mjs";
+import forge from "node-forge";
+import Utils, { isWorkerEnvironment } from "../Utils.mjs";
+import { DELIM_OPTIONS } from "../lib/Delim.mjs";
+
+/**
+ * Pseudo-Random Integer Generator operation
+ */
+class PseudoRandomIntegerGenerator extends Operation {
+
+ // in theory 2**53 is the max range, but we use Number.MAX_SAFE_INTEGER (2**53 - 1) as it is more consistent.
+ static MAX_RANGE = Number.MAX_SAFE_INTEGER;
+ // arbitrary choice
+ static BUFFER_SIZE = 1024;
+
+ /**
+ * PseudoRandomIntegerGenerator constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "Pseudo-Random Integer Generator";
+ this.module = "Ciphers";
+ this.description = "A cryptographically-secure pseudo-random number generator (PRNG).
Generates random integers within a specified range using the browser's built-in crypto.getRandomValues() method if available.
The supported range of integers is from -(2^53 - 1) to (2^53 - 1).";
+ this.infoURL = "https://wikipedia.org/wiki/Pseudorandom_number_generator";
+ this.inputType = "string";
+ this.outputType = "string";
+ this.args = [
+ {
+ "name": "Number of Integers",
+ "type": "number",
+ "value": 1,
+ "min": 1
+ },
+ {
+ "name": "Min Value",
+ "type": "number",
+ "value": 0,
+ "min": Number.MIN_SAFE_INTEGER,
+ "max": Number.MAX_SAFE_INTEGER
+ },
+ {
+ "name": "Max Value",
+ "type": "number",
+ "value": 99,
+ "min": Number.MIN_SAFE_INTEGER,
+ "max": Number.MAX_SAFE_INTEGER
+ },
+ {
+ "name": "Delimiter",
+ "type": "option",
+ "value": DELIM_OPTIONS
+ },
+ {
+ "name": "Output",
+ "type": "option",
+ "value": ["Raw", "Hex", "Decimal"]
+ }
+ ];
+
+ // not using BigUint64Array to avoid BigInt handling overhead
+ this.randomBuffer = new Uint32Array(PseudoRandomIntegerGenerator.BUFFER_SIZE);
+ this.randomBufferOffset = PseudoRandomIntegerGenerator.BUFFER_SIZE;
+ }
+
+ /**
+ * @param {string} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ const [numInts, minInt, maxInt, delimiter, outputType] = args;
+
+ if (minInt === null || maxInt === null) return "";
+
+ const min = Math.ceil(minInt);
+ const max = Math.floor(maxInt);
+ const delim = Utils.charRep(delimiter || "Space");
+
+ if (!Number.isSafeInteger(min) || !Number.isSafeInteger(max)) {
+ throw new OperationError("Min and Max must be between `-(2^53 - 1)` and `2^53 - 1`.");
+ }
+ if (min > max) {
+ throw new OperationError("Min cannot be larger than Max.");
+ }
+ const range = max - min + 1; // inclusive range
+ if (range > PseudoRandomIntegerGenerator.MAX_RANGE) {
+ throw new OperationError("Range between Min and Max cannot be larger than `2^53`");
+ }
+
+ // as large as possible while divisible by range
+ const rejectionThreshold = PseudoRandomIntegerGenerator.MAX_RANGE - (PseudoRandomIntegerGenerator.MAX_RANGE % range);
+ const output = [];
+ for (let i = 0; i < numInts; i++) {
+ const result = this._generateRandomValue(rejectionThreshold);
+ const intValue = min + (result % range);
+
+ switch (outputType) {
+ case "Hex":
+ output.push(intValue.toString(16));
+ break;
+ case "Decimal":
+ output.push(intValue.toString(10));
+ break;
+ case "Raw":
+ default:
+ output.push(Utils.chr(intValue));
+ }
+ }
+
+ if (outputType === "Raw") {
+ return output.join("");
+ }
+ return output.join(delim);
+ }
+
+ /**
+ * Generate a random value, result will be less than the rejection threshold (exclusive).
+ *
+ * @param {number} rejectionThreshold
+ * @returns {number}
+ */
+ _generateRandomValue(rejectionThreshold) {
+ let result;
+ do {
+ if (this.randomBufferOffset + 2 > this.randomBuffer.length) {
+ this._resetRandomBuffer();
+ }
+ // stitching a 53 bit number; not using BigUint64Array to avoid BigInt handling overhead
+ result = (this.randomBuffer[this.randomBufferOffset++] & 0x1f_ffff) * 0x1_0000_0000 +
+ this.randomBuffer[this.randomBufferOffset++];
+ } while (result >= rejectionThreshold);
+
+ return result;
+ }
+
+ /**
+ * Fill random buffer with new random values and rseet the offset.
+ */
+ _resetRandomBuffer() {
+ if (isWorkerEnvironment() && self.crypto) {
+ self.crypto.getRandomValues(this.randomBuffer);
+ } else {
+ const bytes = forge.random.getBytesSync(this.randomBuffer.length * 4);
+ for (let j = 0; j < this.randomBuffer.length; j++) {
+ this.randomBuffer[j] = (bytes.charCodeAt(j * 4) << 24) |
+ (bytes.charCodeAt(j * 4 + 1) << 16) |
+ (bytes.charCodeAt(j * 4 + 2) << 8) |
+ bytes.charCodeAt(j * 4 + 3);
+ }
+ }
+ this.randomBufferOffset = 0;
+ }
+
+}
+
+export default PseudoRandomIntegerGenerator;
diff --git a/src/web/stylesheets/utils/_overrides.css b/src/web/stylesheets/utils/_overrides.css
index a2f8b029..fec7d857 100755
--- a/src/web/stylesheets/utils/_overrides.css
+++ b/src/web/stylesheets/utils/_overrides.css
@@ -249,6 +249,13 @@ optgroup {
}
+/* Bootstrap form inside CodeMirror editor */
+
+.cm-panel > .bmd-form-group {
+ padding-top: 0;
+}
+
+
/* CodeMirror */
.ͼ2 .cm-specialChar,
diff --git a/tests/browser/02_ops.js b/tests/browser/02_ops.js
index d0b89c3e..dde84f68 100644
--- a/tests/browser/02_ops.js
+++ b/tests/browser/02_ops.js
@@ -50,6 +50,14 @@ module.exports = {
testOp(browser, "Analyse hash", "0123456789abcdef", /CRC-64/);
testOp(browser, "Atbash Cipher", "test input", "gvhg rmkfg");
// testOp(browser, "Avro to JSON", "test input", "test_output");
+ testOp(browser,
+ [
+ "From Hex", "Avro to JSON"
+ ],
+ "4f626a0104166176726f2e736368656d6196017b2274797065223a227265636f7264222c226e616d65223a22736d616c6c222c226669656c6473223a5b7b226e616d65223a226e616d65222c2274797065223a22737472696e67227d5d7d146176726f2e636f646563086e756c6c004e0247632e3702e5b75cdab9a62f1541020e0c6d796e616d654e0247632e3702e5b75cdab9a62f1541",
+ '{"name":"myname"}\n',
+ [[], [false]]
+ );
testOp(browser, "BLAKE2b", "test input", "33ebdc8f38177f3f3f334eeb117a84e11f061bbca4db6b8923e5cec85103f59f415551a5d5a933fdb6305dc7bf84671c2540b463dbfa08ee1895cfaa5bd780b5", ["512", "Hex", { "option": "UTF8", "string": "pass" }]);
testOp(browser, "BLAKE2s", "test input", "defe73d61dfa6e5807e4f9643e159a09ccda6be3c26dcd65f8a9bb38bfc973a7", ["256", "Hex", { "option": "UTF8", "string": "pass" }]);
testOp(browser, "BSON deserialise", "\u0011\u0000\u0000\u0000\u0002a\u0000\u0005\u0000\u0000\u0000test\u0000\u0000", '{\u000A "a": "test"\u000A}');
@@ -206,6 +214,7 @@ module.exports = {
testOpHtml(browser, "Index of Coincidence", "test input", "", /Index of Coincidence: 0.08333333333333333/);
testOpImage(browser, "Invert Image", "files/Hitchhikers_Guide.jpeg");
// testOp(browser, "JPath expression", "test input", "test_output");
+ testOp(browser, "Jq", '{"a":{"b":1}}', '{"b":1}', [".a"]);
testOpHtml(browser, "JSON Beautify", "{a:1}", ".json-dict .json-literal", "1");
// testOp(browser, "JSON Minify", "test input", "test_output");
// testOp(browser, "JSON to CSV", "test input", "test_output");
diff --git a/tests/node/index.mjs b/tests/node/index.mjs
index f6abba40..f872f8f4 100644
--- a/tests/node/index.mjs
+++ b/tests/node/index.mjs
@@ -23,6 +23,7 @@ import "./tests/Dish.mjs";
import "./tests/NodeDish.mjs";
import "./tests/Utils.mjs";
import "./tests/Categories.mjs";
+import "./tests/lib/BigIntUtils.mjs";
const testStatus = {
allTestsPassing: true,
diff --git a/tests/node/tests/lib/BigIntUtils.mjs b/tests/node/tests/lib/BigIntUtils.mjs
new file mode 100644
index 00000000..d75a4893
--- /dev/null
+++ b/tests/node/tests/lib/BigIntUtils.mjs
@@ -0,0 +1,150 @@
+import TestRegister from "../../../lib/TestRegister.mjs";
+import { parseBigInt, egcd, modPow } from "../../../../src/core/lib/BigIntUtils.mjs";
+import it from "../../assertionHandler.mjs";
+import assert from "assert";
+
+TestRegister.addApiTests([
+ // ===== parseBigInt tests =====
+ it("BigIntUtils: parseBigInt - decimal number", () => {
+ const value = parseBigInt("1", "test value");
+ assert.deepStrictEqual(value, BigInt("1"));
+ }),
+
+ it("BigIntUtils: parseBigInt - large decimal", () => {
+ const value = parseBigInt("123456789012345678901234567890", "test value");
+ assert.deepStrictEqual(value, BigInt("123456789012345678901234567890"));
+ }),
+
+ it("BigIntUtils: parseBigInt - hexadecimal lowercase", () => {
+ const value = parseBigInt("0xff", "test value");
+ assert.deepStrictEqual(value, BigInt("255"));
+ }),
+
+ it("BigIntUtils: parseBigInt - hexadecimal uppercase", () => {
+ const value = parseBigInt("0xFF", "test value");
+ assert.deepStrictEqual(value, BigInt("255"));
+ }),
+
+ it("BigIntUtils: parseBigInt - large hexadecimal", () => {
+ const value = parseBigInt("0x123456789ABCDEF", "test value");
+ assert.deepStrictEqual(value, BigInt("0x123456789ABCDEF"));
+ }),
+
+ it("BigIntUtils: parseBigInt - whitespace trimming", () => {
+ const value = parseBigInt(" 42 ", "test value");
+ assert.deepStrictEqual(value, BigInt("42"));
+ }),
+
+ it("BigIntUtils: parseBigInt - invalid input (text)", () => {
+ assert.throws(() => parseBigInt("test", "test value"), {
+ name: "Error",
+ message: "test value must be decimal or hex (0x...)"
+ });
+ }),
+
+ it("BigIntUtils: parseBigInt - invalid input (hex without prefix)", () => {
+ assert.throws(() => parseBigInt("FF", "test value"), {
+ name: "Error",
+ message: "test value must be decimal or hex (0x...)"
+ });
+ }),
+
+ it("BigIntUtils: parseBigInt - invalid input (mixed)", () => {
+ assert.throws(() => parseBigInt("12abc", "test value"), {
+ name: "Error",
+ message: "test value must be decimal or hex (0x...)"
+ });
+ }),
+
+ // ===== egcd tests =====
+ it("BigIntUtils: egcd - basic coprime", () => {
+ const a = BigInt("36");
+ const b = BigInt("48");
+ const gcd = BigInt("12");
+ const bezout1 = BigInt("-1");
+ const bezout2 = BigInt("1");
+ assert.deepStrictEqual(egcd(a, b), [gcd, bezout1, bezout2]);
+ }),
+
+ it("BigIntUtils: egcd - coprime numbers", () => {
+ const [g, x, y] = egcd(BigInt("3"), BigInt("11"));
+ assert.strictEqual(g, BigInt("1"));
+ // Verify Bézout identity: a*x + b*y = gcd
+ assert.strictEqual(BigInt("3") * x + BigInt("11") * y, g);
+ }),
+
+ it("BigIntUtils: egcd - non-coprime numbers", () => {
+ const [g, x, y] = egcd(BigInt("240"), BigInt("46"));
+ assert.strictEqual(g, BigInt("2"));
+ // Verify Bézout identity
+ assert.strictEqual(BigInt("240") * x + BigInt("46") * y, g);
+ }),
+
+ it("BigIntUtils: egcd - with zero", () => {
+ const [g, x, y] = egcd(BigInt("17"), BigInt("0"));
+ assert.strictEqual(g, BigInt("17"));
+ assert.strictEqual(x, BigInt("1"));
+ assert.strictEqual(y, BigInt("0"));
+ }),
+
+ it("BigIntUtils: egcd - identical numbers", () => {
+ const [g, x, y] = egcd(BigInt("42"), BigInt("42"));
+ assert.strictEqual(g, BigInt("42"));
+ // Verify Bézout identity
+ assert.strictEqual(BigInt("42") * x + BigInt("42") * y, g);
+ }),
+
+ it("BigIntUtils: egcd - large numbers", () => {
+ const a = BigInt("123456789012345678901234567890");
+ const b = BigInt("987654321098765432109876543210");
+ const [g, x, y] = egcd(a, b);
+ // Verify Bézout identity
+ assert.strictEqual(a * x + b * y, g);
+ }),
+
+ // ===== modPow tests =====
+ it("BigIntUtils: modPow - basic", () => {
+ // 2^10 mod 1000 = 1024 mod 1000 = 24
+ const result = modPow(BigInt("2"), BigInt("10"), BigInt("1000"));
+ assert.strictEqual(result, BigInt("24"));
+ }),
+
+ it("BigIntUtils: modPow - RSA-like example", () => {
+ // Common RSA public exponent
+ const base = BigInt("123456789");
+ const exp = BigInt("65537");
+ const mod = BigInt("999999999999");
+ const result = modPow(base, exp, mod);
+ // Result should be less than modulus
+ assert(result < mod);
+ assert(result >= BigInt("0"));
+ }),
+
+ it("BigIntUtils: modPow - exponent zero", () => {
+ // Any number^0 = 1
+ const result = modPow(BigInt("999"), BigInt("0"), BigInt("100"));
+ assert.strictEqual(result, BigInt("1"));
+ }),
+
+ it("BigIntUtils: modPow - base zero", () => {
+ // 0^n = 0
+ const result = modPow(BigInt("0"), BigInt("5"), BigInt("100"));
+ assert.strictEqual(result, BigInt("0"));
+ }),
+
+ it("BigIntUtils: modPow - large exponent", () => {
+ // Test with very large exponent (efficient algorithm should handle this)
+ const result = modPow(BigInt("3"), BigInt("1000000"), BigInt("1000000007"));
+ assert(result >= BigInt("0"));
+ assert(result < BigInt("1000000007"));
+ }),
+
+ it("BigIntUtils: modPow - modular inverse verification", () => {
+ // If a*x . 1 (mod m), then modPow(a, 1, m) * x . 1 (mod m)
+ const a = BigInt("3");
+ const m = BigInt("11");
+ const x = BigInt("4"); // inverse of 3 mod 11
+ const result = modPow(a, BigInt("1"), m) * x % m;
+ assert.strictEqual(result, BigInt("1"));
+ }),
+]);
diff --git a/tests/operations/tests/FlaskSession.mjs b/tests/operations/tests/FlaskSession.mjs
new file mode 100644
index 00000000..7becf400
--- /dev/null
+++ b/tests/operations/tests/FlaskSession.mjs
@@ -0,0 +1,246 @@
+/**
+ * Flask Session tests
+ *
+ * @author ThePlayer372-FR []
+ *
+ * @license Apache-2.0
+ */
+
+import TestRegister from "../../lib/TestRegister.mjs";
+
+const validTokenSha1 = "eyJyb2xlIjoic3VwZXJ1c2VyIiwidXNlciI6ImFkbWluIn0.aZ-KEw.E_x6bOhA4GU9t72pMinJUjN-O3I";
+const validTokenSha256 = "eyJyb2xlIjoic3VwZXJ1c2VyIiwidXNlciI6ImFkbWluIn0.aab3Ew.Jsx2DOx_H9anZg0YcvhsASxQ11897EFHeQfS2oja4y8";
+
+const validKey = "mysecretkey";
+const wrongKey = "notTheKey";
+
+const outputObject = {
+ user: "admin",
+ role: "superuser",
+};
+
+const outputVerify = {
+ valid: true,
+ payload: outputObject,
+};
+
+TestRegister.addTests([
+ {
+ name: "Flask Session: Decode",
+ input: validTokenSha1,
+ expectedOutput: outputObject,
+ recipeConfig: [
+ {
+ op: "Flask Session Decode",
+ args: [
+ false
+ ],
+ }
+ ]
+ },
+ {
+ name: "Flask Session: Verify Sha1",
+ input: validTokenSha1,
+ expectedOutput: outputVerify,
+ recipeConfig: [
+ {
+ op: "Flask Session Verify",
+ args: [
+ {
+ string: validKey,
+ option: "UTF8"
+ },
+ {
+ string: "cookie-session",
+ option: "UTF8"
+ },
+ "sha1",
+ false,
+ ],
+ }
+ ]
+ },
+ {
+ name: "Flask Session: Verify Sha256",
+ input: validTokenSha256,
+ expectedOutput: outputVerify,
+ recipeConfig: [
+ {
+ op: "Flask Session Verify",
+ args: [
+ {
+ string: validKey,
+ option: "UTF8"
+ },
+ {
+ string: "cookie-session",
+ option: "UTF8"
+ },
+ "sha256",
+ false,
+ ],
+ }
+ ]
+ },
+ {
+ name: "Flask Session: Sign Sha1",
+ input: outputObject,
+ expectedOutput: outputVerify,
+ recipeConfig: [
+ {
+ op: "Flask Session Sign",
+ args: [
+ {
+ string: validKey,
+ option: "UTF8"
+ },
+ {
+ string: "cookie-session",
+ option: "UTF8"
+ },
+ "sha1"
+ ]
+ },
+ {
+ op: "Flask Session Verify",
+ args: [
+ {
+ string: validKey,
+ option: "UTF8"
+ },
+ {
+ string: "cookie-session",
+ option: "UTF8"
+ },
+ "sha1",
+ false,
+ ],
+ }
+ ]
+ },
+ {
+ name: "Flask Session: Sign Sha256",
+ input: outputObject,
+ expectedOutput: outputVerify,
+ recipeConfig: [
+ {
+ op: "Flask Session Sign",
+ args: [
+ {
+ string: validKey,
+ option: "UTF8"
+ },
+ {
+ string: "cookie-session",
+ option: "UTF8"
+ },
+ "sha256"
+ ]
+ },
+ {
+ op: "Flask Session Verify",
+ args: [
+ {
+ string: validKey,
+ option: "UTF8"
+ },
+ {
+ string: "cookie-session",
+ option: "UTF8"
+ },
+ "sha256",
+ false,
+ ],
+ }
+ ]
+ },
+ {
+ name: "Flask Session: Verify Sha1 Wrong Key",
+ input: validTokenSha1,
+ expectedOutput: "Invalid signature!",
+ recipeConfig: [
+ {
+ op: "Flask Session Verify",
+ args: [
+ {
+ string: wrongKey,
+ option: "UTF8"
+ },
+ {
+ string: "cookie-session",
+ option: "UTF8"
+ },
+ "sha1",
+ false,
+ ],
+ }
+ ]
+ },
+ {
+ name: "Flask Session: Verify Sha256 Wrong Key",
+ input: validTokenSha256,
+ expectedOutput: "Invalid signature!",
+ recipeConfig: [
+ {
+ op: "Flask Session Verify",
+ args: [
+ {
+ string: wrongKey,
+ option: "UTF8"
+ },
+ {
+ string: "cookie-session",
+ option: "UTF8"
+ },
+ "sha256",
+ false,
+ ],
+ }
+ ]
+ },
+ {
+ name: "Flask Session: Verify Sha1 Wrong Salt",
+ input: validTokenSha1,
+ expectedOutput: "Invalid signature!",
+ recipeConfig: [
+ {
+ op: "Flask Session Verify",
+ args: [
+ {
+ string: validKey,
+ option: "UTF8"
+ },
+ {
+ string: "notTheSalt",
+ option: "UTF8"
+ },
+ "sha1",
+ false,
+ ],
+ }
+ ]
+ },
+ {
+ name: "Flask Session: Verify Sha256 Wrong Salt",
+ input: validTokenSha256,
+ expectedOutput: "Invalid signature!",
+ recipeConfig: [
+ {
+ op: "Flask Session Verify",
+ args: [
+ {
+ string: validKey,
+ option: "UTF8"
+ },
+ {
+ string: "notTheSalt",
+ option: "UTF8"
+ },
+ "sha256",
+ false,
+ ],
+ }
+ ]
+ },
+
+]);
diff --git a/tests/operations/tests/JWTSign.mjs b/tests/operations/tests/JWTSign.mjs
index a7752138..9954174b 100644
--- a/tests/operations/tests/JWTSign.mjs
+++ b/tests/operations/tests/JWTSign.mjs
@@ -15,7 +15,7 @@ const inputObject = JSON.stringify({
}, null, 4);
const hsKey = "secret_cat";
-const rsKey = `-----BEGIN RSA PRIVATE KEY-----
+const rsKey1024 = `-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw
33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW
+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQAB
@@ -30,11 +30,52 @@ fSSjAkLRi54PKJ8TFUeOP15h9sQzydI8zJU+upvDEKZsZc/UhT/SySDOxQ4G/523
Y0sz/OZtSWcol/UMgQJALesy++GdvoIDLfJX5GBQpuFgFenRiRDabxrE9MNUZ2aP
FaFp+DyAe+b4nDwuJaW2LURbr8AEZga7oQj0uYxcYw==
-----END RSA PRIVATE KEY-----`;
-const esKey = `-----BEGIN PRIVATE KEY-----
+const rsKey2048 = `-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEAk0VOoksAblwP82DALTG6xGC86Hfho3nChbcPGWyqn+ScfHBF
+cg3SeKyy6aWCyLcKfNwE5cPYzuYvVBsZyIrdfFOuV90D/aRYbuw6UkKR3cmmy9qE
+qvu05dogvc0BcmkwbC37Q8JnsZBRcosoLGgTFxcK+LXdsG7DukajpsGesxQjOLb2
+1jnx+ypzx74xvj7grqlXkxeDKr22q7QkO3A1ApoOuJRAU+SjEEZmqdXzRery2RWx
+hkWbCXuQw4PnW5Lh3Wwabnu7XKVIa6wJa1pqL2IAxmlZ0bvGTfjtO5ggNfgJk5V4
+bGSOXnsplpG71AWMrK2q6NqHjFIE1szEycUKrwIDAQABAoIBAAivyt6Zy/G2g8kC
+852hfvcRubLV92eRdAmNGFqTOqaUcS00i3QZyp4MRGqxtOV/88y/nEOtP1RHkZJw
+HXTjHq4JsDvwhnQR8JbCX6z1zkLQdS01u3jrwJTaPpooxdATfPlfO6CYjqM+SapB
+o7dS1ZAZb4U8vPx+MWoDEVNxvO7/xyqho1Oc4H9MwqQUiyG2WfIoqxLSrBYcambv
+RmySwTIpgQZTr61EeWf/0eWpV0iEYbSnkB/VaKW+5tg4gCjPgy5v6/LQ0u/pzlYz
+ayCL3xN2rp0tigXsiiWz3cM5gDsnatK4nVNRs9y3JSZpWpI236ZfZjs8Lts+WBUw
+hAEoE9kCgYEAyEIGD1A7R/t5EYk5HhHDH5tGdyxejAcQL5AIz0YnTZU8Iixyc7FR
+uDmAMiuKIcJY/nUlxZjSxNc3MkOfZNggQvf9ONrt+ftQ1yyTjv+019NfU4w4d0Ep
+LNaiAHgaPKimBUZjYXbLgiMXj/1pBaQmgUYTK/VlO3PVdowxxzxMYlMCgYEAvEOG
+GrhVaQV1nAYx86BgZ3wn90hBFXZWGaN+eXUmyrast93Ih3TCSgQDKPuN3pdv/TIe
+cpQv/BxEMpW+6d5Z1NP3GbrLpaZUiUNk8fqw1S3pmD5aWZrYIUaNukAyOxnZVgjv
+EWD9QTpI663gODaeZZTkDYiRNzTzGOg5HtzporUCgYBBOphEtqqImNXnq13qeHip
+O+eo+8/UJpzUEUN9WGmG8NxEeVvSaWin7DrgnKQCuQ5J3Biwk0XcDgoRmks6Ctf/
+WE2oDk/DxGOhowhxZMMgJd6AFUVzOstRqpvcMULCjWB+iV3nqk1Bl3KeWTmzN7O/
+Gfc2s1kFE4btdV7lebObtwKBgE3rkLS8eLVYCh6Cvef9CAms7Im/wRhV+zrvXWh9
+4YljZEdRpy7RV5z03i33N/faLALa3JlF1jp9pIhfTD5Vxk59ULe4hZNRLYoGd+Bj
+hw8kyps1q4WMvkm/fueIrIGjqD2gwvopb4iwy/+n3rbFfHfE0UL8tEXqR3eWnhW1
+D4pFAoGAccR4eMJD43hJWaUQLtsj0RoW9lFKVXj7aqkIIeupXwt7Ic2z/FhCAJi+
+V0MWpd3K6+kPl+ifdt8U4kcYfubPMfJhd7IkMcgQS+yZK1+5xWdRISvI8GpNwIHE
+LUkVkCCadXNNZ7b1nmUKjse95u4IaE6hwAqjSTNb05gPmCfoEjg=
+-----END RSA PRIVATE KEY-----`;
+const esKeyP256 = `-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgevZzL1gdAFr88hb2
OF/2NxApJCzGCEDdfSp6VQO30hyhRANCAAQRWz+jn65BtOMvdyHKcvjBeBSDZH2r
1RTwjmYSi9R/zpBnuQ4EiMnCqfMPWiZqB4QdbAd0E7oH50VpuZ1P087G
-----END PRIVATE KEY-----`;
+const esKeyP384 = `-----BEGIN PRIVATE KEY-----
+MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDDpgCvB2frnLKd7TuWe
+JM1ejXXmr9y/5gskxKuuylLvpQTiDdtLtuhJnvw1/zWKWO6hZANiAAQ5Crhsi5FD
+t55i53dCtdzG9OzCnbDFf/6136ZfEiakDTDeWCdUvNnB3WQEcVBr97BfSWLI9mO+
+T5yzm0RfhgvWIq/tBou+sIDeGp6NQfJwhDhf+JsdeF174gtfNMZGj/s=
+-----END PRIVATE KEY-----`;
+const esKeyP521 = `-----BEGIN PRIVATE KEY-----
+MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIA0dBErrZ5ovKq4Xf/
+iTlRkYxuOfgBZ6+tWIfG13YwthB1XrH06YmteZGNjHHLZEeycwUt0jM4kUb+tOsJ
+3ckhj1ihgYkDgYYABACYgsa8JWKH46CQagwNw14v/L+DIs1WAjJdMXZySjKlRkD9
+LtLMxkbX2H4H4Zl2KzCMJkwTSETzSKNlXvAUJqKbRwHezCp4y5XZN9MOBYdmyylZ
+NOVxwwTouimNkJ0K6A8+/Im5S3PWB8Ra1D6t+bT1WHHhEePZcltSLLFlbIIyot5m
+2w==
+-----END PRIVATE KEY-----`;
TestRegister.addTests([
{
@@ -88,7 +129,24 @@ TestRegister.addTests([
recipeConfig: [
{
op: "JWT Sign",
- args: [esKey, "ES256", "{}"],
+ args: [esKeyP256, "ES256", "{}"],
+ },
+ {
+ op: "JWT Decode",
+ args: []
+ }
+ ],
+ },
+ {
+ name: "JWT Sign: ES384 - P256 key",
+ input: inputObject,
+ expectedOutput: `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.
+
+Error: "alg" parameter "ES384" requires curve "secp384r1".`,
+ recipeConfig: [
+ {
+ op: "JWT Sign",
+ args: [esKeyP256, "ES384", "{}"],
},
{
op: "JWT Decode",
@@ -103,7 +161,7 @@ TestRegister.addTests([
recipeConfig: [
{
op: "JWT Sign",
- args: [esKey, "ES384", "{}"],
+ args: [esKeyP384, "ES384", "{}"],
},
{
op: "JWT Decode",
@@ -118,7 +176,24 @@ TestRegister.addTests([
recipeConfig: [
{
op: "JWT Sign",
- args: [esKey, "ES512", "{}"],
+ args: [esKeyP521, "ES512", "{}"],
+ },
+ {
+ op: "JWT Decode",
+ args: []
+ }
+ ],
+ },
+ {
+ name: "JWT Sign: RS256, weak key",
+ input: inputObject,
+ expectedOutput: `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.
+
+Error: secretOrPrivateKey has a minimum key size of 2048 bits for RS256`,
+ recipeConfig: [
+ {
+ op: "JWT Sign",
+ args: [rsKey1024, "RS256", "{}"],
},
{
op: "JWT Decode",
@@ -133,7 +208,7 @@ TestRegister.addTests([
recipeConfig: [
{
op: "JWT Sign",
- args: [rsKey, "RS256", "{}"],
+ args: [rsKey2048, "RS256", "{}"],
},
{
op: "JWT Decode",
@@ -148,7 +223,7 @@ TestRegister.addTests([
recipeConfig: [
{
op: "JWT Sign",
- args: [rsKey, "RS384", "{}"],
+ args: [rsKey2048, "RS384", "{}"],
},
{
op: "JWT Decode",
@@ -163,7 +238,7 @@ TestRegister.addTests([
recipeConfig: [
{
op: "JWT Sign",
- args: [esKey, "RS512", "{}"],
+ args: [rsKey2048, "RS512", "{}"],
},
{
op: "JWT Decode",