From 758ff9b6041606454de3b4bc3238fb3e0bd39378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0lteri=C5=9F=20Ya=C4=9F=C4=B1ztegin=20Ero=C4=9Flu?= Date: Sat, 19 Sep 2020 15:24:15 +0300 Subject: [PATCH] feat(Modhex): Introduce basic Modhex conversion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: İlteriş Yağıztegin Eroğlu --- src/core/config/Categories.json | 2 + src/core/lib/Modhex.mjs | 65 +++++++++++++ src/core/operations/FromModhex.mjs | 123 +++++++++++++++++++++++++ src/core/operations/ToModhex.mjs | 110 ++++++++++++++++++++++ tests/operations/index.mjs | 1 + tests/operations/tests/Modhex.mjs | 143 +++++++++++++++++++++++++++++ 6 files changed, 444 insertions(+) create mode 100644 src/core/lib/Modhex.mjs create mode 100644 src/core/operations/FromModhex.mjs create mode 100644 src/core/operations/ToModhex.mjs create mode 100644 tests/operations/tests/Modhex.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 77e3d31941..def4933952 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -10,6 +10,8 @@ "From Hexdump", "To Hex", "From Hex", + "To Modhex", + "From Modhex", "To Charcode", "From Charcode", "To Decimal", diff --git a/src/core/lib/Modhex.mjs b/src/core/lib/Modhex.mjs new file mode 100644 index 0000000000..e1568e85dd --- /dev/null +++ b/src/core/lib/Modhex.mjs @@ -0,0 +1,65 @@ +/** + * Modhex helpers. + * + * @author linuxgemini [ilteris@asenkron.com.tr] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + + +/** + * Modhex to Hex conversion map. + */ +export const MODHEX_TO_HEX_CONVERSION_MAP = { + "c": "0", + "b": "1", + "d": "2", + "e": "3", + "f": "4", + "g": "5", + "h": "6", + "i": "7", + "j": "8", + "k": "9", + "l": "a", + "n": "b", + "r": "c", + "t": "d", + "u": "e", + "v": "f" +}; + + +/** + * Hex to Modhex conversion map. + */ +export const HEX_TO_MODHEX_CONVERSION_MAP = { + "0": "c", + "1": "b", + "2": "d", + "3": "e", + "4": "f", + "5": "g", + "6": "h", + "7": "i", + "8": "j", + "9": "k", + "a": "l", + "b": "n", + "c": "r", + "d": "t", + "e": "u", + "f": "v" +}; + + +/** + * To Modhex delimiters. + */ +export const TO_MODHEX_DELIM_OPTIONS = ["Space", "Percent", "Comma", "Semi-colon", "Colon", "Line feed", "CRLF", "None"]; + + +/** + * From Modhex delimiters. + */ +export const FROM_MODHEX_DELIM_OPTIONS = ["Auto"].concat(TO_MODHEX_DELIM_OPTIONS); diff --git a/src/core/operations/FromModhex.mjs b/src/core/operations/FromModhex.mjs new file mode 100644 index 0000000000..054c259e1f --- /dev/null +++ b/src/core/operations/FromModhex.mjs @@ -0,0 +1,123 @@ +/** + * @author linuxgemini [ilteris@asenkron.com.tr] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { FROM_MODHEX_DELIM_OPTIONS, MODHEX_TO_HEX_CONVERSION_MAP } from "../lib/Modhex.mjs"; +import { fromHex } from "../lib/Hex.mjs"; +import Utils from "../Utils.mjs"; + +/** + * From Modhex operation + */ +class FromModhex extends Operation { + + /** + * FromModhex constructor + */ + constructor() { + super(); + + this.name = "From Modhex"; + this.module = "Default"; + this.description = "Converts a modhex byte string back into its raw value."; + this.infoURL = "https://en.wikipedia.org/wiki/YubiKey#ModHex"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Delimiter", + type: "option", + value: FROM_MODHEX_DELIM_OPTIONS + } + ]; + this.checks = [ + { + pattern: "^(?:[cbdefghijklnrtuv]{2})+$", + flags: "i", + args: ["None"] + }, + { + pattern: "^[cbdefghijklnrtuv]{2}(?: [cbdefghijklnrtuv]{2})*$", + flags: "i", + args: ["Space"] + }, + { + pattern: "^[cbdefghijklnrtuv]{2}(?:,[cbdefghijklnrtuv]{2})*$", + flags: "i", + args: ["Comma"] + }, + { + pattern: "^[cbdefghijklnrtuv]{2}(?:;[cbdefghijklnrtuv]{2})*$", + flags: "i", + args: ["Semi-colon"] + }, + { + pattern: "^[cbdefghijklnrtuv]{2}(?::[cbdefghijklnrtuv]{2})*$", + flags: "i", + args: ["Colon"] + }, + { + pattern: "^[cbdefghijklnrtuv]{2}(?:\\n[cbdefghijklnrtuv]{2})*$", + flags: "i", + args: ["Line feed"] + }, + { + pattern: "^[cbdefghijklnrtuv]{2}(?:\\r\\n[cbdefghijklnrtuv]{2})*$", + flags: "i", + args: ["CRLF"] + } + ]; + } + + /** + * Convert a hex string into a byte array. + * + * @param {string} data + * @param {string} [delim] + * @param {number} [byteLen=2] + * @returns {byteArray} + * + * @example + * // returns [10,20,30] + * fromModhex("cl bf bu"); + * + * // returns [10,20,30] + * fromModhex("cl:bf:bu", "Colon"); + */ + fromModhex(data, delim="Auto", byteLen=2) { + if (!data || data.length === 0) return []; + + data = data.toLowerCase(); + + if (delim !== "None") { + const delimRegex = delim === "Auto" ? /[^cbdefghijklnrtuv]/gi : Utils.regexRep(delim); + data = data.replace(delimRegex, ""); + } + + data = data.replace(/\s/g, ""); + + let hexconv = ""; + for (const letter of data.split("")) { + hexconv += MODHEX_TO_HEX_CONVERSION_MAP[letter]; + } + + const output = fromHex(hexconv, "None", byteLen); + return output; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const delim = args[0] || "Auto"; + return this.fromModhex(input, delim, 2); + } + +} + +export default FromModhex; diff --git a/src/core/operations/ToModhex.mjs b/src/core/operations/ToModhex.mjs new file mode 100644 index 0000000000..2d07fa31e3 --- /dev/null +++ b/src/core/operations/ToModhex.mjs @@ -0,0 +1,110 @@ +/** + * @author linuxgemini [ilteris@asenkron.com.tr] + * @copyright Crown Copyright 2020 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { TO_MODHEX_DELIM_OPTIONS, HEX_TO_MODHEX_CONVERSION_MAP } from "../lib/Modhex.mjs"; +import { toHex } from "../lib/Hex.mjs"; +import Utils from "../Utils.mjs"; + +/** + * To Modhex operation + */ +class ToModhex extends Operation { + + /** + * ToModhex constructor + */ + constructor() { + super(); + + this.name = "To Modhex"; + this.module = "Default"; + this.description = "Converts the input string to modhex bytes separated by the specified delimiter."; + this.infoURL = "https://en.wikipedia.org/wiki/YubiKey#ModHex"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + name: "Delimiter", + type: "option", + value: TO_MODHEX_DELIM_OPTIONS + }, + { + name: "Bytes per line", + type: "number", + value: 0 + } + ]; + } + + /** + * Convert a byte array into a modhex string. + * + * @param {byteArray|Uint8Array|ArrayBuffer} data + * @param {string} [delim=" "] + * @param {number} [padding=2] + * @returns {string} + * + * @example + * // returns "cl bf bu" + * toModhex([10,20,30]); + * + * // returns "cl:bf:bu" + * toModhex([10,20,30], ":"); + */ + toModhex(data, delim=" ", padding=2, extraDelim="", lineSize=0) { + if (!data || data.length === 0) return ""; + if (data instanceof ArrayBuffer) data = new Uint8Array(data); + + const hexconv = toHex(data, "", padding, "", 0); + let modhexconv = ""; + let output = ""; + + for (const letter of hexconv.split("")) { + modhexconv += HEX_TO_MODHEX_CONVERSION_MAP[letter]; + } + + const groupedModhex = modhexconv.match(/.{1,2}/g); + + for (let i = 0; i < groupedModhex.length; i++) { + const group = groupedModhex[i]; + output += group + delim; + + if (extraDelim) { + output += extraDelim; + } + // Add LF after each lineSize amount of bytes but not at the end + if ((i !== groupedModhex.length - 1) && ((i + 1) % lineSize === 0)) { + output += "\n"; + } + } + + // Remove the extraDelim at the end (if there is one) + // and remove the delim at the end, but if it's prepended there's nothing to remove + const rTruncLen = extraDelim.length + delim.length; + if (rTruncLen) { + // If rTruncLen === 0 then output.slice(0,0) will be returned, which is nothing + return output.slice(0, -rTruncLen); + } else { + return output; + } + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const delim = Utils.charRep(args[0]); + const lineSize = args[1]; + + return this.toModhex(new Uint8Array(input), delim, 2, "", lineSize); + } + +} + +export default ToModhex; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 8d3cd623d8..4b8a44dd61 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -58,6 +58,7 @@ import "./tests/JWTSign.mjs"; import "./tests/JWTVerify.mjs"; import "./tests/MS.mjs"; import "./tests/Magic.mjs"; +import "./tests/Modhex.mjs"; import "./tests/MorseCode.mjs"; import "./tests/NetBIOS.mjs"; import "./tests/NormaliseUnicode.mjs"; diff --git a/tests/operations/tests/Modhex.mjs b/tests/operations/tests/Modhex.mjs new file mode 100644 index 0000000000..2d3041207a --- /dev/null +++ b/tests/operations/tests/Modhex.mjs @@ -0,0 +1,143 @@ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "ASCII to Modhex stream", + input: "aberystwyth", + expectedOutput: "hbhdhgidikieifiiikifhj", + recipeConfig: [ + { + "op": "To Modhex", + "args": [ + "None", + 0 + ] + }, + ] + }, + { + name: "ASCII to Modhex with colon deliminator", + input: "aberystwyth", + expectedOutput: "hb:hd:hg:id:ik:ie:if:ii:ik:if:hj", + recipeConfig: [ + { + "op": "To Modhex", + "args": [ + "Colon", + 0 + ] + } + ] + }, + { + name: "Modhex stream to UTF-8", + input: "uhkgkbuhkgkbugltlkugltkc", + expectedOutput: "救救孩子", + recipeConfig: [ + { + "op": "From Modhex", + "args": [ + "Auto" + ] + } + ] + + }, + { + name: "Mixed case Modhex stream to UTF-8", + input: "uhKGkbUHkgkBUGltlkugltkc", + expectedOutput: "救救孩子", + recipeConfig: [ + { + "op": "From Modhex", + "args": [ + "Auto" + ] + } + ] + + }, + { + name: "Mutiline Modhex with comma to ASCII (Auto Mode)", + input: "fk,dc,ie,hb,ii,dc,ht,ik,ie,hg,hr,hh,dc,ie,hk,\n\ +if,if,hk,hu,hi,dc,hk,hu,dc,if,hj,hg,dc,he,id,\n\ +hv,if,he,hj,dc,hv,hh,dc,if,hj,hg,dc,if,hj,hk,\n\ +ie,dc,hh,hk,hi,dc,if,id,hg,hg,dr,dc,ie,if,hb,\n\ +id,ih,hk,hu,hi,dc,if,hv,dc,hf,hg,hb,if,hj,dr,\n\ +dc,hl,ig,ie,if,dc,hd,hg,he,hb,ig,ie,hg,dc,fk,\n\ +dc,he,hv,ig,hr,hf,hu,di,if,dc,ht,hb,hn,hg,dc,\n\ +ig,ic,dc,ht,ik,dc,ht,hk,hu,hf,dc,ii,hj,hk,he,\n\ +hj,dc,hv,hh,dc,if,hj,hg,dc,hh,hk,hi,ie,dc,fk,\n\ +dc,ii,hv,ig,hr,hf,dc,he,hj,hv,hv,ie,hg,du", + expectedOutput: "I saw myself sitting in the crotch of the this fig tree, starving to death, just because I couldn't make up my mind which of the figs I would choose.", + recipeConfig: [ + { + "op": "From Modhex", + "args": [ + "Auto" + ] + } + ] + + }, + { + name: "Mutiline Modhex with percent to ASCII (Percent Mode)", + input: "fk%dc%ie%hb%ii%dc%ht%ik%ie%hg%hr%hh%dc%ie%hk%\n\ +if%if%hk%hu%hi%dc%hk%hu%dc%if%hj%hg%dc%he%id%\n\ +hv%if%he%hj%dc%hv%hh%dc%if%hj%hg%dc%if%hj%hk%\n\ +ie%dc%hh%hk%hi%dc%if%id%hg%hg%dr%dc%ie%if%hb%\n\ +id%ih%hk%hu%hi%dc%if%hv%dc%hf%hg%hb%if%hj%dr%\n\ +dc%hl%ig%ie%if%dc%hd%hg%he%hb%ig%ie%hg%dc%fk%\n\ +dc%he%hv%ig%hr%hf%hu%di%if%dc%ht%hb%hn%hg%dc%\n\ +ig%ic%dc%ht%ik%dc%ht%hk%hu%hf%dc%ii%hj%hk%he%\n\ +hj%dc%hv%hh%dc%if%hj%hg%dc%hh%hk%hi%ie%dc%fk%\n\ +dc%ii%hv%ig%hr%hf%dc%he%hj%hv%hv%ie%hg%du", + expectedOutput: "I saw myself sitting in the crotch of the this fig tree, starving to death, just because I couldn't make up my mind which of the figs I would choose.", + recipeConfig: [ + { + "op": "From Modhex", + "args": [ + "Percent" + ] + } + ] + + }, + { + name: "Mutiline Modhex with semicolon to ASCII (Semi-colon Mode)", + input: "fk;dc;ie;hb;ii;dc;ht;ik;ie;hg;hr;hh;dc;ie;hk;\n\ +if;if;hk;hu;hi;dc;hk;hu;dc;if;hj;hg;dc;he;id;\n\ +hv;if;he;hj;dc;hv;hh;dc;if;hj;hg;dc;if;hj;hk;\n\ +ie;dc;hh;hk;hi;dc;if;id;hg;hg;dr;dc;ie;if;hb;\n\ +id;ih;hk;hu;hi;dc;if;hv;dc;hf;hg;hb;if;hj;dr;\n\ +dc;hl;ig;ie;if;dc;hd;hg;he;hb;ig;ie;hg;dc;fk;\n\ +dc;he;hv;ig;hr;hf;hu;di;if;dc;ht;hb;hn;hg;dc;\n\ +ig;ic;dc;ht;ik;dc;ht;hk;hu;hf;dc;ii;hj;hk;he;\n\ +hj;dc;hv;hh;dc;if;hj;hg;dc;hh;hk;hi;ie;dc;fk;\n\ +dc;ii;hv;ig;hr;hf;dc;he;hj;hv;hv;ie;hg;du", + expectedOutput: "I saw myself sitting in the crotch of the this fig tree, starving to death, just because I couldn't make up my mind which of the figs I would choose.", + recipeConfig: [ + { + "op": "From Modhex", + "args": [ + "Semi-colon" + ] + } + ] + + }, + { + name: "ASCII to Modhex with comma and line breaks", + input: "aberystwyth", + expectedOutput: "hb,hd,hg,id,\nik,ie,if,ii,\nik,if,hj", + recipeConfig: [ + { + "op": "To Modhex", + "args": [ + "Comma", + 4 + ] + } + ] + }, +]);