From e92ed13864d5e404fa9986e6972b61ca83a7123a Mon Sep 17 00:00:00 2001 From: n1073645 Date: Thu, 21 Nov 2019 12:53:44 +0000 Subject: [PATCH 01/99] PLIST viewer. --- src/core/operations/PLISTViewer.mjs | 56 +++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/core/operations/PLISTViewer.mjs diff --git a/src/core/operations/PLISTViewer.mjs b/src/core/operations/PLISTViewer.mjs new file mode 100644 index 00000000..1d263468 --- /dev/null +++ b/src/core/operations/PLISTViewer.mjs @@ -0,0 +1,56 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * PLIST Viewer operation + */ +class PLISTViewer extends Operation { + + /** + * PLISTViewer constructor + */ + constructor() { + super(); + + this.name = "PLIST Viewer"; + this.module = "Other"; + this.description = "Converts PLISTXML file into a human readable format."; + this.infoURL = ""; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + /* Example arguments. See the project wiki for full details. + { + name: "First arg", + type: "string", + value: "Don't Panic" + }, + { + name: "Second arg", + type: "number", + value: 42 + } + */ + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + // const [firstArg, secondArg] = args; + + throw new OperationError("Test"); + } + +} + +export default PLISTViewer; From 63bb19d48d06c8e780ba402f2abb0274e0ecc250 Mon Sep 17 00:00:00 2001 From: n1073645 Date: Fri, 22 Nov 2019 08:32:46 +0000 Subject: [PATCH 02/99] Began implementing the PLIST viewer operation --- src/core/config/Categories.json | 1 + src/core/operations/PLISTViewer.mjs | 111 +++++++++++++++++++++++++++- 2 files changed, 108 insertions(+), 4 deletions(-) diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index f663e16d..11e8f076 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -422,6 +422,7 @@ "Frequency distribution", "Index of Coincidence", "Chi Square", + "PLIST Viewer", "Disassemble x86", "Pseudo-Random Number Generator", "Generate UUID", diff --git a/src/core/operations/PLISTViewer.mjs b/src/core/operations/PLISTViewer.mjs index 1d263468..6229d336 100644 --- a/src/core/operations/PLISTViewer.mjs +++ b/src/core/operations/PLISTViewer.mjs @@ -5,7 +5,6 @@ */ import Operation from "../Operation.mjs"; -import OperationError from "../errors/OperationError.mjs"; /** * PLIST Viewer operation @@ -46,11 +45,115 @@ class PLISTViewer extends Operation { * @returns {string} */ run(input, args) { - // const [firstArg, secondArg] = args; - throw new OperationError("Test"); + const reserved = [["","",8], + ["","",6], + ["","",9], + ["","", 6], + ["","",6], + ["","",7], + ["","",6], + ["","",5], + ["",false,8], + ["",true,7]]; + + function the_viewer(input, dictionary_flag){ + var new_dict = new Array(); + var result = new Array(); + var new_key = null; + while(dictionary_flag ? input.slice(0,7) != "" : input.slice(0,8) != ""){ + reserved.forEach( function (elem, index){ + var element = elem[0]; + var endelement = elem[1]; + var length = elem[2]; + let temp = input.slice(0,length); + if(temp == element){ + input = input.slice(length); + if(temp == ""){ + var returned = the_viewer(input, true); + input = returned[1]; + if(new_key) + new_dict[new_key] = returned[0]; + else + new_dict["plist"] = returned[0]; + new_key = null; + }else if(temp == ""){ + var returned = the_viewer(input, false); + if(dictionary_flag) + new_dict[new_key] = returned[0]; + else + result.push(returned[0]); + input = returned[1]; + new_key = null; + }else if(temp == ""){ + var end = input.indexOf(endelement); + new_key = input.slice(0, end); + input = input.slice(end+length+1); + }else if(temp == "" || temp == ""){ + new_dict[new_key] = endelement; + new_key = null; + }else{ + var end = input.indexOf(endelement); + var toadd = input.slice(0, end); + if(temp == "") + toadd = parseInt(toadd); + else if(temp == "") + toadd = parseFloat(toadd); + if(dictionary_flag){ + new_dict[new_key] = toadd; + new_key = null; + }else{ + result.push(toadd); + } + input = input.slice(end+length+1); + } + } + }); + } + if(dictionary_flag){ + input = input.slice(7); + return [new_dict, input]; + }else{ + input = input.slice(8); + return [result, input]; + } + } + + let result = ""; + function print_it(input, depth) { + Object.keys(input).forEach((key, index) => { + if(typeof(input[key]) == "object") { + result += (("\t".repeat(depth)) + key + ": {\n"); + print_it(input[key], depth+1); + result += (("\t".repeat(depth)) + "}\n"); + } else { + result += (("\t".repeat(depth)) + key + " : " + input[key] + "\n"); + } + }); + } + + while (input.indexOf("/, ""); + } + while (input.indexOf("") !== -1){ + input = input.replace(/<\/plist>/, ""); + } + console.log(input); + while(input.indexOf("\n") !== -1) + input = input.replace("\n", ""); + while(input.indexOf("\t") !== -1) + input = input.replace("\t", ""); + while(input.indexOf(" ") !== -1) + input = input.replace(" ", ""); + console.log(input); + input = input.slice(input.indexOf("")+6); + //return input + var other = the_viewer(input, 1); + print_it(other[0],1); + result = "{\n" + result; + result += "}"; + return result; } - } export default PLISTViewer; From 8e1e1d56cadbb1465a323adec3ac544e9c53f3af Mon Sep 17 00:00:00 2001 From: n1073645 Date: Fri, 22 Nov 2019 15:39:43 +0000 Subject: [PATCH 03/99] Plist viewer operation added. --- src/core/operations/PLISTViewer.mjs | 156 ++++++++++------------------ 1 file changed, 55 insertions(+), 101 deletions(-) diff --git a/src/core/operations/PLISTViewer.mjs b/src/core/operations/PLISTViewer.mjs index 6229d336..939b7d1a 100644 --- a/src/core/operations/PLISTViewer.mjs +++ b/src/core/operations/PLISTViewer.mjs @@ -46,112 +46,66 @@ class PLISTViewer extends Operation { */ run(input, args) { - const reserved = [["","",8], - ["","",6], - ["","",9], - ["","", 6], - ["","",6], - ["","",7], - ["","",6], - ["","",5], - ["",false,8], - ["",true,7]]; - - function the_viewer(input, dictionary_flag){ - var new_dict = new Array(); - var result = new Array(); - var new_key = null; - while(dictionary_flag ? input.slice(0,7) != "" : input.slice(0,8) != ""){ - reserved.forEach( function (elem, index){ - var element = elem[0]; - var endelement = elem[1]; - var length = elem[2]; - let temp = input.slice(0,length); - if(temp == element){ - input = input.slice(length); - if(temp == ""){ - var returned = the_viewer(input, true); - input = returned[1]; - if(new_key) - new_dict[new_key] = returned[0]; - else - new_dict["plist"] = returned[0]; - new_key = null; - }else if(temp == ""){ - var returned = the_viewer(input, false); - if(dictionary_flag) - new_dict[new_key] = returned[0]; - else - result.push(returned[0]); - input = returned[1]; - new_key = null; - }else if(temp == ""){ - var end = input.indexOf(endelement); - new_key = input.slice(0, end); - input = input.slice(end+length+1); - }else if(temp == "" || temp == ""){ - new_dict[new_key] = endelement; - new_key = null; - }else{ - var end = input.indexOf(endelement); - var toadd = input.slice(0, end); - if(temp == "") - toadd = parseInt(toadd); - else if(temp == "") - toadd = parseFloat(toadd); - if(dictionary_flag){ - new_dict[new_key] = toadd; - new_key = null; - }else{ - result.push(toadd); - } - input = input.slice(end+length+1); - } - } - }); - } - if(dictionary_flag){ - input = input.slice(7); - return [new_dict, input]; - }else{ - input = input.slice(8); - return [result, input]; - } - } - + // Regexes are designed to transform the xml format into a reasonably more readable string format. + input = input.slice(input.indexOf("/g, "plist => ") + .replace(//g, "{") + .replace(/<\/dict>/g, "}") + .replace(//g, "[") + .replace(/<\/array>/g, "]") + .replace(/.+<\/key>/g, m => `${m.slice(5, m.indexOf(/<\/key>/g)-5)}\t=> `) + .replace(/.+<\/real>/g, m => `${m.slice(6, m.indexOf(/<\/real>/g)-6)}\n`) + .replace(/.+<\/string>/g, m => `${m.slice(8, m.indexOf(/<\/string>/g)-8)}\n`) + .replace(/.+<\/integer>/g, m => `${m.slice(9, m.indexOf(/<\/integer>/g)-9)}\n`) + .replace(//g, m => "false") + .replace(//g, m => "true") + .replace(/<\/plist>/g, "/plist") + .replace(/.+<\/date>/g, m => `${m.slice(6, m.indexOf(/<\/integer>/g)-6)}`) + .replace(/(\s|.)+?<\/data>/g, m => `${m.slice(6, m.indexOf(/<\/data>/g)-6)}`) + .replace(/[ \t\r\f\v]/g, ""); + let result = ""; - function print_it(input, depth) { - Object.keys(input).forEach((key, index) => { - if(typeof(input[key]) == "object") { - result += (("\t".repeat(depth)) + key + ": {\n"); - print_it(input[key], depth+1); - result += (("\t".repeat(depth)) + "}\n"); + + /** + * Formats the input after the regex has replaced all of the relevant parts. + * + * @param {array} input + * @param {number} depthCount + */ + function printIt(input, depthCount) { + if (!(input.length)) + return; + + // If the current position points at a larger dynamic structure. + if (input[0].indexOf("=>") !== -1) { + + // If the LHS also points at a larger structure (nested plists in a dictionary). + if (input[1].indexOf("=>") !== -1) { + result += ("\t".repeat(depthCount)) + input[0].slice(0, -2) + " => " + input[1].slice(0, -2) + " =>\n"; } else { - result += (("\t".repeat(depth)) + key + " : " + input[key] + "\n"); + result += ("\t".repeat(depthCount)) + input[0].slice(0, -2) + " => " + input[1] + "\n"; } - }); + + // Controls the tab depth for how many opening braces there have been. + if (input[1] === "{" || input[1] === "[") { + depthCount += 1; + } + input = input.slice(1); + } else { + // Controls the tab depth for how many closing braces there have been. + if (input[0] === "}" || input[0] === "]") + depthCount--; + + // Has to be here since the formatting breaks otherwise. + result += ("\t".repeat(depthCount)) + input[0] + "\n"; + if (input[0] === "{" || input[0] === "[") + depthCount++; + } + printIt(input.slice(1), depthCount); } - while (input.indexOf("/, ""); - } - while (input.indexOf("") !== -1){ - input = input.replace(/<\/plist>/, ""); - } - console.log(input); - while(input.indexOf("\n") !== -1) - input = input.replace("\n", ""); - while(input.indexOf("\t") !== -1) - input = input.replace("\t", ""); - while(input.indexOf(" ") !== -1) - input = input.replace(" ", ""); - console.log(input); - input = input.slice(input.indexOf("")+6); - //return input - var other = the_viewer(input, 1); - print_it(other[0],1); - result = "{\n" + result; - result += "}"; + input = input.split("\n").filter(e => e !== ""); + printIt(input, 0); return result; } } From 0295d0c9b47d6cd6b30492ce3b77b3741414cde9 Mon Sep 17 00:00:00 2001 From: n1073645 Date: Mon, 25 Nov 2019 10:35:45 +0000 Subject: [PATCH 04/99] Tided up presentation of the PLIST --- src/core/operations/PLISTViewer.mjs | 71 +++++++++++++++++++++-------- 1 file changed, 52 insertions(+), 19 deletions(-) diff --git a/src/core/operations/PLISTViewer.mjs b/src/core/operations/PLISTViewer.mjs index 939b7d1a..8232fb14 100644 --- a/src/core/operations/PLISTViewer.mjs +++ b/src/core/operations/PLISTViewer.mjs @@ -55,7 +55,7 @@ class PLISTViewer extends Operation { .replace(/<\/array>/g, "]") .replace(/.+<\/key>/g, m => `${m.slice(5, m.indexOf(/<\/key>/g)-5)}\t=> `) .replace(/.+<\/real>/g, m => `${m.slice(6, m.indexOf(/<\/real>/g)-6)}\n`) - .replace(/.+<\/string>/g, m => `${m.slice(8, m.indexOf(/<\/string>/g)-8)}\n`) + .replace(/.+<\/string>/g, m => `"${m.slice(8, m.indexOf(/<\/string>/g)-8)}"\n`) .replace(/.+<\/integer>/g, m => `${m.slice(9, m.indexOf(/<\/integer>/g)-9)}\n`) .replace(//g, m => "false") .replace(//g, m => "true") @@ -64,44 +64,77 @@ class PLISTViewer extends Operation { .replace(/(\s|.)+?<\/data>/g, m => `${m.slice(6, m.indexOf(/<\/data>/g)-6)}`) .replace(/[ \t\r\f\v]/g, ""); + /** + * Depending on the type of brace, it will increment the depth and amount of arrays accordingly. + * + * @param {string} elem + * @param {array} vals + * @param {number} offset + */ + function braces(elem, vals,offset) { + let temp = vals.indexOf(elem); + if (temp !== -1) { + depthCount += offset; + if (temp === 1) + arrCount += offset; + } + } + let result = ""; + let arrCount = 0; + let depthCount = 0; /** * Formats the input after the regex has replaced all of the relevant parts. * * @param {array} input - * @param {number} depthCount + * @param {number} index */ - function printIt(input, depthCount) { + function printIt(input, index) { if (!(input.length)) return; + let temp = ""; + const origArr = arrCount; + let currElem = input[0]; + // If the current position points at a larger dynamic structure. - if (input[0].indexOf("=>") !== -1) { + if (currElem.indexOf("=>") !== -1) { // If the LHS also points at a larger structure (nested plists in a dictionary). - if (input[1].indexOf("=>") !== -1) { - result += ("\t".repeat(depthCount)) + input[0].slice(0, -2) + " => " + input[1].slice(0, -2) + " =>\n"; - } else { - result += ("\t".repeat(depthCount)) + input[0].slice(0, -2) + " => " + input[1] + "\n"; - } + if (input[1].indexOf("=>") !== -1) + temp = currElem.slice(0, -2) + " => " + input[1].slice(0, -2) + " =>\n"; + else + temp = currElem.slice(0, -2) + " => " + input[1] + "\n"; - // Controls the tab depth for how many opening braces there have been. - if (input[1] === "{" || input[1] === "[") { - depthCount += 1; - } input = input.slice(1); } else { // Controls the tab depth for how many closing braces there have been. - if (input[0] === "}" || input[0] === "]") - depthCount--; + + braces(currElem, ["}", "]"], -1); // Has to be here since the formatting breaks otherwise. - result += ("\t".repeat(depthCount)) + input[0] + "\n"; - if (input[0] === "{" || input[0] === "[") - depthCount++; + temp = currElem + "\n"; } - printIt(input.slice(1), depthCount); + + currElem = input[0]; + + // Tab out to the correct distance. + result += ("\t".repeat(depthCount)); + + // If it is enclosed in an array show index. + if (arrCount > 0 && currElem !== "]") + result += index.toString() + " => "; + + result += temp; + + // Controls the tab depth for how many opening braces there have been. + braces(currElem, ["{", "["],1); + + // If there has been a new array then reset index. + if (arrCount > origArr) + return printIt(input.slice(1), 0); + return printIt(input.slice(1), ++index); } input = input.split("\n").filter(e => e !== ""); From d8405e5f814e17319dd293fdcddfdeeca2a43f15 Mon Sep 17 00:00:00 2001 From: n1073645 Date: Mon, 25 Nov 2019 10:37:30 +0000 Subject: [PATCH 05/99] Linting on PLIST viewer operation. --- src/core/operations/PLISTViewer.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/operations/PLISTViewer.mjs b/src/core/operations/PLISTViewer.mjs index 8232fb14..b8a90c5b 100644 --- a/src/core/operations/PLISTViewer.mjs +++ b/src/core/operations/PLISTViewer.mjs @@ -71,8 +71,8 @@ class PLISTViewer extends Operation { * @param {array} vals * @param {number} offset */ - function braces(elem, vals,offset) { - let temp = vals.indexOf(elem); + function braces(elem, vals, offset) { + const temp = vals.indexOf(elem); if (temp !== -1) { depthCount += offset; if (temp === 1) @@ -129,7 +129,7 @@ class PLISTViewer extends Operation { result += temp; // Controls the tab depth for how many opening braces there have been. - braces(currElem, ["{", "["],1); + braces(currElem, ["{", "["], 1); // If there has been a new array then reset index. if (arrCount > origArr) From 0259ed8314c0635124d7be316f9e9ec583f4cce0 Mon Sep 17 00:00:00 2001 From: n1073645 Date: Mon, 27 Jan 2020 16:07:54 +0000 Subject: [PATCH 06/99] LS47 implemented, needs linting --- src/core/lib/LS47.mjs | 148 ++++++++++++++++++++++++++++ src/core/operations/LS47Decrypt.mjs | 58 +++++++++++ src/core/operations/LS47Encrypt.mjs | 63 ++++++++++++ 3 files changed, 269 insertions(+) create mode 100644 src/core/lib/LS47.mjs create mode 100644 src/core/operations/LS47Decrypt.mjs create mode 100644 src/core/operations/LS47Encrypt.mjs diff --git a/src/core/lib/LS47.mjs b/src/core/lib/LS47.mjs new file mode 100644 index 00000000..a4ef10a5 --- /dev/null +++ b/src/core/lib/LS47.mjs @@ -0,0 +1,148 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import OperationError from "../errors/OperationError.mjs"; + +let letters = "_abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()"; +let tiles = []; + +export function init_tiles() { + for (let i = 0; i < 49; i++) + tiles.push([letters.charAt(i), [Math.floor(i/7), i % 7]]); +} + +function rotate_down(key, col, n) { + let lines = []; + for (let i = 0; i < 7; i++) + lines.push(key.slice(i*7, (i + 1) * 7)); + let lefts = []; + let mids = []; + let rights = []; + lines.forEach((element) => { + lefts.push(element.slice(0, col)); + mids.push(element.charAt(col)); + rights.push(element.slice(col+1)); + }); + n = (7 - n % 7) % 7; + mids = mids.slice(n).concat(mids.slice(0, n)); + let result = ""; + for (let i = 0; i < 7; i++) + result += lefts[i] + mids[i] + rights[i]; + return result; +} + +function rotate_right(key, row, n) { + let mid = key.slice(row * 7, (row + 1) * 7); + n = (7 - n % 7) % 7; + return key.slice(0, 7 * row) + mid.slice(n) + mid.slice(0, n) + key.slice(7 * (row + 1)); +} + +function find_ix(letter) { + for (let i = 0; i < tiles.length; i++) + if (tiles[i][0] === letter) + return tiles[i][1]; + throw new OperationError("Letter " + letter + " is not included in LS47"); +} + +export function derive_key(password) { + let i = 0; + let k = letters; + for (const c of password) { + let [row, col] = find_ix(c); + k = rotate_down(rotate_right(k, i, col), i, row); + i = (i + 1) % 7; + } + return k; +} + +function check_key(key) { + if (key.length !== letters.length) + throw new OperationError("Wrong key size"); + let counts = new Array(); + for (let i = 0; i < letters.length; i++) + counts[letters.charAt(i)] = 0; + for (const elem of letters){ + if (letters.indexOf(elem) === -1) + throw new OperationError("Letter " + elem + " not in LS47!"); + counts[elem]++; + if (counts[elem] > 1) + throw new OperationError("Letter duplicated in the key!"); + } +} + +function find_pos (key, letter) { + let index = key.indexOf(letter); + if (index >= 0 && index < 49) + return [Math.floor(index/7), index%7]; + throw new OperationError("Letter " + letter + " is not in the key!"); +} + +function find_at_pos(key, coord) { + return key.charAt(coord[1] + (coord[0] * 7)); +} + +function add_pos(a, b) { + return [(a[0] + b[0]) % 7, (a[1] + b[1]) % 7]; +} + +function sub_pos(a, b) { + let asub = a[0] - b[0]; + let bsub = a[1] - b[1]; + return [asub - (Math.floor(asub/7) * 7), bsub - (Math.floor(bsub/7) * 7)]; +} + +function encrypt(key, plaintext) { + check_key(key); + let mp = [0, 0]; + let ciphertext = ''; + for (const p of plaintext) { + let pp = find_pos(key, p); + let mix = find_ix(find_at_pos(key, mp)); + let cp = add_pos(pp, mix); + let c = find_at_pos(key, cp); + ciphertext += c; + key = rotate_right(key, pp[0], 1); + cp = find_pos(key, c); + key = rotate_down(key, cp[1], 1); + mp = add_pos(mp, find_ix(c)); + } + return ciphertext; +} + +function decrypt(key, ciphertext) { + check_key(key); + let mp = [0,0]; + let plaintext = ''; + for (const c of ciphertext) { + let cp = find_pos(key, c); + let mix = find_ix(find_at_pos(key, mp)); + let pp = sub_pos(cp, mix); + let p = find_at_pos(key, pp); + + plaintext += p; + key = rotate_right(key, pp[0], 1); + cp = find_pos(key, c); + key = rotate_down(key, cp[1], 1); + mp = add_pos(mp, find_ix(c)); + } + return plaintext; +} + +export function encrypt_pad(key, plaintext, signature, padding_size) { + init_tiles(); + check_key(key); + let padding = ""; + for (let i = 0; i < padding_size; i++) { + padding += letters.charAt(Math.floor(Math.random() * letters.length)); + } + return encrypt(key, padding+plaintext+'---'+signature); +} + +export function decrypt_pad(key, ciphertext, padding_size) { + init_tiles(); + check_key(key); + return decrypt(key, ciphertext).slice(padding_size); +} \ No newline at end of file diff --git a/src/core/operations/LS47Decrypt.mjs b/src/core/operations/LS47Decrypt.mjs new file mode 100644 index 00000000..ffda8f93 --- /dev/null +++ b/src/core/operations/LS47Decrypt.mjs @@ -0,0 +1,58 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import * as LS47 from "../lib/LS47.mjs" + +/** + * LS47 Decrypt operation + */ +class LS47Decrypt extends Operation { + + /** + * LS47Decrypt constructor + */ + constructor() { + super(); + + this.name = "LS47 Decrypt"; + this.module = "Crypto"; + this.description = ""; + this.infoURL = ""; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Password", + type: "string", + value: "" + }, + { + name: "Padding", + type: "number", + value: 10 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + + this.padding_size = parseInt(args[1], 10); + + LS47.init_tiles(); + + let key = LS47.derive_key(args[0]); + return LS47.decrypt_pad(key, input, this.padding_size); + } + +} + +export default LS47Decrypt; diff --git a/src/core/operations/LS47Encrypt.mjs b/src/core/operations/LS47Encrypt.mjs new file mode 100644 index 00000000..bf3b0306 --- /dev/null +++ b/src/core/operations/LS47Encrypt.mjs @@ -0,0 +1,63 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import * as LS47 from "../lib/LS47.mjs" + +/** + * LS47 Encrypt operation + */ +class LS47Encrypt extends Operation { + + /** + * LS47Encrypt constructor + */ + constructor() { + super(); + + this.name = "LS47 Encrypt"; + this.module = "Crypto"; + this.description = ""; + this.infoURL = ""; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Password", + type: "string", + value: "" + }, + { + name: "Padding", + type: "number", + value: 10 + }, + { + name: "Signature", + type: "string", + value: "" + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + + this.padding_size = parseInt(args[1], 10); + + LS47.init_tiles(); + + let key = LS47.derive_key(args[0]); + return LS47.encrypt_pad(key, input, args[2], this.padding_size); + } + +} + +export default LS47Encrypt; From 5cdd062ed9c639bf387c783667d8bd86302e8acb Mon Sep 17 00:00:00 2001 From: n1073645 Date: Tue, 28 Jan 2020 09:33:32 +0000 Subject: [PATCH 07/99] Linting done --- src/core/config/Categories.json | 2 + src/core/lib/LS47.mjs | 153 ++++++++++++++++++---------- src/core/operations/LS47Decrypt.mjs | 12 +-- src/core/operations/LS47Encrypt.mjs | 14 +-- 4 files changed, 112 insertions(+), 69 deletions(-) diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 53ca796d..1b810d37 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -75,6 +75,8 @@ "DES Decrypt", "Triple DES Encrypt", "Triple DES Decrypt", + "LS47 Encrypt", + "LS47 Decrypt", "RC2 Encrypt", "RC2 Decrypt", "RC4", diff --git a/src/core/lib/LS47.mjs b/src/core/lib/LS47.mjs index a4ef10a5..b028fc4f 100644 --- a/src/core/lib/LS47.mjs +++ b/src/core/lib/LS47.mjs @@ -6,21 +6,27 @@ import OperationError from "../errors/OperationError.mjs"; -let letters = "_abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()"; -let tiles = []; +const letters = "_abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()"; +const tiles = []; -export function init_tiles() { +/** + * + */ +export function initTiles() { for (let i = 0; i < 49; i++) tiles.push([letters.charAt(i), [Math.floor(i/7), i % 7]]); } -function rotate_down(key, col, n) { - let lines = []; - for (let i = 0; i < 7; i++) +/** + * + */ +function rotateDown(key, col, n) { + const lines = []; + for (let i = 0; i < 7; i++) lines.push(key.slice(i*7, (i + 1) * 7)); - let lefts = []; + const lefts = []; let mids = []; - let rights = []; + const rights = []; lines.forEach((element) => { lefts.push(element.slice(0, col)); mids.push(element.charAt(col)); @@ -34,37 +40,49 @@ function rotate_down(key, col, n) { return result; } -function rotate_right(key, row, n) { - let mid = key.slice(row * 7, (row + 1) * 7); +/** + * + */ +function rotateRight(key, row, n) { + const mid = key.slice(row * 7, (row + 1) * 7); n = (7 - n % 7) % 7; return key.slice(0, 7 * row) + mid.slice(n) + mid.slice(0, n) + key.slice(7 * (row + 1)); } -function find_ix(letter) { +/** + * + */ +function findIx(letter) { for (let i = 0; i < tiles.length; i++) if (tiles[i][0] === letter) return tiles[i][1]; throw new OperationError("Letter " + letter + " is not included in LS47"); } -export function derive_key(password) { +/** + * + */ +export function deriveKey(password) { let i = 0; let k = letters; for (const c of password) { - let [row, col] = find_ix(c); - k = rotate_down(rotate_right(k, i, col), i, row); + const [row, col] = findIx(c); + k = rotateDown(rotateRight(k, i, col), i, row); i = (i + 1) % 7; } return k; } -function check_key(key) { +/** + * + */ +function checkKey(key) { if (key.length !== letters.length) throw new OperationError("Wrong key size"); - let counts = new Array(); + const counts = new Array(); for (let i = 0; i < letters.length; i++) counts[letters.charAt(i)] = 0; - for (const elem of letters){ + for (const elem of letters) { if (letters.indexOf(elem) === -1) throw new OperationError("Letter " + elem + " not in LS47!"); counts[elem]++; @@ -73,76 +91,99 @@ function check_key(key) { } } -function find_pos (key, letter) { - let index = key.indexOf(letter); +/** + * + */ +function findPos (key, letter) { + const index = key.indexOf(letter); if (index >= 0 && index < 49) return [Math.floor(index/7), index%7]; throw new OperationError("Letter " + letter + " is not in the key!"); } -function find_at_pos(key, coord) { +/** + * + */ +function findAtPos(key, coord) { return key.charAt(coord[1] + (coord[0] * 7)); } -function add_pos(a, b) { +/** + * + */ +function addPos(a, b) { return [(a[0] + b[0]) % 7, (a[1] + b[1]) % 7]; } -function sub_pos(a, b) { - let asub = a[0] - b[0]; - let bsub = a[1] - b[1]; +/** + * + */ +function subPos(a, b) { + const asub = a[0] - b[0]; + const bsub = a[1] - b[1]; return [asub - (Math.floor(asub/7) * 7), bsub - (Math.floor(bsub/7) * 7)]; } +/** + * + */ function encrypt(key, plaintext) { - check_key(key); + checkKey(key); let mp = [0, 0]; - let ciphertext = ''; + let ciphertext = ""; for (const p of plaintext) { - let pp = find_pos(key, p); - let mix = find_ix(find_at_pos(key, mp)); - let cp = add_pos(pp, mix); - let c = find_at_pos(key, cp); + const pp = findPos(key, p); + const mix = findIx(findAtPos(key, mp)); + let cp = addPos(pp, mix); + const c = findAtPos(key, cp); ciphertext += c; - key = rotate_right(key, pp[0], 1); - cp = find_pos(key, c); - key = rotate_down(key, cp[1], 1); - mp = add_pos(mp, find_ix(c)); + key = rotateRight(key, pp[0], 1); + cp = findPos(key, c); + key = rotateDown(key, cp[1], 1); + mp = addPos(mp, findIx(c)); } return ciphertext; } +/** + * + */ function decrypt(key, ciphertext) { - check_key(key); - let mp = [0,0]; - let plaintext = ''; + checkKey(key); + let mp = [0, 0]; + let plaintext = ""; for (const c of ciphertext) { - let cp = find_pos(key, c); - let mix = find_ix(find_at_pos(key, mp)); - let pp = sub_pos(cp, mix); - let p = find_at_pos(key, pp); - + let cp = findPos(key, c); + const mix = findIx(findAtPos(key, mp)); + const pp = subPos(cp, mix); + const p = findAtPos(key, pp); plaintext += p; - key = rotate_right(key, pp[0], 1); - cp = find_pos(key, c); - key = rotate_down(key, cp[1], 1); - mp = add_pos(mp, find_ix(c)); + key = rotateRight(key, pp[0], 1); + cp = findPos(key, c); + key = rotateDown(key, cp[1], 1); + mp = addPos(mp, findIx(c)); } return plaintext; } -export function encrypt_pad(key, plaintext, signature, padding_size) { - init_tiles(); - check_key(key); +/** + * + */ +export function encryptPad(key, plaintext, signature, paddingSize) { + initTiles(); + checkKey(key); let padding = ""; - for (let i = 0; i < padding_size; i++) { + for (let i = 0; i < paddingSize; i++) { padding += letters.charAt(Math.floor(Math.random() * letters.length)); } - return encrypt(key, padding+plaintext+'---'+signature); + return encrypt(key, padding+plaintext+"---"+signature); } -export function decrypt_pad(key, ciphertext, padding_size) { - init_tiles(); - check_key(key); - return decrypt(key, ciphertext).slice(padding_size); -} \ No newline at end of file +/** + * + */ +export function decryptPad(key, ciphertext, paddingSize) { + initTiles(); + checkKey(key); + return decrypt(key, ciphertext).slice(paddingSize); +} diff --git a/src/core/operations/LS47Decrypt.mjs b/src/core/operations/LS47Decrypt.mjs index ffda8f93..a5a92ebf 100644 --- a/src/core/operations/LS47Decrypt.mjs +++ b/src/core/operations/LS47Decrypt.mjs @@ -5,7 +5,7 @@ */ import Operation from "../Operation.mjs"; -import * as LS47 from "../lib/LS47.mjs" +import * as LS47 from "../lib/LS47.mjs"; /** * LS47 Decrypt operation @@ -45,12 +45,12 @@ class LS47Decrypt extends Operation { */ run(input, args) { - this.padding_size = parseInt(args[1], 10); + this.paddingSize = parseInt(args[1], 10); - LS47.init_tiles(); - - let key = LS47.derive_key(args[0]); - return LS47.decrypt_pad(key, input, this.padding_size); + LS47.initTiles(); + + const key = LS47.deriveKey(args[0]); + return LS47.decryptPad(key, input, this.paddingSize); } } diff --git a/src/core/operations/LS47Encrypt.mjs b/src/core/operations/LS47Encrypt.mjs index bf3b0306..f82baaab 100644 --- a/src/core/operations/LS47Encrypt.mjs +++ b/src/core/operations/LS47Encrypt.mjs @@ -5,7 +5,7 @@ */ import Operation from "../Operation.mjs"; -import * as LS47 from "../lib/LS47.mjs" +import * as LS47 from "../lib/LS47.mjs"; /** * LS47 Encrypt operation @@ -49,13 +49,13 @@ class LS47Encrypt extends Operation { * @returns {string} */ run(input, args) { - - this.padding_size = parseInt(args[1], 10); - LS47.init_tiles(); - - let key = LS47.derive_key(args[0]); - return LS47.encrypt_pad(key, input, args[2], this.padding_size); + this.paddingSize = parseInt(args[1], 10); + + LS47.initTiles(); + + const key = LS47.deriveKey(args[0]); + return LS47.encryptPad(key, input, args[2], this.paddingSize); } } From 6fd929160d9eb5ee332af80c90e823513b0a86f1 Mon Sep 17 00:00:00 2001 From: n1073645 Date: Tue, 28 Jan 2020 10:35:01 +0000 Subject: [PATCH 08/99] Comments and linting. --- src/core/lib/LS47.mjs | 57 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/src/core/lib/LS47.mjs b/src/core/lib/LS47.mjs index b028fc4f..6696aafc 100644 --- a/src/core/lib/LS47.mjs +++ b/src/core/lib/LS47.mjs @@ -10,7 +10,7 @@ const letters = "_abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()"; const tiles = []; /** - * + * Initialises the tiles with values and positions. */ export function initTiles() { for (let i = 0; i < 49; i++) @@ -18,7 +18,12 @@ export function initTiles() { } /** + * Rotates the key "down". * + * @param {string} key + * @param {number} col + * @param {number} n + * @returns {string} */ function rotateDown(key, col, n) { const lines = []; @@ -41,7 +46,12 @@ function rotateDown(key, col, n) { } /** + * Rotates the key "right". * + * @param {string} key + * @param {number} row + * @param {number} n + * @returns {string} */ function rotateRight(key, row, n) { const mid = key.slice(row * 7, (row + 1) * 7); @@ -50,7 +60,10 @@ function rotateRight(key, row, n) { } /** + * Finds the position of a letter in the tiles. * + * @param {string} letter + * @returns {string} */ function findIx(letter) { for (let i = 0; i < tiles.length; i++) @@ -60,7 +73,10 @@ function findIx(letter) { } /** + * Derives key from the input password. * + * @param {string} password + * @returns {string} */ export function deriveKey(password) { let i = 0; @@ -74,7 +90,9 @@ export function deriveKey(password) { } /** + * Checks the key is a valid key. * + * @param {string} key */ function checkKey(key) { if (key.length !== letters.length) @@ -92,7 +110,11 @@ function checkKey(key) { } /** + * Finds the position of a letter in they key. * + * @param {letter} key + * @param {string} letter + * @returns {object} */ function findPos (key, letter) { const index = key.indexOf(letter); @@ -102,21 +124,35 @@ function findPos (key, letter) { } /** + * Returns the character at the position on the tiles. * + * @param {string} key + * @param {object} coord + * @returns {string} */ function findAtPos(key, coord) { return key.charAt(coord[1] + (coord[0] * 7)); } /** + * Returns new position by adding two positions. * + * @param {object} a + * @param {object} b + * @returns {object} */ function addPos(a, b) { return [(a[0] + b[0]) % 7, (a[1] + b[1]) % 7]; } /** + * Returns new position by subtracting two positions. + * Note: We have to manually do the remainder division, since JS does not + * operate correctly on negative numbers (e.g. -3 % 4 = -3 when it should be 1). * + * @param {object} a + * @param {object} b + * @returns {object} */ function subPos(a, b) { const asub = a[0] - b[0]; @@ -125,7 +161,11 @@ function subPos(a, b) { } /** + * Encrypts the plaintext string. * + * @param {string} key + * @param {string} plaintext + * @returns {string} */ function encrypt(key, plaintext) { checkKey(key); @@ -146,7 +186,11 @@ function encrypt(key, plaintext) { } /** + * Decrypts the ciphertext string. * + * @param {string} key + * @param {string} ciphertext + * @returns {string} */ function decrypt(key, ciphertext) { checkKey(key); @@ -167,7 +211,13 @@ function decrypt(key, ciphertext) { } /** + * Adds padding to the input. * + * @param {string} key + * @param {string} plaintext + * @param {string} signature + * @param {number} paddingSize + * @returns {string} */ export function encryptPad(key, plaintext, signature, paddingSize) { initTiles(); @@ -180,7 +230,12 @@ export function encryptPad(key, plaintext, signature, paddingSize) { } /** + * Removes padding from the ouput. * + * @param {string} key + * @param {string} ciphertext + * @param {number} paddingSize + * @returns {string} */ export function decryptPad(key, ciphertext, paddingSize) { initTiles(); From e71794d362cf8112fc940a2ae6177c84ffce3bb5 Mon Sep 17 00:00:00 2001 From: n1073645 Date: Fri, 14 Feb 2020 12:28:12 +0000 Subject: [PATCH 09/99] Tests added for LS47 --- src/core/operations/LS47Decrypt.mjs | 4 +-- src/core/operations/LS47Encrypt.mjs | 4 +-- tests/operations/index.mjs | 1 + tests/operations/tests/LS47.mjs | 45 +++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 tests/operations/tests/LS47.mjs diff --git a/src/core/operations/LS47Decrypt.mjs b/src/core/operations/LS47Decrypt.mjs index a5a92ebf..cb92cd27 100644 --- a/src/core/operations/LS47Decrypt.mjs +++ b/src/core/operations/LS47Decrypt.mjs @@ -20,8 +20,8 @@ class LS47Decrypt extends Operation { this.name = "LS47 Decrypt"; this.module = "Crypto"; - this.description = ""; - this.infoURL = ""; + this.description = "This is a slight improvement of the ElsieFour cipher as described by Alan Kaminsky. We use 7x7 characters instead of original (barely fitting) 6x6, to be able to encrypt some structured information. We also describe a simple key-expansion algorithm, because remembering passwords is popular. Similar security considerations as with ElsieFour hold.\nThe LS47 alphabet consists of following characters: _abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()\nA LS47 key is a permutation of the alphabet that is then represented in a 7x7 grid used for the encryption or decryption."; + this.infoURL = "https://gitea.blesmrt.net/exa/ls47/src/branch/master"; this.inputType = "string"; this.outputType = "string"; this.args = [ diff --git a/src/core/operations/LS47Encrypt.mjs b/src/core/operations/LS47Encrypt.mjs index f82baaab..51283844 100644 --- a/src/core/operations/LS47Encrypt.mjs +++ b/src/core/operations/LS47Encrypt.mjs @@ -20,8 +20,8 @@ class LS47Encrypt extends Operation { this.name = "LS47 Encrypt"; this.module = "Crypto"; - this.description = ""; - this.infoURL = ""; + this.description = "This is a slight improvement of the ElsieFour cipher as described by Alan Kaminsky. We use 7x7 characters instead of original (barely fitting) 6x6, to be able to encrypt some structured information. We also describe a simple key-expansion algorithm, because remembering passwords is popular. Similar security considerations as with ElsieFour hold.\nThe LS47 alphabet consists of following characters: _abcdefghijklmnopqrstuvwxyz.0123456789,-+*/:?!'()\nA LS47 key is a permutation of the alphabet that is then represented in a 7x7 grid used for the encryption or decryption."; + this.infoURL = "https://gitea.blesmrt.net/exa/ls47/src/branch/master"; this.inputType = "string"; this.outputType = "string"; this.args = [ diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index bf440414..b3731727 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -96,6 +96,7 @@ import "./tests/DefangIP.mjs"; import "./tests/ParseUDP.mjs"; import "./tests/AvroToJSON.mjs"; import "./tests/Lorenz.mjs"; +import "./tests/LS47.mjs"; // Cannot test operations that use the File type yet diff --git a/tests/operations/tests/LS47.mjs b/tests/operations/tests/LS47.mjs new file mode 100644 index 00000000..40d876ee --- /dev/null +++ b/tests/operations/tests/LS47.mjs @@ -0,0 +1,45 @@ +/** + * Cartesian Product tests. + * + * @author n1073645 [n1073645@gmail.com] + * + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "LS47 Encrypt", + input: "thequickbrownfoxjumped", + expectedOutput: "(,t74ci78cp/8trx*yesu:alp1wqy", + recipeConfig: [ + { + op: "LS47 Encrypt", + args: ["helloworld", 0, "test"], + }, + ], + }, + { + name: "LS47 Decrypt", + input: "(,t74ci78cp/8trx*yesu:alp1wqy", + expectedOutput: "thequickbrownfoxjumped---test", + recipeConfig: [ + { + op: "LS47 Decrypt", + args: ["helloworld", 0], + }, + ], + }, + { + name: "LS47 Encrypt", + input: "thequickbrownfoxjumped", + expectedOutput: "Letter H is not included in LS47", + recipeConfig: [ + { + op: "LS47 Encrypt", + args: ["Helloworld", 0, "test"], + }, + ], + } +]); From e91e993fb5e7ec99db8fcb179fbd18a3f53b97bc Mon Sep 17 00:00:00 2001 From: n1073645 <57447333+n1073645@users.noreply.github.com> Date: Fri, 14 Feb 2020 13:43:30 +0000 Subject: [PATCH 10/99] Update LS47.mjs --- tests/operations/tests/LS47.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/operations/tests/LS47.mjs b/tests/operations/tests/LS47.mjs index 40d876ee..ce613923 100644 --- a/tests/operations/tests/LS47.mjs +++ b/tests/operations/tests/LS47.mjs @@ -1,5 +1,5 @@ /** - * Cartesian Product tests. + * LS47 tests. * * @author n1073645 [n1073645@gmail.com] * From 0182cdda69f7c877746084a75600d87b2cb34e19 Mon Sep 17 00:00:00 2001 From: Benedikt Werner <1benediktwerner@gmail.com> Date: Sat, 16 May 2020 00:42:02 +0200 Subject: [PATCH 11/99] Base85: Fix alphabetName --- src/core/lib/Base85.mjs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/core/lib/Base85.mjs b/src/core/lib/Base85.mjs index 8da729e2..e5778132 100644 --- a/src/core/lib/Base85.mjs +++ b/src/core/lib/Base85.mjs @@ -1,3 +1,5 @@ +import Utils from "../Utils.mjs"; + /** * Base85 resources. * @@ -32,13 +34,12 @@ export const ALPHABET_OPTIONS = [ * @returns {string} */ export function alphabetName(alphabet) { - alphabet = alphabet.replace("'", "'"); - alphabet = alphabet.replace("\"", """); - alphabet = alphabet.replace("\\", "\"); + alphabet = escape(alphabet); let name; ALPHABET_OPTIONS.forEach(function(a) { - if (escape(alphabet) === escape(a.value)) name = a.name; + const expanded = Utils.expandAlphRange(a.value).join(""); + if (alphabet === escape(expanded)) name = a.name; }); return name; From 103ecff6a7465b7a46a8f452885ec99d0e45ea26 Mon Sep 17 00:00:00 2001 From: Benedikt Werner <1benediktwerner@gmail.com> Date: Sat, 16 May 2020 00:42:31 +0200 Subject: [PATCH 12/99] Base85: Ignore whitespace --- src/core/operations/FromBase85.mjs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/operations/FromBase85.mjs b/src/core/operations/FromBase85.mjs index c874d5dc..c0d0328e 100644 --- a/src/core/operations/FromBase85.mjs +++ b/src/core/operations/FromBase85.mjs @@ -52,6 +52,8 @@ class FromBase85 extends Operation { if (input.length === 0) return []; + input = input.replace(/\s+/g, ""); + const matches = input.match(/<~(.+?)~>/); if (matches !== null) input = matches[1]; From 15dd9d4c93fa5bcfb1341ad8dccdb5671ae08d22 Mon Sep 17 00:00:00 2001 From: Benedikt Werner <1benediktwerner@gmail.com> Date: Sat, 16 May 2020 00:42:50 +0200 Subject: [PATCH 13/99] Add magic checks for base85 --- src/core/operations/FromBase85.mjs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/core/operations/FromBase85.mjs b/src/core/operations/FromBase85.mjs index c0d0328e..42f37a1c 100644 --- a/src/core/operations/FromBase85.mjs +++ b/src/core/operations/FromBase85.mjs @@ -33,6 +33,23 @@ class FromBase85 extends Operation { value: ALPHABET_OPTIONS }, ]; + this.checks = [ + { + pattern: "^\\s*(?:<~)?(?:(?:\\s*[!-u]){5}|\\s*z)+[!-u\\s]*(?:~>)?\\s*$", + flags: "i", + args: ["!-u"] + }, + { + pattern: "^(?:\\s*[0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#])+\\s*$", + flags: "i", + args: ["0-9a-zA-Z.\\-:+=^!/*?&<>()[]{}@%$#"] + }, + { + pattern: "^(?:\\s*[0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~])+\\s*$", + flags: "i", + args: ["0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~"] + }, + ]; } /** From eab1be0e2c58c3d69f8b2c477e4102f601b611c7 Mon Sep 17 00:00:00 2001 From: Benedikt Werner <1benediktwerner@gmail.com> Date: Wed, 20 May 2020 00:23:50 +0200 Subject: [PATCH 14/99] Magic base85: Remove 'i' flag --- src/core/operations/FromBase85.mjs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/core/operations/FromBase85.mjs b/src/core/operations/FromBase85.mjs index 42f37a1c..22033f99 100644 --- a/src/core/operations/FromBase85.mjs +++ b/src/core/operations/FromBase85.mjs @@ -36,17 +36,14 @@ class FromBase85 extends Operation { this.checks = [ { pattern: "^\\s*(?:<~)?(?:(?:\\s*[!-u]){5}|\\s*z)+[!-u\\s]*(?:~>)?\\s*$", - flags: "i", args: ["!-u"] }, { pattern: "^(?:\\s*[0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#])+\\s*$", - flags: "i", args: ["0-9a-zA-Z.\\-:+=^!/*?&<>()[]{}@%$#"] }, { pattern: "^(?:\\s*[0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~])+\\s*$", - flags: "i", args: ["0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~"] }, ]; From 1294d764e258bb6caa739b6111bb6d79a61d394f Mon Sep 17 00:00:00 2001 From: Benedikt Werner <1benediktwerner@gmail.com> Date: Fri, 22 May 2020 03:30:15 +0200 Subject: [PATCH 15/99] Base85: Only remove start and end markers with standard/ascii85 encoding --- src/core/operations/FromBase85.mjs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core/operations/FromBase85.mjs b/src/core/operations/FromBase85.mjs index 22033f99..09ded171 100644 --- a/src/core/operations/FromBase85.mjs +++ b/src/core/operations/FromBase85.mjs @@ -68,8 +68,10 @@ class FromBase85 extends Operation { input = input.replace(/\s+/g, ""); - const matches = input.match(/<~(.+?)~>/); - if (matches !== null) input = matches[1]; + if (encoding === "Standard") { + const matches = input.match(/<~(.+?)~>/); + if (matches !== null) input = matches[1]; + } let i = 0; let block, blockBytes; From ee408f7add6d633b9c42a6677f5bfa75055e9ca6 Mon Sep 17 00:00:00 2001 From: Benedikt Werner <1benediktwerner@gmail.com> Date: Fri, 22 May 2020 03:30:57 +0200 Subject: [PATCH 16/99] Base85: Update magic regexes to require 20 non-whitespace base85 chars --- src/core/operations/FromBase85.mjs | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/core/operations/FromBase85.mjs b/src/core/operations/FromBase85.mjs index 09ded171..9d73baa1 100644 --- a/src/core/operations/FromBase85.mjs +++ b/src/core/operations/FromBase85.mjs @@ -35,16 +35,31 @@ class FromBase85 extends Operation { ]; this.checks = [ { - pattern: "^\\s*(?:<~)?(?:(?:\\s*[!-u]){5}|\\s*z)+[!-u\\s]*(?:~>)?\\s*$", - args: ["!-u"] + pattern: + "^\\s*(?:<~)?" + // Optional whitespace and starting marker + "[\\s!-uz]*" + // Any amount of base85 characters and whitespace + "[!-uz]{20}" + // At least 20 continoues base85 characters without whitespace + "[\\s!-uz]*" + // Any amount of base85 characters and whitespace + "(?:~>)?\\s*$", // Optional ending marker and whitespace + args: ["!-u"], }, { - pattern: "^(?:\\s*[0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#])+\\s*$", - args: ["0-9a-zA-Z.\\-:+=^!/*?&<>()[]{}@%$#"] + pattern: + "^" + + "[\\s0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#]*" + + "[0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#]{20}" + // At least 20 continoues base85 characters without whitespace + "[\\s0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#]*" + + "$", + args: ["0-9a-zA-Z.\\-:+=^!/*?&<>()[]{}@%$#"], }, { - pattern: "^(?:\\s*[0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~])+\\s*$", - args: ["0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~"] + pattern: + "^" + + "[\\s0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~]*" + + "[0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~]{20}" + // At least 20 continoues base85 characters without whitespace + "[\\s0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~]*" + + "$", + args: ["0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~"], }, ]; } From f5a7db03cd8ab8bf9e7bbd43ec47ae9c57975a37 Mon Sep 17 00:00:00 2001 From: Benedikt Werner <1benediktwerner@gmail.com> Date: Wed, 10 Jun 2020 15:50:26 +0200 Subject: [PATCH 17/99] Base85: Only require 15 continuous base85 chars --- src/core/operations/FromBase85.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/operations/FromBase85.mjs b/src/core/operations/FromBase85.mjs index 9d73baa1..3555b020 100644 --- a/src/core/operations/FromBase85.mjs +++ b/src/core/operations/FromBase85.mjs @@ -38,7 +38,7 @@ class FromBase85 extends Operation { pattern: "^\\s*(?:<~)?" + // Optional whitespace and starting marker "[\\s!-uz]*" + // Any amount of base85 characters and whitespace - "[!-uz]{20}" + // At least 20 continoues base85 characters without whitespace + "[!-uz]{15}" + // At least 15 continoues base85 characters without whitespace "[\\s!-uz]*" + // Any amount of base85 characters and whitespace "(?:~>)?\\s*$", // Optional ending marker and whitespace args: ["!-u"], @@ -47,7 +47,7 @@ class FromBase85 extends Operation { pattern: "^" + "[\\s0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#]*" + - "[0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#]{20}" + // At least 20 continoues base85 characters without whitespace + "[0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#]{15}" + // At least 15 continoues base85 characters without whitespace "[\\s0-9a-zA-Z.\\-:+=^!/*?&<>()[\\]{}@%$#]*" + "$", args: ["0-9a-zA-Z.\\-:+=^!/*?&<>()[]{}@%$#"], @@ -56,7 +56,7 @@ class FromBase85 extends Operation { pattern: "^" + "[\\s0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~]*" + - "[0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~]{20}" + // At least 20 continoues base85 characters without whitespace + "[0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~]{15}" + // At least 15 continoues base85 characters without whitespace "[\\s0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~]*" + "$", args: ["0-9A-Za-z!#$%&()*+\\-;<=>?@^_`{|}~"], From 3e3c526a625cabeae64d8f3b61f88d326e98473a Mon Sep 17 00:00:00 2001 From: n1073645 Date: Mon, 6 Jul 2020 16:35:14 +0100 Subject: [PATCH 18/99] Caesar Box Cipher Added --- src/core/config/Categories.json | 1 + src/core/operations/CaesarBoxCipher.mjs | 61 ++++++++++++++++++++++ tests/operations/index.mjs | 1 + tests/operations/tests/CaesarBoxCipher.mjs | 45 ++++++++++++++++ 4 files changed, 108 insertions(+) create mode 100644 src/core/operations/CaesarBoxCipher.mjs create mode 100644 tests/operations/tests/CaesarBoxCipher.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 77e3d319..36465ced 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -91,6 +91,7 @@ "Bacon Cipher Decode", "Bifid Cipher Encode", "Bifid Cipher Decode", + "Caesar Box Cipher", "Affine Cipher Encode", "Affine Cipher Decode", "A1Z26 Cipher Encode", diff --git a/src/core/operations/CaesarBoxCipher.mjs b/src/core/operations/CaesarBoxCipher.mjs new file mode 100644 index 00000000..2e4d9830 --- /dev/null +++ b/src/core/operations/CaesarBoxCipher.mjs @@ -0,0 +1,61 @@ +/** + * @author n1073645 [n1073645@gmail.com] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * Caesar Box Cipher operation + */ +class CaesarBoxCipher extends Operation { + + /** + * CaesarBoxCipher constructor + */ + constructor() { + super(); + + this.name = "Caesar Box Cipher"; + this.module = "Ciphers"; + this.description = ""; + this.infoURL = ""; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Box Height", + type: "number", + value: 1 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const tableHeight = args[0]; + const tableWidth = Math.ceil(input.length / tableHeight); + while (input.indexOf(" ") !== -1) + input = input.replace(" ", ""); + for (let i = 0; i < (tableHeight * tableWidth) - input.length; i++) { + input += "\x00"; + } + let result = ""; + for (let i = 0; i < tableHeight; i++) { + for (let j = i; j < input.length; j += tableHeight) { + if (input.charAt(j) !== "\x00") { + result += input.charAt(j); + } + } + } + return result; + } + +} + +export default CaesarBoxCipher; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 8d3cd623..bd6cd3ed 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -101,6 +101,7 @@ import "./tests/LuhnChecksum.mjs"; import "./tests/CipherSaber2.mjs"; import "./tests/Colossus.mjs"; import "./tests/ParseObjectIDTimestamp.mjs"; +import "./tests/CaesarBoxCipher.mjs"; // Cannot test operations that use the File type yet diff --git a/tests/operations/tests/CaesarBoxCipher.mjs b/tests/operations/tests/CaesarBoxCipher.mjs new file mode 100644 index 00000000..3ccdae66 --- /dev/null +++ b/tests/operations/tests/CaesarBoxCipher.mjs @@ -0,0 +1,45 @@ +/** + * Base58 tests. + * + * @author n1073645 [n1073645@gmail.com] + * + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Caesar Box Cipher: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "Caesar Box Cipher", + args: ["1"], + }, + ], + }, + { + name: "Caesar Box Cipher: Hello World!", + input: "Hello World!", + expectedOutput: "Hlodeor!lWl", + recipeConfig: [ + { + op: "Caesar Box Cipher", + args: ["3"], + }, + ], + }, + { + name: "Caesar Box Cipher: Hello World!", + input: "Hlodeor!lWl", + expectedOutput: "HelloWorld!", + recipeConfig: [ + { + op: "Caesar Box Cipher", + args: ["4"], + }, + ], + } +]); From 667dfd820e5e2b93a5daf4258f547d6a0a605a37 Mon Sep 17 00:00:00 2001 From: n1073645 Date: Mon, 6 Jul 2020 16:46:40 +0100 Subject: [PATCH 19/99] info url added --- src/core/operations/CaesarBoxCipher.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/operations/CaesarBoxCipher.mjs b/src/core/operations/CaesarBoxCipher.mjs index 2e4d9830..9c835b4b 100644 --- a/src/core/operations/CaesarBoxCipher.mjs +++ b/src/core/operations/CaesarBoxCipher.mjs @@ -19,8 +19,8 @@ class CaesarBoxCipher extends Operation { this.name = "Caesar Box Cipher"; this.module = "Ciphers"; - this.description = ""; - this.infoURL = ""; + this.description = "Caesar Box Encryption uses a box, a rectangle (or a square), or at least a size W caracterizing its width."; + this.infoURL = "https://www.dcode.fr/caesar-box-cipher"; this.inputType = "string"; this.outputType = "string"; this.args = [ From a7cdb095d23c3af402d02a347fe8fd2b1ede0d0b Mon Sep 17 00:00:00 2001 From: MikeCAT Date: Mon, 4 Oct 2021 22:39:16 +0900 Subject: [PATCH 20/99] Added input validation to fromBase64() --- src/core/lib/Base64.mjs | 45 ++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/src/core/lib/Base64.mjs b/src/core/lib/Base64.mjs index d9452575..c96e579e 100644 --- a/src/core/lib/Base64.mjs +++ b/src/core/lib/Base64.mjs @@ -95,6 +95,7 @@ export function fromBase64(data, alphabet="A-Za-z0-9+/=", returnType="string", r const output = []; let chr1, chr2, chr3, + encChr1, encChr2, encChr3, encChr4, enc1, enc2, enc3, enc4, i = 0; @@ -103,15 +104,39 @@ export function fromBase64(data, alphabet="A-Za-z0-9+/=", returnType="string", r data = data.replace(re, ""); } - while (i < data.length) { - enc1 = alphabet.indexOf(data.charAt(i++)); - enc2 = alphabet.indexOf(data.charAt(i++) || "="); - enc3 = alphabet.indexOf(data.charAt(i++) || "="); - enc4 = alphabet.indexOf(data.charAt(i++) || "="); + if (data.length % 4 === 1) { + throw new OperationError(`Invalid Base64 input length (${data.length}): it won't be 4n+1`); + } - enc2 = enc2 === -1 ? 64 : enc2; - enc3 = enc3 === -1 ? 64 : enc3; - enc4 = enc4 === -1 ? 64 : enc4; + if (alphabet.length === 65) { + const pad = alphabet.charAt(64); + const padPos = data.indexOf(pad); + if (padPos >= 0) { + // padding character should appear only at the end of the input + // there should be only one or two padding character(s) if it exists + if (padPos < data.length - 2 || data.charAt(data.length - 1) !== pad) { + throw new OperationError("Invalid Base64 input: padding character misused"); + } + if (data.length % 4 !== 0) { + throw new OperationError("Invalid Base64 input: padded not to multiple of 4"); + } + } + } + + while (i < data.length) { + encChr1 = data.charAt(i++); + encChr2 = data.charAt(i++); + encChr3 = data.charAt(i++); + encChr4 = data.charAt(i++); + + enc1 = alphabet.indexOf(encChr1); + enc2 = alphabet.indexOf(encChr2); + enc3 = alphabet.indexOf(encChr3); + enc4 = alphabet.indexOf(encChr4); + + if (enc1 < 0 || enc2 < 0 || enc3 < 0 || enc4 < 0) { + throw new OperationError("Invalid Base64 input: contains non-alphabet char(s)"); + } chr1 = (enc1 << 2) | (enc2 >> 4); chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); @@ -119,10 +144,10 @@ export function fromBase64(data, alphabet="A-Za-z0-9+/=", returnType="string", r output.push(chr1); - if (enc3 !== 64) { + if (encChr3 !== "" && enc3 !== 64) { output.push(chr2); } - if (enc4 !== 64) { + if (encChr4 !== "" && enc4 !== 64) { output.push(chr3); } } From e8f91316ffd280715a3e90177a5b04068689883d Mon Sep 17 00:00:00 2001 From: MikeCAT Date: Wed, 20 Oct 2021 21:28:48 +0900 Subject: [PATCH 21/99] Added ROT13/47 Brute Force --- src/core/config/Categories.json | 2 + src/core/operations/ROT13BruteForce.mjs | 102 ++++++++++++++++++++++++ src/core/operations/ROT47BruteForce.mjs | 82 +++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 src/core/operations/ROT13BruteForce.mjs create mode 100644 src/core/operations/ROT47BruteForce.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 09ee8d15..1369267a 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -82,7 +82,9 @@ "RC4", "RC4 Drop", "ROT13", + "ROT13 Brute Force", "ROT47", + "ROT47 Brute Force", "XOR", "XOR Brute Force", "Vigenère Encode", diff --git a/src/core/operations/ROT13BruteForce.mjs b/src/core/operations/ROT13BruteForce.mjs new file mode 100644 index 00000000..bdf9d40a --- /dev/null +++ b/src/core/operations/ROT13BruteForce.mjs @@ -0,0 +1,102 @@ +/** + * @author MikeCAT + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * ROT13 Brute Force operation. + */ +class ROT13BruteForce extends Operation { + + /** + * ROT13BruteForce constructor + */ + constructor() { + super(); + + this.name = "ROT13 Brute Force"; + this.module = "Default"; + this.description = "Try all meaningful amounts for ROT13.

Optionally you can enter your known plaintext (crib) to filter the result."; + this.infoURL = "https://wikipedia.org/wiki/ROT13"; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + name: "Rotate lower case chars", + type: "boolean", + value: true + }, + { + name: "Rotate upper case chars", + type: "boolean", + value: true + }, + { + name: "Rotate numbers", + type: "boolean", + value: false + }, + { + "name": "Sample length", + "type": "number", + "value": 100 + }, + { + "name": "Sample offset", + "type": "number", + "value": 0 + }, + { + "name": "Print amount", + "type": "boolean", + "value": true + }, + { + "name": "Crib (known plaintext string)", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [rotateLower, rotateUpper, rotateNum, sampleLength, sampleOffset, printAmount, crib] = args; + const sample = input.slice(sampleOffset, sampleOffset + sampleLength); + const cribLower = crib.toLowerCase(); + const lowerStart = "a".charCodeAt(0), upperStart = "A".charCodeAt(0), numStart = "0".charCodeAt(0); + const result = []; + for (let amount = 1; amount < 26; amount++) { + const rotated = sample.slice(); + for (let i = 0; i < rotated.length; i++) { + if (rotateLower && lowerStart <= rotated[i] && rotated[i] < lowerStart + 26) { + rotated[i] = (rotated[i] - lowerStart + amount) % 26 + lowerStart; + } else if (rotateUpper && upperStart <= rotated[i] && rotated[i] < upperStart + 26) { + rotated[i] = (rotated[i] - upperStart + amount) % 26 + upperStart; + } else if (rotateNum && numStart <= rotated[i] && rotated[i] < numStart + 10) { + rotated[i] = (rotated[i] - numStart + amount) % 10 + numStart; + } + } + const rotatedString = Utils.byteArrayToUtf8(rotated); + if (rotatedString.toLowerCase().indexOf(cribLower) >= 0) { + const rotatedStringPrintable = Utils.printable(rotatedString, false); + if (printAmount) { + const amountStr = "Amount = " + (" " + amount).slice(-2) + ": "; + result.push(amountStr + rotatedStringPrintable); + } else { + result.push(rotatedStringPrintable); + } + } + } + return result.join("\n"); + } +} + +export default ROT13BruteForce; diff --git a/src/core/operations/ROT47BruteForce.mjs b/src/core/operations/ROT47BruteForce.mjs new file mode 100644 index 00000000..5fce5259 --- /dev/null +++ b/src/core/operations/ROT47BruteForce.mjs @@ -0,0 +1,82 @@ +/** + * @author MikeCAT + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; + +/** + * ROT47 Brute Force operation. + */ +class ROT47BruteForce extends Operation { + + /** + * ROT47BruteForce constructor + */ + constructor() { + super(); + + this.name = "ROT47 Brute Force"; + this.module = "Default"; + this.description = "Try all meaningful amounts for ROT47.

Optionally you can enter your known plaintext (crib) to filter the result."; + this.infoURL = "https://wikipedia.org/wiki/ROT13#Variants"; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + "name": "Sample length", + "type": "number", + "value": 100 + }, + { + "name": "Sample offset", + "type": "number", + "value": 0 + }, + { + "name": "Print amount", + "type": "boolean", + "value": true + }, + { + "name": "Crib (known plaintext string)", + "type": "string", + "value": "" + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [sampleLength, sampleOffset, printAmount, crib] = args; + const sample = input.slice(sampleOffset, sampleOffset + sampleLength); + const cribLower = crib.toLowerCase(); + const result = []; + for (let amount = 1; amount < 94; amount++) { + const rotated = sample.slice(); + for (let i = 0; i < rotated.length; i++) { + if (33 <= rotated[i] && rotated[i] <= 126) { + rotated[i] = (rotated[i] - 33 + amount) % 94 + 33; + } + } + const rotatedString = Utils.byteArrayToUtf8(rotated); + if (rotatedString.toLowerCase().indexOf(cribLower) >= 0) { + const rotatedStringPrintable = Utils.printable(rotatedString, false); + if (printAmount) { + const amountStr = "Amount = " + (" " + amount).slice(-2) + ": "; + result.push(amountStr + rotatedStringPrintable); + } else { + result.push(rotatedStringPrintable); + } + } + } + return result.join("\n"); + } +} + +export default ROT47BruteForce; From 671ae6558fbf773d72bb7519fde7ce1ce203c514 Mon Sep 17 00:00:00 2001 From: Peter Jacobs Date: Fri, 29 Oct 2021 01:31:55 -0500 Subject: [PATCH 22/99] Added 'LZString Decompress' and 'LZString Compress' operations --- package-lock.json | 23 +++++---- package.json | 1 + src/core/config/Categories.json | 4 +- src/core/lib/LZString.mjs | 21 ++++++++ src/core/operations/LZStringCompress.mjs | 55 +++++++++++++++++++++ src/core/operations/LZStringDecompress.mjs | 56 ++++++++++++++++++++++ tests/operations/index.mjs | 1 + tests/operations/tests/LZString.mjs | 33 +++++++++++++ 8 files changed, 184 insertions(+), 10 deletions(-) create mode 100644 src/core/lib/LZString.mjs create mode 100644 src/core/operations/LZStringCompress.mjs create mode 100644 src/core/operations/LZStringDecompress.mjs create mode 100644 tests/operations/tests/LZString.mjs diff --git a/package-lock.json b/package-lock.json index 6fa9016e..d579a3d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2295,9 +2295,9 @@ "dev": true }, "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", + "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", "dev": true, "requires": { "delegates": "^1.0.0", @@ -8731,6 +8731,11 @@ "yallist": "^4.0.0" } }, + "lz-string": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", + "integrity": "sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=" + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -9014,9 +9019,9 @@ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "minipass": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", - "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.5.tgz", + "integrity": "sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw==", "dev": true, "requires": { "yallist": "^4.0.0" @@ -12667,9 +12672,9 @@ "dev": true }, "tar": { - "version": "6.1.10", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.10.tgz", - "integrity": "sha512-kvvfiVvjGMxeUNB6MyYv5z7vhfFRwbwCXJAeL0/lnbrttBVqcMOnpHUf0X42LrPMR8mMpgapkJMchFH4FSHzNA==", + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", "dev": true, "requires": { "chownr": "^2.0.0", diff --git a/package.json b/package.json index 78a8696b..cf4ceba6 100644 --- a/package.json +++ b/package.json @@ -133,6 +133,7 @@ "lodash": "^4.17.21", "loglevel": "^1.7.1", "loglevel-message-prefix": "^3.0.0", + "lz-string": "^1.4.4", "markdown-it": "^12.2.0", "moment": "^2.29.1", "moment-timezone": "^0.5.33", diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 09ee8d15..fcd4f6af 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -313,7 +313,9 @@ "Bzip2 Decompress", "Bzip2 Compress", "Tar", - "Untar" + "Untar", + "LZString Compress", + "LZString Decompress" ] }, { diff --git a/src/core/lib/LZString.mjs b/src/core/lib/LZString.mjs new file mode 100644 index 00000000..c4919a87 --- /dev/null +++ b/src/core/lib/LZString.mjs @@ -0,0 +1,21 @@ +/** + * lz-string exports. + * + * @author crespyl [peter@crespyl.net] + * @copyright Peter Jacobs 2021 + * @license Apache-2.0 + */ + +import LZString from "lz-string"; + +export const COMPRESSION_OUTPUT_FORMATS = ["default", "UTF16", "Base64"]; +export const COMPRESSION_FUNCTIONS = { + "default": LZString.compress, + "UTF16": LZString.compressToUTF16, + "Base64": LZString.compressToBase64, +}; +export const DECOMPRESSION_FUNCTIONS = { + "default": LZString.decompress, + "UTF16": LZString.decompressFromUTF16, + "Base64": LZString.decompressFromBase64, +}; diff --git a/src/core/operations/LZStringCompress.mjs b/src/core/operations/LZStringCompress.mjs new file mode 100644 index 00000000..11d5710a --- /dev/null +++ b/src/core/operations/LZStringCompress.mjs @@ -0,0 +1,55 @@ +/** + * @author crespyl [peter@crespyl.net] + * @copyright Peter Jacobs 2021 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +import {COMPRESSION_OUTPUT_FORMATS, COMPRESSION_FUNCTIONS} from "../lib/LZString.mjs"; + +/** + * LZString Compress operation + */ +class LZStringCompress extends Operation { + + /** + * LZStringCompress constructor + */ + constructor() { + super(); + + this.name = "LZString Compress"; + this.module = "Compression"; + this.description = "Compress the input with lz-string."; + this.infoURL = "https://pieroxy.net/blog/pages/lz-string/index.html"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Compression Format", + type: "option", + defaultIndex: 0, + value: COMPRESSION_OUTPUT_FORMATS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const compress = COMPRESSION_FUNCTIONS[args[0]]; + if (compress) { + return compress(input); + } else { + throw new OperationError("Unable to find compression function"); + } + } + +} + +export default LZStringCompress; diff --git a/src/core/operations/LZStringDecompress.mjs b/src/core/operations/LZStringDecompress.mjs new file mode 100644 index 00000000..87d0d85b --- /dev/null +++ b/src/core/operations/LZStringDecompress.mjs @@ -0,0 +1,56 @@ +/** + * @author crespyl [peter@crespyl.net] + * @copyright Peter Jacobs 2021 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +import {COMPRESSION_OUTPUT_FORMATS, DECOMPRESSION_FUNCTIONS} from "../lib/LZString.mjs"; + +/** + * LZString Decompress operation + */ +class LZStringDecompress extends Operation { + + /** + * LZStringDecompress constructor + */ + constructor() { + super(); + + this.name = "LZString Decompress"; + this.module = "Compression"; + this.description = "Decompresses data that was compressed with lz-string."; + this.infoURL = "https://pieroxy.net/blog/pages/lz-string/index.html"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Compression Format", + type: "option", + defaultIndex: 0, + value: COMPRESSION_OUTPUT_FORMATS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const decompress = DECOMPRESSION_FUNCTIONS[args[0]]; + if (decompress) { + return decompress(input); + } else { + throw new OperationError("Unable to find decompression function"); + } + } + + +} + +export default LZStringDecompress; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 9add20b9..27f0fbb6 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -107,6 +107,7 @@ import "./tests/CBORDecode.mjs"; import "./tests/JA3Fingerprint.mjs"; import "./tests/JA3SFingerprint.mjs"; import "./tests/HASSH.mjs"; +import "./tests/LZString.mjs"; // Cannot test operations that use the File type yet diff --git a/tests/operations/tests/LZString.mjs b/tests/operations/tests/LZString.mjs new file mode 100644 index 00000000..69e47bdd --- /dev/null +++ b/tests/operations/tests/LZString.mjs @@ -0,0 +1,33 @@ +/** + * LZString tests. + * + * @author crespyl [peter@crespyl.net] + * @copyright Peter Jacobs 2021 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "LZString Compress To Base64", + input: "hello world", + expectedOutput: "BYUwNmD2AEDukCcwBMg=", + recipeConfig: [ + { + "op": "LZString Compress", + "args": ["Base64"] + } + ], + }, + { + name: "LZString Decompress From Base64", + input: "BYUwNmD2AEDukCcwBMg=", + expectedOutput: "hello world", + recipeConfig: [ + { + "op": "LZString Decompress", + "args": ["Base64"] + } + ], + } +]); From 1dbcd2ac8499bf37e6e12aef698e01c86f0d61e0 Mon Sep 17 00:00:00 2001 From: CPlusSharp Date: Sun, 7 Nov 2021 11:21:17 +0100 Subject: [PATCH 23/99] PEMtoHex: Support arbitrary PEMs previous implementation only supported some PEMs (e.g. Certificate) the new implementation is more general, it just extracts the base64 between header and footer and decodes that to hex --- src/core/operations/PEMToHex.mjs | 33 ++-- tests/operations/index.mjs | 1 + tests/operations/tests/PEMtoHex.mjs | 294 ++++++++++++++++++++++++++++ 3 files changed, 317 insertions(+), 11 deletions(-) create mode 100644 tests/operations/tests/PEMtoHex.mjs diff --git a/src/core/operations/PEMToHex.mjs b/src/core/operations/PEMToHex.mjs index 095e2c56..575f6f77 100644 --- a/src/core/operations/PEMToHex.mjs +++ b/src/core/operations/PEMToHex.mjs @@ -1,11 +1,14 @@ /** * @author n1474335 [n1474335@gmail.com] + * @author cplussharp * @copyright Crown Copyright 2016 * @license Apache-2.0 */ -import r from "jsrsasign"; +import {fromBase64} from "../lib/Base64.mjs"; import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; /** * PEM to Hex operation @@ -33,17 +36,25 @@ class PEMToHex extends Operation { * @returns {string} */ run(input, args) { - if (input.indexOf("-----BEGIN") < 0) { - // Add header so that the KEYUTIL function works - input = "-----BEGIN CERTIFICATE-----" + input; + let output = ""; + let match; + const regex = /-----BEGIN ([A-Z][A-Z ]+[A-Z])-----/g; + while ((match = regex.exec(input)) !== null) { + // find corresponding end tag + const indexBase64 = match.index + match[0].length; + const footer = `-----END ${match[1]}-----`; + const indexFooter = input.indexOf(footer, indexBase64); + if (indexFooter === -1) { + throw new OperationError(`PEM footer '${footer}' not found`); + } + + // decode base64 content + const base64 = input.substring(indexBase64, indexFooter); + const bytes = fromBase64(base64, "A-Za-z0-9+/=", "byteArray", true); + const hex = bytes.map(b => Utils.hex(b)).join(""); + output += hex; } - if (input.indexOf("-----END") < 0) { - // Add footer so that the KEYUTIL function works - input = input + "-----END CERTIFICATE-----"; - } - const cert = new r.X509(); - cert.readCertPEM(input); - return cert.hex; + return output; } } diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 9add20b9..fede1441 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -66,6 +66,7 @@ import "./tests/PGP.mjs"; import "./tests/PHP.mjs"; import "./tests/ParseIPRange.mjs"; import "./tests/ParseQRCode.mjs"; +import "./tests/PEMtoHex.mjs"; import "./tests/PowerSet.mjs"; import "./tests/Regex.mjs"; import "./tests/Register.mjs"; diff --git a/tests/operations/tests/PEMtoHex.mjs b/tests/operations/tests/PEMtoHex.mjs new file mode 100644 index 00000000..7929bfdd --- /dev/null +++ b/tests/operations/tests/PEMtoHex.mjs @@ -0,0 +1,294 @@ +/** + * Test PEMtoHex with different inputs + * + * @author cplussharp + * + * @license Apache-2.0 + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +/** RSA 2048bit key pair as PKCS1 and PKCS8 and as certificate */ +const PEMS_RSA_PRIVATE_KEY_PKCS1 = `-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEA5WykLKHiBAhmZh5WhocgpQQqZjdrApuRxRT21SJZx6Ce+Oz2 +V17/heozu5LEz63jCxW1NrBckzl/Ys8p9LeqYTu6x/LbKloTjfEWxlzXnzUSqn9J +HIxmmJQzjXp9X1D99Tj+NWpRGEIiCFE7JfDhe2KnMGVDDg6kfCLokDdLo256LeQ4 +CEkViwY6d+at4xDlIHwvZZmG4Smk56eHhvQE3I8sSAzgoLMBamQ5m3MbiULAYtxs +kCpCfjFxrL6Ziaaj7HZoneF40R30KCI9ygF8vkzxLwe3t5Y4XgHL9TYQm1+BDnin +upIB/zTeO1ygBGA66m6zpmkmuG7d8HXIducz+wIDAQABAoIBACQu3jWr0lmQeZXh +cwQEi8F6xrUYSGhA4NyUUdmLcV1ql6fqt29QLDySk1Yh76hRZF17LvlRF0ig6NZM +lfFihhyPrwWZ57bmPe9E9rKSMe+KD0eUi5NVEVk/BmJpzxwZSfRC6NTDz8Zjp7po +FUwGkYlEJdocHlc5N/fcCZG1Jti/Z1AsZIjO6r6S0O7neC7icECBKHUyWa8/yE1N +++TVyMV+z53Ad1PC+SHMGDlbqJAM3o4wAzD/FAIzyVo6GSnnC+bFdgMtIwtwgYTH +rbr8M8j5fqAHTqNJeblqt/5KHEj2VVsIsHIuQ6lv4llESEqmH+N5KE4O33U7/Wmj +y/+VGAECgYEA9ysROHXOx1ofh3tI/r2C2FUan6/AAe4dc+8E4jMGI5T4xeqYHTRV +l1yS+ZKIxqclIoAmd6SJ7Nx2GdQ55MmokZdZRqsFN1flFOZw2kFH/I0zQZXdOjF+ +sf5Lu0FfcTw3VJhJ/YU3CVdlgdP4ekHbaJVFW5i/pTUf5vNs6AGBGxsCgYEA7Z9D +0qnF6GhxA8lJfwnyuktYnwIQoXE6Xp26/NZD7t7HzxHDf43LQxmTk+mDP/yIQHwb +xIx2NE/ncNxlUMl/g1PkJbKVkB8tdIZrLyiT4lebeqgT72Q07IsxRl/GHOr7CfN0 +61OBRCe44IlOtaNAZk4zWwuAwAYx+G8ifuOJ+KECgYBP5NvsJChyx+7pHDC8JwXk +Z53zgBvQg+eBUgGCHHwfhEflsa75wbDo/EOF6JfNnrmiLUpB4i2zIpAKSU9tZMHY +TdPNw/orqX2jA9n2sqNSP1ISIR8hcF5Dqq9QGBGByLUZ4yAHksf3fQiSrrHi0ubZ +J2cD9Jv+Cu4E+Sp61AGngQKBgHmuTPTbq1TP5s+hi9laJsnvO3pxfEKv0MwSyWYf +8rmnq3oGBq6S1buOpVvhAC0MDFm5NB76Lq2rHUFWGyu7g2ik1PfY823SCVzaWJjV +lqUZZ6zv1QWJsvBOdvUqpjC4w8TcvsqjAFb+YFXa+ktZRekdsn607UFn6r7laizA +KC8BAoGAZty7sIGMt1gDaoIjySgOox8x7AlY3QUyNQC5N8KW3MZ8KLC5UBtjhqLy +wYOJr+/1R/7ibiHrKkIE/dmg5QN1iS5tZmFvyLwJ+nHQZObFrlcKtpr+1lekQY/m +ly6YJFk3yj2nhYzt8eVXBX2lCoLG1gsrbpXvUfIGJ53L9m1mVAo= +-----END RSA PRIVATE KEY-----`; + +const PEMS_RSA_PRIVATE_KEY_PKCS8 = `-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDlbKQsoeIECGZm +HlaGhyClBCpmN2sCm5HFFPbVIlnHoJ747PZXXv+F6jO7ksTPreMLFbU2sFyTOX9i +zyn0t6phO7rH8tsqWhON8RbGXNefNRKqf0kcjGaYlDONen1fUP31OP41alEYQiII +UTsl8OF7YqcwZUMODqR8IuiQN0ujbnot5DgISRWLBjp35q3jEOUgfC9lmYbhKaTn +p4eG9ATcjyxIDOCgswFqZDmbcxuJQsBi3GyQKkJ+MXGsvpmJpqPsdmid4XjRHfQo +Ij3KAXy+TPEvB7e3ljheAcv1NhCbX4EOeKe6kgH/NN47XKAEYDrqbrOmaSa4bt3w +dch25zP7AgMBAAECggEAJC7eNavSWZB5leFzBASLwXrGtRhIaEDg3JRR2YtxXWqX +p+q3b1AsPJKTViHvqFFkXXsu+VEXSKDo1kyV8WKGHI+vBZnntuY970T2spIx74oP +R5SLk1URWT8GYmnPHBlJ9ELo1MPPxmOnumgVTAaRiUQl2hweVzk399wJkbUm2L9n +UCxkiM7qvpLQ7ud4LuJwQIEodTJZrz/ITU375NXIxX7PncB3U8L5IcwYOVuokAze +jjADMP8UAjPJWjoZKecL5sV2Ay0jC3CBhMetuvwzyPl+oAdOo0l5uWq3/kocSPZV +Wwiwci5DqW/iWURISqYf43koTg7fdTv9aaPL/5UYAQKBgQD3KxE4dc7HWh+He0j+ +vYLYVRqfr8AB7h1z7wTiMwYjlPjF6pgdNFWXXJL5kojGpyUigCZ3pIns3HYZ1Dnk +yaiRl1lGqwU3V+UU5nDaQUf8jTNBld06MX6x/ku7QV9xPDdUmEn9hTcJV2WB0/h6 +QdtolUVbmL+lNR/m82zoAYEbGwKBgQDtn0PSqcXoaHEDyUl/CfK6S1ifAhChcTpe +nbr81kPu3sfPEcN/jctDGZOT6YM//IhAfBvEjHY0T+dw3GVQyX+DU+QlspWQHy10 +hmsvKJPiV5t6qBPvZDTsizFGX8Yc6vsJ83TrU4FEJ7jgiU61o0BmTjNbC4DABjH4 +byJ+44n4oQKBgE/k2+wkKHLH7ukcMLwnBeRnnfOAG9CD54FSAYIcfB+ER+WxrvnB +sOj8Q4Xol82euaItSkHiLbMikApJT21kwdhN083D+iupfaMD2fayo1I/UhIhHyFw +XkOqr1AYEYHItRnjIAeSx/d9CJKuseLS5tknZwP0m/4K7gT5KnrUAaeBAoGAea5M +9NurVM/mz6GL2Vomye87enF8Qq/QzBLJZh/yuaeregYGrpLVu46lW+EALQwMWbk0 +HvourasdQVYbK7uDaKTU99jzbdIJXNpYmNWWpRlnrO/VBYmy8E529SqmMLjDxNy+ +yqMAVv5gVdr6S1lF6R2yfrTtQWfqvuVqLMAoLwECgYBm3LuwgYy3WANqgiPJKA6j +HzHsCVjdBTI1ALk3wpbcxnwosLlQG2OGovLBg4mv7/VH/uJuIesqQgT92aDlA3WJ +Lm1mYW/IvAn6cdBk5sWuVwq2mv7WV6RBj+aXLpgkWTfKPaeFjO3x5VcFfaUKgsbW +Cytule9R8gYnncv2bWZUCg== +-----END PRIVATE KEY-----`; + +const PEMS_RSA_PUBLIC_KEY_PKCS1 = `-----BEGIN RSA PUBLIC KEY----- +MIIBCgKCAQEA5WykLKHiBAhmZh5WhocgpQQqZjdrApuRxRT21SJZx6Ce+Oz2V17/ +heozu5LEz63jCxW1NrBckzl/Ys8p9LeqYTu6x/LbKloTjfEWxlzXnzUSqn9JHIxm +mJQzjXp9X1D99Tj+NWpRGEIiCFE7JfDhe2KnMGVDDg6kfCLokDdLo256LeQ4CEkV +iwY6d+at4xDlIHwvZZmG4Smk56eHhvQE3I8sSAzgoLMBamQ5m3MbiULAYtxskCpC +fjFxrL6Ziaaj7HZoneF40R30KCI9ygF8vkzxLwe3t5Y4XgHL9TYQm1+BDninupIB +/zTeO1ygBGA66m6zpmkmuG7d8HXIducz+wIDAQAB +-----END RSA PUBLIC KEY-----`; + +const PEMS_RSA_PUBLIC_KEY_PKCS8 = `-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5WykLKHiBAhmZh5Whocg +pQQqZjdrApuRxRT21SJZx6Ce+Oz2V17/heozu5LEz63jCxW1NrBckzl/Ys8p9Leq +YTu6x/LbKloTjfEWxlzXnzUSqn9JHIxmmJQzjXp9X1D99Tj+NWpRGEIiCFE7JfDh +e2KnMGVDDg6kfCLokDdLo256LeQ4CEkViwY6d+at4xDlIHwvZZmG4Smk56eHhvQE +3I8sSAzgoLMBamQ5m3MbiULAYtxskCpCfjFxrL6Ziaaj7HZoneF40R30KCI9ygF8 +vkzxLwe3t5Y4XgHL9TYQm1+BDninupIB/zTeO1ygBGA66m6zpmkmuG7d8HXIducz ++wIDAQAB +-----END PUBLIC KEY-----`; + +const PEMS_RSA_CERT = `-----BEGIN CERTIFICATE----- +MIIDGzCCAgOgAwIBAgIUROs52CB3BsvEVLOCtALalnJG8tEwDQYJKoZIhvcNAQEL +BQAwHTEbMBkGA1UEAwwSUlNBIDIwNDggUHVibGljS2V5MB4XDTIxMDQxMzIxMDE0 +OVoXDTMxMDQxMTIxMDE0OVowHTEbMBkGA1UEAwwSUlNBIDIwNDggUHVibGljS2V5 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5WykLKHiBAhmZh5Whocg +pQQqZjdrApuRxRT21SJZx6Ce+Oz2V17/heozu5LEz63jCxW1NrBckzl/Ys8p9Leq +YTu6x/LbKloTjfEWxlzXnzUSqn9JHIxmmJQzjXp9X1D99Tj+NWpRGEIiCFE7JfDh +e2KnMGVDDg6kfCLokDdLo256LeQ4CEkViwY6d+at4xDlIHwvZZmG4Smk56eHhvQE +3I8sSAzgoLMBamQ5m3MbiULAYtxskCpCfjFxrL6Ziaaj7HZoneF40R30KCI9ygF8 +vkzxLwe3t5Y4XgHL9TYQm1+BDninupIB/zTeO1ygBGA66m6zpmkmuG7d8HXIducz ++wIDAQABo1MwUTAdBgNVHQ4EFgQUcRhRB6H5JqlDHbymwqydW2/EAt8wHwYDVR0j +BBgwFoAUcRhRB6H5JqlDHbymwqydW2/EAt8wDwYDVR0TAQH/BAUwAwEB/zANBgkq +hkiG9w0BAQsFAAOCAQEALXBmDizTp/Uz4M2A4nCl0AVclrXEk+YjAKqZnvtj44Gs +CUcpxtcXu64ppsSYCwawvzIm6B2Mdmib422aInH0e0oNrn8cRzC144Hjnzxguamj +LyZXnH/0wN9SAjqCKt++urH9wbRMIl0v+g4CWjGyY+eYkMmd1UMQvdCCCv6RVm56 +7dBCijJIHg23JbgPJD72JCluXtTYWllv3duSwuWeYHo5EftU456pDcztkgn9XwFk +PFGnHLmbjpSzjE7u29qCjwHl3CiUsjfUlYFl/mf27oDXPqaWqPYv3fWH3H3ymiZQ +cqptUF4hDtPkaNkKWFmlljChN92o8g/jrv4DVDgJzQ== +-----END CERTIFICATE-----`; + +/** EC P-256 key pair and certificate */ +const PEMS_EC_P256_PRIVATE_KEY = `-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIIhdQxQIcMnCHD3X4WqNv+VgycWmFoEZpRl9X0+dT9uHoAoGCCqGSM49 +AwEHoUQDQgAEFLQcBbzDweo6af4k3k0gKWMNWOZVn8+9hH2rv4DKKYZ7E1z64LBt +PnB1gMz++HDKySr2ozD3/46dIbQMXUZKpw== +-----END EC PRIVATE KEY-----`; + +const PEMS_EC_P256_PRIVATE_KEY_PKCS8 = `-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgiF1DFAhwycIcPdfh +ao2/5WDJxaYWgRmlGX1fT51P24ehRANCAAQUtBwFvMPB6jpp/iTeTSApYw1Y5lWf +z72Efau/gMophnsTXPrgsG0+cHWAzP74cMrJKvajMPf/jp0htAxdRkqn +-----END PRIVATE KEY-----`; + +const PEMS_EC_P256_PUBLIC_KEY = `-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEFLQcBbzDweo6af4k3k0gKWMNWOZV +n8+9hH2rv4DKKYZ7E1z64LBtPnB1gMz++HDKySr2ozD3/46dIbQMXUZKpw== +-----END PUBLIC KEY-----`; + +const PEMS_FOO = `-----BEGIN FOO----- +Rk9P +-----END FOO-----`; +const PEMS_BAR = `-----BEGIN BAR----- +QkFS +-----END BAR-----`; + +TestRegister.addTests([ + { + name: "PEMtoHex: Nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + "op": "PEM to Hex", + "args": [] + } + ] + }, + { + name: "PEMtoHex: No footer", + input: PEMS_RSA_PRIVATE_KEY_PKCS1.substr(0, 200), + expectedOutput: "PEM footer '-----END RSA PRIVATE KEY-----' not found", + recipeConfig: [ + { + "op": "PEM to Hex", + "args": [] + } + ] + }, + { + name: "PEMtoHex: Multiple PEMs", + input: PEMS_FOO + '\n' + PEMS_BAR, + expectedOutput: "FOOBAR", + recipeConfig: [ + { + "op": "PEM to Hex", + "args": [] + }, + { + "op": "From Hex", + "args": ["None"] + } + ] + }, + { + name: "PEMtoHex: Single line PEM", + input: PEMS_FOO.replace(/(\n|\r)/gm,""), + expectedOutput: "FOO", + recipeConfig: [ + { + "op": "PEM to Hex", + "args": [] + }, + { + "op": "From Hex", + "args": ["None"] + } + ] + }, + { + name: "PEMtoHex: EC P-256 Private Key", + input: PEMS_EC_P256_PRIVATE_KEY, + expectedOutput: "30770201010420885d43140870c9c21c3dd7e16a8dbfe560c9c5a6168119a5197d5f4f9d4fdb87a00a06082a8648ce3d030107a1440342000414b41c05bcc3c1ea3a69fe24de4d2029630d58e6559fcfbd847dabbf80ca29867b135cfae0b06d3e707580ccfef870cac92af6a330f7ff8e9d21b40c5d464aa7", + recipeConfig: [ + { + "op": "PEM to Hex", + "args": [] + } + ] + }, + { + name: "PEMtoHex: EC P-256 Private Key PKCS8", + input: PEMS_EC_P256_PRIVATE_KEY_PKCS8, + expectedOutput: "308187020100301306072a8648ce3d020106082a8648ce3d030107046d306b0201010420885d43140870c9c21c3dd7e16a8dbfe560c9c5a6168119a5197d5f4f9d4fdb87a1440342000414b41c05bcc3c1ea3a69fe24de4d2029630d58e6559fcfbd847dabbf80ca29867b135cfae0b06d3e707580ccfef870cac92af6a330f7ff8e9d21b40c5d464aa7", + recipeConfig: [ + { + "op": "PEM to Hex", + "args": [] + } + ] + }, + { + name: "PEMtoHex: EC P-256 Public Key", + input: PEMS_EC_P256_PUBLIC_KEY, + expectedOutput: "3059301306072a8648ce3d020106082a8648ce3d0301070342000414b41c05bcc3c1ea3a69fe24de4d2029630d58e6559fcfbd847dabbf80ca29867b135cfae0b06d3e707580ccfef870cac92af6a330f7ff8e9d21b40c5d464aa7", + recipeConfig: [ + { + "op": "PEM to Hex", + "args": [] + } + ] + }, + { + name: "PEMtoHex: RSA Private Key PKCS1", + input: PEMS_RSA_PRIVATE_KEY_PKCS1, + expectedOutput: "fb49bd96ffc5d1351a35d773921fac03", + recipeConfig: [ + { + "op": "PEM to Hex", + "args": [] + }, + { + "op": "MD5", + "args": [] + } + ] + }, + { + name: "PEMtoHex: RSA Private Key PKCS8", + input: PEMS_RSA_PRIVATE_KEY_PKCS8, + expectedOutput: "23086d03633689fee64680c3c24409eb", + recipeConfig: [ + { + "op": "PEM to Hex", + "args": [] + }, + { + "op": "MD5", + "args": [] + } + ] + }, + { + name: "PEMtoHex: RSA Public Key PKCS1", + input: PEMS_RSA_PUBLIC_KEY_PKCS1, + expectedOutput: "5fc3f1f6c5d5806760b12eaad0c0292c", + recipeConfig: [ + { + "op": "PEM to Hex", + "args": [] + }, + { + "op": "MD5", + "args": [] + } + ] + }, + { + name: "PEMtoHex: RSA Public Key PKCS8", + input: PEMS_RSA_PUBLIC_KEY_PKCS8, + expectedOutput: "30fbe8e9495d591232affebdd6206ea6", + recipeConfig: [ + { + "op": "PEM to Hex", + "args": [] + }, + { + "op": "MD5", + "args": [] + } + ] + }, + { + name: "PEMtoHex: Certificate", + input: PEMS_RSA_CERT, + expectedOutput: "6694d8ca4a0ceb84c3951d25dc05ec6e", + recipeConfig: [ + { + "op": "PEM to Hex", + "args": [] + }, + { + "op": "MD5", + "args": [] + } + ] + } +]); From b7a978505f8ea4f4d98dc5f166875db80bef0d8e Mon Sep 17 00:00:00 2001 From: CPlusSharp Date: Wed, 17 Nov 2021 20:19:42 +0100 Subject: [PATCH 24/99] PEMToHex: add magic check so it gets found --- src/core/operations/PEMToHex.mjs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/core/operations/PEMToHex.mjs b/src/core/operations/PEMToHex.mjs index 575f6f77..1c42d6a9 100644 --- a/src/core/operations/PEMToHex.mjs +++ b/src/core/operations/PEMToHex.mjs @@ -28,6 +28,12 @@ class PEMToHex extends Operation { this.inputType = "string"; this.outputType = "string"; this.args = []; + this.checks = [ + { + "pattern": "----BEGIN ([A-Z][A-Z ]+[A-Z])-----", + "args": [] + } + ]; } /** From 2574a63975ecced6e066ae673a986ad87394b7db Mon Sep 17 00:00:00 2001 From: Michael Rowley Date: Wed, 29 Dec 2021 19:32:39 +0000 Subject: [PATCH 25/99] Minor adjustments --- src/core/lib/Binary.mjs | 26 ++++++++++++---------- src/core/operations/ToUpperCase.mjs | 34 ++++++++++++++--------------- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/core/lib/Binary.mjs b/src/core/lib/Binary.mjs index dc63fc58..56db4c2a 100644 --- a/src/core/lib/Binary.mjs +++ b/src/core/lib/Binary.mjs @@ -19,27 +19,29 @@ import OperationError from "../errors/OperationError.mjs"; * @returns {string} * * @example - * // returns "00010000 00100000 00110000" + * // returns "00001010 00010100 00011110" * toBinary([10,20,30]); * - * // returns "00010000 00100000 00110000" - * toBinary([10,20,30], ":"); + * // returns "00001010:00010100:00011110" + * toBinary([10,20,30], "Colon"); + * + * // returns "1010:10100:11110" + * toBinary([10,20,30], "Colon", 0); */ export function toBinary(data, delim="Space", padding=8) { - if (!data) return ""; + if (!data) throw new OperationError("Empty input data enocuntered"); delim = Utils.charRep(delim); let output = ""; - for (let i = 0; i < data.length; i++) { - output += data[i].toString(2).padStart(padding, "0") + delim; + output += data[i].toString(2).padStart(padding, "0"); + if (i !== data.length - 1) output += delim; } - if (delim.length) { - return output.slice(0, -delim.length); - } else { - return output; + // Remove the delimiter from the end of the string. + output = output.slice(0, -delim.length); } + return output; } @@ -53,10 +55,10 @@ export function toBinary(data, delim="Space", padding=8) { * * @example * // returns [10,20,30] - * fromBinary("00010000 00100000 00110000"); + * fromBinary("00001010 00010100 00011110"); * * // returns [10,20,30] - * fromBinary("00010000:00100000:00110000", "Colon"); + * fromBinary("00001010:00010100:00011110", "Colon"); */ export function fromBinary(data, delim="Space", byteLen=8) { if (byteLen < 1 || Math.round(byteLen) !== byteLen) diff --git a/src/core/operations/ToUpperCase.mjs b/src/core/operations/ToUpperCase.mjs index 0bc9b5a9..eb0e315a 100644 --- a/src/core/operations/ToUpperCase.mjs +++ b/src/core/operations/ToUpperCase.mjs @@ -38,23 +38,23 @@ class ToUpperCase extends Operation { */ run(input, args) { const scope = args[0]; - - switch (scope) { - case "Word": - return input.replace(/(\b\w)/gi, function(m) { - return m.toUpperCase(); - }); - case "Sentence": - return input.replace(/(?:\.|^)\s*(\b\w)/gi, function(m) { - return m.toUpperCase(); - }); - case "Paragraph": - return input.replace(/(?:\n|^)\s*(\b\w)/gi, function(m) { - return m.toUpperCase(); - }); - case "All": /* falls through */ - default: - return input.toUpperCase(); + if (scope === "All") { + return input.toUpperCase(); + } + const scopeRegex = { + "Word": /(\b\w)/gi, + "Sentence": /(?:\.|^)\s*(\b\w)/gi, + "Paragraph": /(?:\n|^)\s*(\b\w)/gi + }[ scope ]; + if (scopeRegex !== undefined) { + // Use the regexes to capitalize the input. + return input.replace(scopeRegex, function(m) { + return m.toUpperCase(); + }); + } + else { + // The selected scope was invalid. + throw new OperationError("Unrecognized capitalization scope"); } } From ed542582f96541dd90fde52e4f0f07a9b6427ca6 Mon Sep 17 00:00:00 2001 From: Michael Rowley Date: Wed, 29 Dec 2021 19:59:48 +0000 Subject: [PATCH 26/99] Added more error-handling to ToUpperCase() --- src/core/operations/ToUpperCase.mjs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/core/operations/ToUpperCase.mjs b/src/core/operations/ToUpperCase.mjs index eb0e315a..08ffce87 100644 --- a/src/core/operations/ToUpperCase.mjs +++ b/src/core/operations/ToUpperCase.mjs @@ -37,6 +37,9 @@ class ToUpperCase extends Operation { * @returns {string} */ run(input, args) { + if (!args || args.length === 0) { + throw new OperationException("No capitalization scope was provided."); + } const scope = args[0]; if (scope === "All") { return input.toUpperCase(); From 578a61d33122d935557777b4dea38a6a27789a56 Mon Sep 17 00:00:00 2001 From: Robin Scholtes Date: Mon, 17 Jan 2022 23:37:24 +1300 Subject: [PATCH 27/99] add cetacean cipher encoder and decoder operations, tests. Update .gitignore to exclude idea generated files --- .gitignore | 3 + src/core/config/Categories.json | 2 + src/core/operations/CetaceanCipherDecode.mjs | 64 +++++++++++++++++++ src/core/operations/CetaceanCipherEncode.mjs | 54 ++++++++++++++++ tests/operations/index.mjs | 2 + .../operations/tests/CetaceanCipherDecode.mjs | 22 +++++++ .../operations/tests/CetaceanCipherEncode.mjs | 22 +++++++ 7 files changed, 169 insertions(+) create mode 100644 src/core/operations/CetaceanCipherDecode.mjs create mode 100644 src/core/operations/CetaceanCipherEncode.mjs create mode 100644 tests/operations/tests/CetaceanCipherDecode.mjs create mode 100644 tests/operations/tests/CetaceanCipherEncode.mjs diff --git a/.gitignore b/.gitignore index 40682646..a2aa99a3 100755 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ src/node/index.mjs **/*.DS_Store tests/browser/output/* +# ide +.idea +*.iml diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 09ee8d15..8f05d4c9 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -75,6 +75,8 @@ "Blowfish Decrypt", "DES Encrypt", "DES Decrypt", + "Cetacean Cipher Encode", + "Cetacean Cipher Decode", "Triple DES Encrypt", "Triple DES Decrypt", "RC2 Encrypt", diff --git a/src/core/operations/CetaceanCipherDecode.mjs b/src/core/operations/CetaceanCipherDecode.mjs new file mode 100644 index 00000000..a79b98c5 --- /dev/null +++ b/src/core/operations/CetaceanCipherDecode.mjs @@ -0,0 +1,64 @@ +/** + * @author dolphinOnKeys [robin@weird.io] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * Cetacean Cipher Decode operation + */ +class CetaceanCipherDecode extends Operation { + + /** + * CetaceanCipherDecode constructor + */ + constructor() { + super(); + + this.name = "Cetacean Cipher Decode"; + this.module = "Ciphers"; + this.description = "Decode Cetacean Cipher input.

e.g. EEEEEEEEEeeEeEEEEEEEEEEEEeeEeEEe becomes hi"; + this.infoURL = ""; + this.inputType = "string"; + this.outputType = "string"; + + this.checks = [ + { + pattern: "^(?:[eE]{16,})(?: [eE]{16,})*$", + flags: "", + args: [] + } + ] + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const binaryArray = []; + for ( const char of input ) { + if ( char === ' ' ) { + binaryArray.push(...[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 ]); + } else { + binaryArray.push( char === 'e' ? 1 : 0 ); + } + } + + const byteArray = []; + + for ( let i = 0; i < binaryArray.length; i += 16 ) { + byteArray.push(binaryArray.slice(i, i + 16).join('')) + } + + return byteArray.map( byte => + String.fromCharCode(parseInt( byte , 2 ) + ) + ).join(''); + } +} + +export default CetaceanCipherDecode; diff --git a/src/core/operations/CetaceanCipherEncode.mjs b/src/core/operations/CetaceanCipherEncode.mjs new file mode 100644 index 00000000..e32e4f81 --- /dev/null +++ b/src/core/operations/CetaceanCipherEncode.mjs @@ -0,0 +1,54 @@ +/** + * @author dolphinOnKeys [robin@weird.io] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * Cetacean Cipher Encode operation + */ +class CetaceanCipherEncode extends Operation { + + /** + * CetaceanCipherEncode constructor + */ + constructor() { + super(); + + this.name = "Cetacean Cipher Encode"; + this.module = "Ciphers"; + this.description = "Converts any input into Cetacean Cipher.

e.g. hi becomes EEEEEEEEEeeEeEEEEEEEEEEEEeeEeEEe\""; + this.infoURL = ""; + this.inputType = "string"; + this.outputType = "string"; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let result = []; + let charArray = input.split(''); + + charArray.map( ( character ) => { + if ( character === ' ' ) { + result.push( character ); + } else { + const binaryArray = this.encodeToBinary( character ).split(''); + result.push( binaryArray.map(( str ) => str === '1' ? 'e' : 'E' ).join('')); + } + }); + + return result.join(''); + } + + encodeToBinary( char, padding = 16 ) { + return char.charCodeAt(0).toString(2).padStart( padding, '0'); + } +} + +export default CetaceanCipherEncode; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 9add20b9..5200e3a1 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -26,6 +26,8 @@ import "./tests/Base62.mjs"; import "./tests/BitwiseOp.mjs"; import "./tests/ByteRepr.mjs"; import "./tests/CartesianProduct.mjs"; +import "./tests/CetaceanCipherEncode.mjs"; +import "./tests/CetaceanCipherDecode.mjs"; import "./tests/CharEnc.mjs"; import "./tests/ChangeIPFormat.mjs"; import "./tests/Charts.mjs"; diff --git a/tests/operations/tests/CetaceanCipherDecode.mjs b/tests/operations/tests/CetaceanCipherDecode.mjs new file mode 100644 index 00000000..e834dc1e --- /dev/null +++ b/tests/operations/tests/CetaceanCipherDecode.mjs @@ -0,0 +1,22 @@ +/** + * CetaceanCipher Encode tests + * + * @author dolphinOnKeys + * @copyright Crown Copyright 2022 + * @licence Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Cetacean Cipher Decode", + input: "EEEEEEEEEeeEEEEe EEEEEEEEEeeEEEeE EEEEEEEEEeeEEEee EEeeEEEEEeeEEeee", + expectedOutput: "a b c で", + recipeConfig: [ + { + op: "Cetacean Cipher Decode", + args: [] + }, + ], + } +]); diff --git a/tests/operations/tests/CetaceanCipherEncode.mjs b/tests/operations/tests/CetaceanCipherEncode.mjs new file mode 100644 index 00000000..bef76388 --- /dev/null +++ b/tests/operations/tests/CetaceanCipherEncode.mjs @@ -0,0 +1,22 @@ +/** + * CetaceanCipher Encode tests + * + * @author dolphinOnKeys + * @copyright Crown Copyright 2022 + * @licence Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Cetacean Cipher Encode", + input: "a b c で", + expectedOutput: "EEEEEEEEEeeEEEEe EEEEEEEEEeeEEEeE EEEEEEEEEeeEEEee EEeeEEEEEeeEEeee", + recipeConfig: [ + { + op: "Cetacean Cipher Encode", + args: [] + }, + ], + } +]); From e712af33b78764fceb90f40a17ffb800f13fcd38 Mon Sep 17 00:00:00 2001 From: Thomas Leplus Date: Mon, 11 Apr 2022 18:44:14 -0700 Subject: [PATCH 28/99] Adding ROT8000 --- src/core/config/Categories.json | 4 +- src/core/operations/ROT8000.mjs | 105 ++++++++++++++++++++++++++++++ tests/browser/ops.js | 1 + tests/operations/tests/Rotate.mjs | 33 ++++++++++ 4 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 src/core/operations/ROT8000.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 9540f8a3..f9e47aac 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -87,6 +87,7 @@ "SM4 Decrypt", "ROT13", "ROT47", + "ROT8000", "XOR", "XOR Brute Force", "Vigenère Encode", @@ -176,7 +177,8 @@ "Bit shift right", "Rotate left", "Rotate right", - "ROT13" + "ROT13", + "ROT8000" ] }, { diff --git a/src/core/operations/ROT8000.mjs b/src/core/operations/ROT8000.mjs new file mode 100644 index 00000000..1f039de0 --- /dev/null +++ b/src/core/operations/ROT8000.mjs @@ -0,0 +1,105 @@ +/** + * @author Daniel Temkin [http://danieltemkin.com] + * @author Thomas Leplus [https://www.leplus.org] + * @copyright Crown Copyright 2021 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * ROT8000 operation. + */ +class ROT8000 extends Operation { + + /** + * ROT8000 constructor + */ + constructor() { + super(); + this.name = "ROT8000"; + this.module = "Default"; + this.description = "The simple Caesar-cypher encryption that replaces each Unicode character with the one 0x8000 places forward or back along the alphabet."; + this.infoURL = "https://github.com/rottytooth/rot8000"; + this.inputType = "string"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + // Inspired from https://github.com/rottytooth/rot8000/blob/main/rot8000.js + // these come from the valid-code-point-transitions.json file generated from the c# proj + // this is done bc: 1) don't trust JS's understanging of surrogate pairs and 2) consistency with original rot8000 + const validCodePoints = JSON.parse('{"33":true,"127":false,"161":true,"5760":false,"5761":true,"8192":false,"8203":true,"8232":false,"8234":true,"8239":false,"8240":true,"8287":false,"8288":true,"12288":false,"12289":true,"55296":false,"57344":true}'); + const bmpSize = 0x10000; + const rotList = {}; // the mapping of char to rotated char + const hiddenBlocks = []; + let startBlock = 0; + for (const key in validCodePoints) { + if (Object.prototype.hasOwnProperty.call(validCodePoints, key)) { + if (validCodePoints[key] === true) + hiddenBlocks.push({ start: startBlock, end: parseInt(key, 10) - 1 }); + else + startBlock = parseInt(key, 10); + } + } + const validIntList = []; // list of all valid chars + let currValid = false; + for (let i = 0; i < bmpSize; i++) { + if (validCodePoints[i] !== undefined) { + currValid = validCodePoints[i]; + } + if (currValid) validIntList.push(i); + } + const rotateNum = Object.keys(validIntList).length / 2; + // go through every valid char and find its match + for (let i = 0; i < validIntList.length; i++) { + rotList[String.fromCharCode(validIntList[i])] = + String.fromCharCode(validIntList[(i + rotateNum) % (rotateNum * 2)]); + } + let output = ""; + for (let count = 0; count < input.length; count++) { + // if it is not in the mappings list, just add it directly (no rotation) + if (rotList[input[count]] === undefined) { + output += input[count]; + continue; + } + // otherwise, rotate it and add it to the string + output += rotList[input[count]]; + } + return output; + } + + /** + * Highlight ROT8000 + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight ROT8000 in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } +} + +export default ROT8000; diff --git a/tests/browser/ops.js b/tests/browser/ops.js index f2522a79..62436a89 100644 --- a/tests/browser/ops.js +++ b/tests/browser/ops.js @@ -246,6 +246,7 @@ module.exports = { // testOp(browser, "RIPEMD", "test input", "test_output"); // testOp(browser, "ROT13", "test input", "test_output"); // testOp(browser, "ROT47", "test input", "test_output"); + // testOp(browser, "ROT8000", "test input", "test_output"); // testOp(browser, "Rail Fence Cipher Decode", "test input", "test_output"); // testOp(browser, "Rail Fence Cipher Encode", "test input", "test_output"); // testOp(browser, "Randomize Colour Palette", "test input", "test_output"); diff --git a/tests/operations/tests/Rotate.mjs b/tests/operations/tests/Rotate.mjs index 85e31508..c12fa377 100644 --- a/tests/operations/tests/Rotate.mjs +++ b/tests/operations/tests/Rotate.mjs @@ -212,4 +212,37 @@ TestRegister.addTests([ }, ], }, + { + name: "ROT8000: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "ROT8000", + args: [] + }, + ], + }, + { + name: "ROT8000: normal", + input: "The Quick Brown Fox Jumped Over The Lazy Dog.", + expectedOutput: "籝籱籮 籚籾籲籬籴 籋类籸粀籷 籏籸粁 籓籾籶籹籮籭 籘籿籮类 籝籱籮 籕籪粃粂 籍籸籰簷", + recipeConfig: [ + { + op: "ROT8000", + args: [] + }, + ], + }, + { + name: "ROT8000: backward", + input: "籝籱籮 籚籾籲籬籴 籋类籸粀籷 籏籸粁 籓籾籶籹籮籭 籘籿籮类 籝籱籮 籕籪粃粂 籍籸籰簷", + expectedOutput: "The Quick Brown Fox Jumped Over The Lazy Dog.", + recipeConfig: [ + { + op: "ROT8000", + args: [] + }, + ], + }, ]); From 5b68bad18568450cbbd2d42d40799e62185c2cb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luke=20Sern=C3=A9?= Date: Fri, 13 May 2022 17:35:50 +0200 Subject: [PATCH 29/99] Support UTF8 encoded characters in Substitution operation This adds support for UTF8-encoded characters in the input and the parameters. --- src/core/operations/Substitute.mjs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/core/operations/Substitute.mjs b/src/core/operations/Substitute.mjs index 39f13e89..1afe1f42 100644 --- a/src/core/operations/Substitute.mjs +++ b/src/core/operations/Substitute.mjs @@ -44,8 +44,8 @@ class Substitute extends Operation { * @returns {string} */ run(input, args) { - const plaintext = Utils.expandAlphRange(args[0]).join(""), - ciphertext = Utils.expandAlphRange(args[1]).join(""); + const plaintext = Utils.expandAlphRange([...args[0]]), + ciphertext = Utils.expandAlphRange([...args[1]]); let output = "", index = -1; @@ -53,9 +53,9 @@ class Substitute extends Operation { output = "Warning: Plaintext and Ciphertext lengths differ\n\n"; } - for (let i = 0; i < input.length; i++) { - index = plaintext.indexOf(input[i]); - output += index > -1 && index < ciphertext.length ? ciphertext[index] : input[i]; + for (const character of input) { + index = plaintext.indexOf(character); + output += index > -1 && index < ciphertext.length ? ciphertext[index] : character; } return output; From 11da4188ee7d96df6ca96726f0d6321137294f94 Mon Sep 17 00:00:00 2001 From: MikeCAT Date: Fri, 20 May 2022 11:59:04 +0000 Subject: [PATCH 30/99] fix "To Base45" ( #1351 ) --- src/core/operations/ToBase45.mjs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/core/operations/ToBase45.mjs b/src/core/operations/ToBase45.mjs index 3246ab4e..a1cd9d2f 100644 --- a/src/core/operations/ToBase45.mjs +++ b/src/core/operations/ToBase45.mjs @@ -65,6 +65,10 @@ class ToBase45 extends Operation { if (chars < 2) { res.push("0"); + chars++; + } + if (pair.length > 1 && chars < 3) { + res.push("0"); } } From a895d1d82a2f92d440a0c5eca2bc7c898107b737 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Mon, 30 May 2022 18:06:15 +0100 Subject: [PATCH 31/99] Added 'Parse TCP' operation --- Gruntfile.js | 3 +- package-lock.json | 14 +- package.json | 2 +- src/core/config/Categories.json | 1 + src/core/lib/Binary.mjs | 14 +- src/core/lib/FileSignatures.mjs | 24 +-- src/core/lib/Protocol.mjs | 47 +++++ src/core/lib/Stream.mjs | 34 ++-- src/core/operations/ParseTCP.mjs | 245 +++++++++++++++++++++++++++ src/core/operations/ParseUDP.mjs | 53 +++--- src/web/waiters/OperationsWaiter.mjs | 6 +- tests/operations/index.mjs | 1 + tests/operations/tests/ParseTCP.mjs | 44 +++++ tests/operations/tests/ParseUDP.mjs | 23 +-- 14 files changed, 430 insertions(+), 81 deletions(-) create mode 100644 src/core/lib/Protocol.mjs create mode 100644 src/core/operations/ParseTCP.mjs create mode 100644 tests/operations/tests/ParseTCP.mjs diff --git a/Gruntfile.js b/Gruntfile.js index f073a136..78c26532 100755 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -217,7 +217,8 @@ module.exports = function (grunt) { client: { logging: "error", overlay: true - } + }, + hot: "only" }, plugins: [ new webpack.DefinePlugin(BUILD_CONSTANTS), diff --git a/package-lock.json b/package-lock.json index d956ea2f..16c88c1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -98,7 +98,7 @@ "autoprefixer": "^10.4.4", "babel-loader": "^8.2.4", "babel-plugin-dynamic-import-node": "^2.3.3", - "chromedriver": "^99.0.0", + "chromedriver": "^101.0.0", "cli-progress": "^3.10.0", "colors": "^1.4.0", "copy-webpack-plugin": "^10.2.4", @@ -4467,9 +4467,9 @@ } }, "node_modules/chromedriver": { - "version": "99.0.0", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-99.0.0.tgz", - "integrity": "sha512-pyB+5LuyZdb7EBPL3i5D5yucZUD+SlkdiUtmpjaEnLd9zAXp+SvD/hP5xF4l/ZmWvUo/1ZLxAI1YBdhazGTpgA==", + "version": "101.0.0", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-101.0.0.tgz", + "integrity": "sha512-LkkWxy6KM/0YdJS8qBeg5vfkTZTRamhBfOttb4oic4echDgWvCU1E8QcBbUBOHqZpSrYMyi7WMKmKMhXFUaZ+w==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -19171,9 +19171,9 @@ "dev": true }, "chromedriver": { - "version": "99.0.0", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-99.0.0.tgz", - "integrity": "sha512-pyB+5LuyZdb7EBPL3i5D5yucZUD+SlkdiUtmpjaEnLd9zAXp+SvD/hP5xF4l/ZmWvUo/1ZLxAI1YBdhazGTpgA==", + "version": "101.0.0", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-101.0.0.tgz", + "integrity": "sha512-LkkWxy6KM/0YdJS8qBeg5vfkTZTRamhBfOttb4oic4echDgWvCU1E8QcBbUBOHqZpSrYMyi7WMKmKMhXFUaZ+w==", "dev": true, "requires": { "@testim/chrome-version": "^1.1.2", diff --git a/package.json b/package.json index 73d2d19a..d2d71272 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "autoprefixer": "^10.4.4", "babel-loader": "^8.2.4", "babel-plugin-dynamic-import-node": "^2.3.3", - "chromedriver": "^99.0.0", + "chromedriver": "^101.0.0", "cli-progress": "^3.10.0", "colors": "^1.4.0", "copy-webpack-plugin": "^10.2.4", diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 9540f8a3..c15ec3d2 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -190,6 +190,7 @@ "Parse IP range", "Parse IPv6 address", "Parse IPv4 header", + "Parse TCP", "Parse UDP", "Parse SSH Host Key", "Parse URI", diff --git a/src/core/lib/Binary.mjs b/src/core/lib/Binary.mjs index dc63fc58..4e41d2a1 100644 --- a/src/core/lib/Binary.mjs +++ b/src/core/lib/Binary.mjs @@ -13,7 +13,7 @@ import OperationError from "../errors/OperationError.mjs"; /** * Convert a byte array into a binary string. * - * @param {Uint8Array|byteArray} data + * @param {Uint8Array|byteArray|number} data * @param {string} [delim="Space"] * @param {number} [padding=8] * @returns {string} @@ -26,13 +26,17 @@ import OperationError from "../errors/OperationError.mjs"; * toBinary([10,20,30], ":"); */ export function toBinary(data, delim="Space", padding=8) { - if (!data) return ""; - delim = Utils.charRep(delim); let output = ""; - for (let i = 0; i < data.length; i++) { - output += data[i].toString(2).padStart(padding, "0") + delim; + if (data.length) { // array + for (let i = 0; i < data.length; i++) { + output += data[i].toString(2).padStart(padding, "0") + delim; + } + } else if (typeof data === "number") { // Single value + return data.toString(2).padStart(padding, "0"); + } else { + return ""; } if (delim.length) { diff --git a/src/core/lib/FileSignatures.mjs b/src/core/lib/FileSignatures.mjs index a515a9e6..7b77f2d9 100644 --- a/src/core/lib/FileSignatures.mjs +++ b/src/core/lib/FileSignatures.mjs @@ -3778,8 +3778,8 @@ function parseDEFLATE(stream) { while (!finalBlock) { // Read header - finalBlock = stream.readBits(1); - const blockType = stream.readBits(2); + finalBlock = stream.readBits(1, "le"); + const blockType = stream.readBits(2, "le"); if (blockType === 0) { /* No compression */ @@ -3798,16 +3798,16 @@ function parseDEFLATE(stream) { /* Dynamic Huffman */ // Read the number of liternal and length codes - const hlit = stream.readBits(5) + 257; + const hlit = stream.readBits(5, "le") + 257; // Read the number of distance codes - const hdist = stream.readBits(5) + 1; + const hdist = stream.readBits(5, "le") + 1; // Read the number of code lengths - const hclen = stream.readBits(4) + 4; + const hclen = stream.readBits(4, "le") + 4; // Parse code lengths const codeLengths = new Uint8Array(huffmanOrder.length); for (let i = 0; i < hclen; i++) { - codeLengths[huffmanOrder[i]] = stream.readBits(3); + codeLengths[huffmanOrder[i]] = stream.readBits(3, "le"); } // Parse length table @@ -3819,16 +3819,16 @@ function parseDEFLATE(stream) { code = readHuffmanCode(stream, codeLengthsTable); switch (code) { case 16: - repeat = 3 + stream.readBits(2); + repeat = 3 + stream.readBits(2, "le"); while (repeat--) lengthTable[i++] = prev; break; case 17: - repeat = 3 + stream.readBits(3); + repeat = 3 + stream.readBits(3, "le"); while (repeat--) lengthTable[i++] = 0; prev = 0; break; case 18: - repeat = 11 + stream.readBits(7); + repeat = 11 + stream.readBits(7, "le"); while (repeat--) lengthTable[i++] = 0; prev = 0; break; @@ -3886,11 +3886,11 @@ function parseHuffmanBlock(stream, litTab, distTab) { if (code < 256) continue; // Length code - stream.readBits(lengthExtraTable[code - 257]); + stream.readBits(lengthExtraTable[code - 257], "le"); // Dist code code = readHuffmanCode(stream, distTab); - stream.readBits(distanceExtraTable[code]); + stream.readBits(distanceExtraTable[code], "le"); } } @@ -3948,7 +3948,7 @@ function readHuffmanCode(stream, table) { const [codeTable, maxCodeLength] = table; // Read max length - const bitsBuf = stream.readBits(maxCodeLength); + const bitsBuf = stream.readBits(maxCodeLength, "le"); const codeWithLength = codeTable[bitsBuf & ((1 << maxCodeLength) - 1)]; const codeLength = codeWithLength >>> 16; diff --git a/src/core/lib/Protocol.mjs b/src/core/lib/Protocol.mjs new file mode 100644 index 00000000..57d2374a --- /dev/null +++ b/src/core/lib/Protocol.mjs @@ -0,0 +1,47 @@ +/** + * Protocol parsing functions. + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import BigNumber from "bignumber.js"; +import {toHexFast} from "../lib/Hex.mjs"; + +/** + * Recursively displays a JSON object as an HTML table + * + * @param {Object} obj + * @returns string + */ +export function objToTable(obj, nested=false) { + let html = ``; + if (!nested) + html += ` + + + `; + + for (const key in obj) { + html += ``; + if (typeof obj[key] === "object") + html += ``; + else + html += ``; + html += ""; + } + html += "
FieldValue
${key}${objToTable(obj[key], true)}${obj[key]}
"; + return html; +} + +/** + * Converts bytes into a BigNumber string + * @param {Uint8Array} bs + * @returns {string} + */ +export function bytesToLargeNumber(bs) { + return BigNumber(toHexFast(bs), 16).toString(); +} diff --git a/src/core/lib/Stream.mjs b/src/core/lib/Stream.mjs index f332f908..b2a183f4 100644 --- a/src/core/lib/Stream.mjs +++ b/src/core/lib/Stream.mjs @@ -27,15 +27,17 @@ export default class Stream { } /** - * Get a number of bytes from the current position. + * Get a number of bytes from the current position, or all remaining bytes. * - * @param {number} numBytes + * @param {number} [numBytes=null] * @returns {Uint8Array} */ - getBytes(numBytes) { + getBytes(numBytes=null) { if (this.position > this.length) return undefined; - const newPosition = this.position + numBytes; + const newPosition = numBytes !== null ? + this.position + numBytes : + this.length; const bytes = this.bytes.slice(this.position, newPosition); this.position = newPosition; this.bitPos = 0; @@ -91,34 +93,40 @@ export default class Stream { } /** - * Reads a number of bits from the buffer. - * - * @TODO Add endianness + * Reads a number of bits from the buffer in big or little endian. * * @param {number} numBits + * @param {string} [endianness="be"] * @returns {number} */ - readBits(numBits) { + readBits(numBits, endianness="be") { if (this.position > this.length) return undefined; let bitBuf = 0, bitBufLen = 0; // Add remaining bits from current byte - bitBuf = (this.bytes[this.position++] & bitMask(this.bitPos)) >>> this.bitPos; + bitBuf = this.bytes[this.position++] & bitMask(this.bitPos); + if (endianness !== "be") bitBuf >>>= this.bitPos; bitBufLen = 8 - this.bitPos; this.bitPos = 0; // Not enough bits yet while (bitBufLen < numBits) { - bitBuf |= this.bytes[this.position++] << bitBufLen; + if (endianness === "be") + bitBuf = (bitBuf << bitBufLen) | this.bytes[this.position++]; + else + bitBuf |= this.bytes[this.position++] << bitBufLen; bitBufLen += 8; } // Reverse back to numBits if (bitBufLen > numBits) { const excess = bitBufLen - numBits; - bitBuf &= (1 << numBits) - 1; + if (endianness === "be") + bitBuf >>>= excess; + else + bitBuf &= (1 << numBits) - 1; bitBufLen -= excess; this.position--; this.bitPos = 8 - excess; @@ -133,7 +141,9 @@ export default class Stream { * @returns {number} The bit mask */ function bitMask(bitPos) { - return 256 - (1 << bitPos); + return endianness === "be" ? + (1 << (8 - bitPos)) - 1 : + 256 - (1 << bitPos); } } diff --git a/src/core/operations/ParseTCP.mjs b/src/core/operations/ParseTCP.mjs new file mode 100644 index 00000000..7adb37e0 --- /dev/null +++ b/src/core/operations/ParseTCP.mjs @@ -0,0 +1,245 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Stream from "../lib/Stream.mjs"; +import {toHexFast, fromHex} from "../lib/Hex.mjs"; +import {toBinary} from "../lib/Binary.mjs"; +import {objToTable, bytesToLargeNumber} from "../lib/Protocol.mjs"; +import Utils from "../Utils.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import BigNumber from "bignumber.js"; + +/** + * Parse TCP operation + */ +class ParseTCP extends Operation { + + /** + * ParseTCP constructor + */ + constructor() { + super(); + + this.name = "Parse TCP"; + this.module = "Default"; + this.description = "Parses a TCP header and payload (if present)."; + this.infoURL = "https://wikipedia.org/wiki/Transmission_Control_Protocol"; + this.inputType = "string"; + this.outputType = "json"; + this.presentType = "html"; + this.args = [ + { + name: "Input format", + type: "option", + value: ["Hex", "Raw"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {html} + */ + run(input, args) { + const format = args[0]; + + if (format === "Hex") { + input = fromHex(input); + } else if (format === "Raw") { + input = Utils.strToArrayBuffer(input); + } else { + throw new OperationError("Unrecognised input format."); + } + + const s = new Stream(new Uint8Array(input)); + if (s.length < 20) { + throw new OperationError("Need at least 20 bytes for a TCP Header"); + } + + // Parse Header + const TCPPacket = { + "Source port": s.readInt(2), + "Destination port": s.readInt(2), + "Sequence number": bytesToLargeNumber(s.getBytes(4)), + "Acknowledgement number": s.readInt(4), + "Data offset": s.readBits(4), + "Flags": { + "Reserved": toBinary(s.readBits(3), "", 3), + "NS": s.readBits(1), + "CWR": s.readBits(1), + "ECE": s.readBits(1), + "URG": s.readBits(1), + "ACK": s.readBits(1), + "PSH": s.readBits(1), + "RST": s.readBits(1), + "SYN": s.readBits(1), + "FIN": s.readBits(1), + }, + "Window size": s.readInt(2), + "Checksum": "0x" + toHexFast(s.getBytes(2)), + "Urgent pointer": "0x" + toHexFast(s.getBytes(2)) + }; + + // Parse options if present + let windowScaleShift = 0; + if (TCPPacket["Data offset"] > 5) { + let remainingLength = TCPPacket["Data offset"] * 4 - 20; + + const options = {}; + while (remainingLength > 0) { + const option = { + "Kind": s.readInt(1) + }; + + let opt = { name: "Reserved", length: true }; + if (Object.prototype.hasOwnProperty.call(TCP_OPTION_KIND_LOOKUP, option.Kind)) { + opt = TCP_OPTION_KIND_LOOKUP[option.Kind]; + } + + // Add Length and Value fields + if (opt.length) { + option.Length = s.readInt(1); + + if (option.Length > 2) { + if (Object.prototype.hasOwnProperty.call(opt, "parser")) { + option.Value = opt.parser(s.getBytes(option.Length - 2)); + } else { + option.Value = option.Length <= 6 ? + s.readInt(option.Length - 2): + "0x" + toHexFast(s.getBytes(option.Length - 2)); + } + + // Store Window Scale shift for later + if (option.Kind === 3 && option.Value) { + windowScaleShift = option.Value["Shift count"]; + } + } + } + options[opt.name] = option; + + const length = option.Length || 1; + remainingLength -= length; + } + TCPPacket.Options = options; + } + + if (s.hasMore()) { + TCPPacket.Data = "0x" + toHexFast(s.getBytes()); + } + + // Improve values + TCPPacket["Data offset"] = `${TCPPacket["Data offset"]} (${TCPPacket["Data offset"] * 4} bytes)`; + const trueWndSize = BigNumber(TCPPacket["Window size"]).multipliedBy(BigNumber(2).pow(BigNumber(windowScaleShift))); + TCPPacket["Window size"] = `${TCPPacket["Window size"]} (Scaled: ${trueWndSize})`; + + return TCPPacket; + } + + /** + * Displays the TCP Packet in a tabular style + * @param {Object} data + * @returns {html} + */ + present(data) { + return objToTable(data); + } + +} + +// Taken from https://www.iana.org/assignments/tcp-parameters/tcp-parameters.xhtml +// on 2022-05-30 +const TCP_OPTION_KIND_LOOKUP = { + 0: { name: "End of Option List", length: false }, + 1: { name: "No-Operation", length: false }, + 2: { name: "Maximum Segment Size", length: true }, + 3: { name: "Window Scale", length: true, parser: windowScaleParser }, + 4: { name: "SACK Permitted", length: true }, + 5: { name: "SACK", length: true }, + 6: { name: "Echo (obsoleted by option 8)", length: true }, + 7: { name: "Echo Reply (obsoleted by option 8)", length: true }, + 8: { name: "Timestamps", length: true, parser: tcpTimestampParser }, + 9: { name: "Partial Order Connection Permitted (obsolete)", length: true }, + 10: { name: "Partial Order Service Profile (obsolete)", length: true }, + 11: { name: "CC (obsolete)", length: true }, + 12: { name: "CC.NEW (obsolete)", length: true }, + 13: { name: "CC.ECHO (obsolete)", length: true }, + 14: { name: "TCP Alternate Checksum Request (obsolete)", length: true, parser: tcpAlternateChecksumParser }, + 15: { name: "TCP Alternate Checksum Data (obsolete)", length: true }, + 16: { name: "Skeeter", length: true }, + 17: { name: "Bubba", length: true }, + 18: { name: "Trailer Checksum Option", length: true }, + 19: { name: "MD5 Signature Option (obsoleted by option 29)", length: true }, + 20: { name: "SCPS Capabilities", length: true }, + 21: { name: "Selective Negative Acknowledgements", length: true }, + 22: { name: "Record Boundaries", length: true }, + 23: { name: "Corruption experienced", length: true }, + 24: { name: "SNAP", length: true }, + 25: { name: "Unassigned (released 2000-12-18)", length: true }, + 26: { name: "TCP Compression Filter", length: true }, + 27: { name: "Quick-Start Response", length: true }, + 28: { name: "User Timeout Option (also, other known unauthorized use)", length: true }, + 29: { name: "TCP Authentication Option (TCP-AO)", length: true }, + 30: { name: "Multipath TCP (MPTCP)", length: true }, + 69: { name: "Encryption Negotiation (TCP-ENO)", length: true }, + 70: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true }, + 76: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true }, + 77: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true }, + 78: { name: "Reserved (known unauthorized use without proper IANA assignment)", length: true }, + 253: { name: "RFC3692-style Experiment 1 (also improperly used for shipping products) ", length: true }, + 254: { name: "RFC3692-style Experiment 2 (also improperly used for shipping products) ", length: true } +}; + +/** + * Parses the TCP Alternate Checksum Request field + * @param {Uint8Array} data + */ +function tcpAlternateChecksumParser(data) { + const lookup = { + 0: "TCP Checksum", + 1: "8-bit Fletchers's algorithm", + 2: "16-bit Fletchers's algorithm", + 3: "Redundant Checksum Avoidance" + }[data[0]]; + + return `${lookup} (0x${toHexFast(data)})`; +} + +/** + * Parses the TCP Timestamp field + * @param {Uint8Array} data + */ +function tcpTimestampParser(data) { + const s = new Stream(data); + + if (s.length !== 8) + return `Error: Timestamp field should be 8 bytes long (received 0x${toHexFast(data)})`; + + const tsval = bytesToLargeNumber(s.getBytes(4)), + tsecr = bytesToLargeNumber(s.getBytes(4)); + + return { + "Current Timestamp": tsval, + "Echo Reply": tsecr + }; +} + +/** + * Parses the Window Scale field + * @param {Uint8Array} data + */ +function windowScaleParser(data) { + if (data.length !== 1) + return `Error: Window Scale should be one byte long (received 0x${toHexFast(data)})`; + + return { + "Shift count": data[0], + "Multiplier": 1 << data[0] + }; +} + +export default ParseTCP; diff --git a/src/core/operations/ParseUDP.mjs b/src/core/operations/ParseUDP.mjs index 0a88fd5d..2aa762ae 100644 --- a/src/core/operations/ParseUDP.mjs +++ b/src/core/operations/ParseUDP.mjs @@ -6,7 +6,9 @@ import Operation from "../Operation.mjs"; import Stream from "../lib/Stream.mjs"; -import {toHex} from "../lib/Hex.mjs"; +import {toHexFast, fromHex} from "../lib/Hex.mjs"; +import {objToTable} from "../lib/Protocol.mjs"; +import Utils from "../Utils.mjs"; import OperationError from "../errors/OperationError.mjs"; /** @@ -24,58 +26,61 @@ class ParseUDP extends Operation { this.module = "Default"; this.description = "Parses a UDP header and payload (if present)."; this.infoURL = "https://wikipedia.org/wiki/User_Datagram_Protocol"; - this.inputType = "ArrayBuffer"; + this.inputType = "string"; this.outputType = "json"; this.presentType = "html"; - this.args = []; + this.args = [ + { + name: "Input format", + type: "option", + value: ["Hex", "Raw"] + } + ]; } /** - * @param {ArrayBuffer} input + * @param {string} input + * @param {Object[]} args * @returns {Object} */ run(input, args) { - if (input.byteLength < 8) { - throw new OperationError("Need 8 bytes for a UDP Header"); + const format = args[0]; + + if (format === "Hex") { + input = fromHex(input); + } else if (format === "Raw") { + input = Utils.strToArrayBuffer(input); + } else { + throw new OperationError("Unrecognised input format."); } const s = new Stream(new Uint8Array(input)); + if (s.length < 8) { + throw new OperationError("Need 8 bytes for a UDP Header"); + } + // Parse Header const UDPPacket = { "Source port": s.readInt(2), "Destination port": s.readInt(2), "Length": s.readInt(2), - "Checksum": toHex(s.getBytes(2), "") + "Checksum": "0x" + toHexFast(s.getBytes(2)) }; // Parse data if present if (s.hasMore()) { - UDPPacket.Data = toHex(s.getBytes(UDPPacket.Length - 8), ""); + UDPPacket.Data = "0x" + toHexFast(s.getBytes(UDPPacket.Length - 8)); } return UDPPacket; } /** - * Displays the UDP Packet in a table style + * Displays the UDP Packet in a tabular style * @param {Object} data * @returns {html} */ present(data) { - const html = []; - html.push(""); - html.push(""); - html.push(""); - html.push(""); - html.push(""); - - for (const key in data) { - html.push(""); - html.push(""); - html.push(""); - html.push(""); - } - html.push("
FieldValue
" + key + "" + data[key] + "
"); - return html.join(""); + return objToTable(data); } } diff --git a/src/web/waiters/OperationsWaiter.mjs b/src/web/waiters/OperationsWaiter.mjs index 6efbab72..dee0dd06 100755 --- a/src/web/waiters/OperationsWaiter.mjs +++ b/src/web/waiters/OperationsWaiter.mjs @@ -109,11 +109,15 @@ class OperationsWaiter { const matchedOps = []; const matchedDescs = []; + // Create version with no whitespace for the fuzzy match + // Helps avoid missing matches e.g. query "TCP " would not find "Parse TCP" + const inStrNWS = inStr.replace(/\s/g, ""); + for (const opName in this.app.operations) { const op = this.app.operations[opName]; // Match op name using fuzzy match - const [nameMatch, score, idxs] = fuzzyMatch(inStr, opName); + const [nameMatch, score, idxs] = fuzzyMatch(inStrNWS, opName); // Match description based on exact match const descPos = op.description.toLowerCase().indexOf(inStr.toLowerCase()); diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 4dd469ce..67d16ab5 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -96,6 +96,7 @@ import "./tests/Protobuf.mjs"; import "./tests/ParseSSHHostKey.mjs"; import "./tests/DefangIP.mjs"; import "./tests/ParseUDP.mjs"; +import "./tests/ParseTCP.mjs"; import "./tests/AvroToJSON.mjs"; import "./tests/Lorenz.mjs"; import "./tests/LuhnChecksum.mjs"; diff --git a/tests/operations/tests/ParseTCP.mjs b/tests/operations/tests/ParseTCP.mjs new file mode 100644 index 00000000..acecb5d7 --- /dev/null +++ b/tests/operations/tests/ParseTCP.mjs @@ -0,0 +1,44 @@ +/** + * Parse TCP tests. + * + * @author n1474335 + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Parse TCP: No options", + input: "c2eb0050a138132e70dc9fb9501804025ea70000", + expectedMatch: /1026 \(Scaled: 1026\)/, + recipeConfig: [ + { + op: "Parse TCP", + args: ["Hex"], + } + ], + }, + { + name: "Parse TCP: Options", + input: "c2eb0050a1380c1f000000008002faf080950000020405b40103030801010402", + expectedMatch: /1460/, + recipeConfig: [ + { + op: "Parse TCP", + args: ["Hex"], + } + ], + }, + { + name: "Parse TCP: Timestamps", + input: "9e90e11574d57b2c00000000a002ffffe5740000020405b40402080aa4e8c8f50000000001030308", + expectedMatch: /2766719221/, + recipeConfig: [ + { + op: "Parse TCP", + args: ["Hex"], + } + ], + } +]); diff --git a/tests/operations/tests/ParseUDP.mjs b/tests/operations/tests/ParseUDP.mjs index 2c519232..6d1b5518 100644 --- a/tests/operations/tests/ParseUDP.mjs +++ b/tests/operations/tests/ParseUDP.mjs @@ -2,7 +2,6 @@ * Parse UDP tests. * * @author h345983745 - * * @copyright Crown Copyright 2019 * @license Apache-2.0 */ @@ -12,15 +11,11 @@ TestRegister.addTests([ { name: "Parse UDP: No Data - JSON", input: "04 89 00 35 00 2c 01 01", - expectedOutput: "{\"Source port\":1161,\"Destination port\":53,\"Length\":44,\"Checksum\":\"0101\"}", + expectedOutput: "{\"Source port\":1161,\"Destination port\":53,\"Length\":44,\"Checksum\":\"0x0101\"}", recipeConfig: [ - { - op: "From Hex", - args: ["Auto"], - }, { op: "Parse UDP", - args: [], + args: ["Hex"], }, { op: "JSON Minify", @@ -30,15 +25,11 @@ TestRegister.addTests([ }, { name: "Parse UDP: With Data - JSON", input: "04 89 00 35 00 2c 01 01 02 02", - expectedOutput: "{\"Source port\":1161,\"Destination port\":53,\"Length\":44,\"Checksum\":\"0101\",\"Data\":\"0202\"}", + expectedOutput: "{\"Source port\":1161,\"Destination port\":53,\"Length\":44,\"Checksum\":\"0x0101\",\"Data\":\"0x0202\"}", recipeConfig: [ - { - op: "From Hex", - args: ["Auto"], - }, { op: "Parse UDP", - args: [], + args: ["Hex"], }, { op: "JSON Minify", @@ -51,13 +42,9 @@ TestRegister.addTests([ input: "04 89 00", expectedOutput: "Need 8 bytes for a UDP Header", recipeConfig: [ - { - op: "From Hex", - args: ["Auto"], - }, { op: "Parse UDP", - args: [], + args: ["Hex"], }, { op: "JSON Minify", From cf2b54e8c03788f3a1c15876048fd942168f7623 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Mon, 30 May 2022 18:14:41 +0100 Subject: [PATCH 32/99] Update CHANGELOG --- CHANGELOG.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a90b36de..7ad241ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ All major and minor version changes will be documented in this file. Details of ## Details +### [9.38.0] - 2022-05-30 +- Added 'Parse TCP' operation [@n1474335] | [a895d1d] + ### [9.37.0] - 2022-03-29 - 'SM4 Encrypt' and 'SM4 Decrypt' operations added [@swesven] | [#1189] - NoPadding options added for CBC and ECB modes in AES, DES and Triple DES Decrypt operations [@swesven] | [#1189] @@ -135,7 +138,7 @@ All major and minor version changes will be documented in this file. Details of
Click to expand v8 minor versions - + ### [8.38.0] - 2019-07-03 - 'Streebog' and 'GOST hash' operations added [@MShwed] [@n1474335] | [#530] @@ -288,6 +291,7 @@ All major and minor version changes will be documented in this file. Details of +[9.38.0]: https://github.com/gchq/CyberChef/releases/tag/v9.38.0 [9.37.0]: https://github.com/gchq/CyberChef/releases/tag/v9.37.0 [9.36.0]: https://github.com/gchq/CyberChef/releases/tag/v9.36.0 [9.35.0]: https://github.com/gchq/CyberChef/releases/tag/v9.35.0 @@ -416,6 +420,7 @@ All major and minor version changes will be documented in this file. Details of [289a417]: https://github.com/gchq/CyberChef/commit/289a417dfb5923de5e1694354ec42a08d9395bfe [e9ca4dc]: https://github.com/gchq/CyberChef/commit/e9ca4dc9caf98f33fd986431cd400c88082a42b8 [dd18e52]: https://github.com/gchq/CyberChef/commit/dd18e529939078b89867297b181a584e8b2cc7da +[a895d1d]: https://github.com/gchq/CyberChef/commit/a895d1d82a2f92d440a0c5eca2bc7c898107b737 [#95]: https://github.com/gchq/CyberChef/pull/299 [#173]: https://github.com/gchq/CyberChef/pull/173 @@ -502,4 +507,4 @@ All major and minor version changes will be documented in this file. Details of [#1242]: https://github.com/gchq/CyberChef/pull/1242 [#1244]: https://github.com/gchq/CyberChef/pull/1244 [#1313]: https://github.com/gchq/CyberChef/pull/1313 -[#1326]: https://github.com/gchq/CyberChef/pull/1326 \ No newline at end of file +[#1326]: https://github.com/gchq/CyberChef/pull/1326 From cc9d51b7be8437602f32769efec6fb8f9e3652d3 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Mon, 30 May 2022 18:14:46 +0100 Subject: [PATCH 33/99] 9.38.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 16c88c1b..12bf39a4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cyberchef", - "version": "9.37.3", + "version": "9.38.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "9.37.3", + "version": "9.38.0", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index d2d71272..eabfaed7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "9.37.3", + "version": "9.38.0", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", From ec577fc075380dfec5dfa33a82251e08528e599f Mon Sep 17 00:00:00 2001 From: n1474335 Date: Mon, 30 May 2022 19:25:41 +0100 Subject: [PATCH 34/99] Fixed CSS for maximising output pane --- src/web/html/index.html | 22 ++++++++++++---------- src/web/stylesheets/components/_pane.css | 15 +++++++++++---- src/web/stylesheets/layout/_controls.css | 4 ++++ src/web/stylesheets/layout/_io.css | 3 +-- src/web/waiters/OutputWaiter.mjs | 2 ++ 5 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/web/html/index.html b/src/web/html/index.html index 8a7adac7..d222fee1 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -176,7 +176,7 @@
Recipe - + @@ -190,7 +190,7 @@
    -
    +
    @@ -236,9 +239,7 @@ view_compact -
    -
    -
    +