From 126debf44e94e9fa35dc4cf72b7b48c4fc270612 Mon Sep 17 00:00:00 2001 From: samgbell <32361770+samgbell@users.noreply.github.com> Date: Thu, 13 Oct 2022 11:52:19 +0200 Subject: [PATCH 01/74] Adding Markdown format for To Table operation --- src/core/operations/ToTable.mjs | 55 +++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/src/core/operations/ToTable.mjs b/src/core/operations/ToTable.mjs index 91b07771..114d5e21 100644 --- a/src/core/operations/ToTable.mjs +++ b/src/core/operations/ToTable.mjs @@ -20,7 +20,7 @@ class ToTable extends Operation { this.name = "To Table"; this.module = "Default"; - this.description = "Data can be split on different characters and rendered as an HTML or ASCII table with an optional header row.

Supports the CSV (Comma Separated Values) file format by default. Change the cell delimiter argument to \\t to support TSV (Tab Separated Values) or | for PSV (Pipe Separated Values).

You can enter as many delimiters as you like. Each character will be treat as a separate possible delimiter."; + this.description = "Data can be split on different characters and rendered as an HTML, ASCII or Markdown table with an optional header row.

Supports the CSV (Comma Separated Values) file format by default. Change the cell delimiter argument to \\t to support TSV (Tab Separated Values) or | for PSV (Pipe Separated Values).

You can enter as many delimiters as you like. Each character will be treat as a separate possible delimiter."; this.infoURL = "https://wikipedia.org/wiki/Comma-separated_values"; this.inputType = "string"; this.outputType = "html"; @@ -43,7 +43,7 @@ class ToTable extends Operation { { "name": "Format", "type": "option", - "value": ["ASCII", "HTML"] + "value": ["ASCII", "HTML", "Markdown"] } ]; } @@ -66,6 +66,10 @@ class ToTable extends Operation { case "ASCII": return asciiOutput(tableData); case "HTML": + return htmlOutput(tableData); + case "Markdown": + return markdownOutput(tableData); + default: return htmlOutput(tableData); } @@ -183,6 +187,53 @@ class ToTable extends Operation { return output; } } + + function markdownOutput(tableData) { + const headerDivider = "-"; + const verticalBorder = "|"; + + let output = ""; + const longestCells = []; + + // Find longestCells value per column to pad cells equally. + tableData.forEach(function(row, index) { + row.forEach(function(cell, cellIndex) { + if (longestCells[cellIndex] === undefined || cell.length > longestCells[cellIndex]) { + longestCells[cellIndex] = cell.length; + } + }); + }); + + // Ignoring the checkbox, as current Mardown renderer in CF doesn't handle table without headers + const row = tableData.shift(); + output += outputRow(row, longestCells); + let rowOutput = verticalBorder; + row.forEach(function(cell, index) { + rowOutput += " " + headerDivider + " " + verticalBorder; + }); + output += rowOutput += "\n"; + + // Add the rest of the table rows. + tableData.forEach(function(row, index) { + output += outputRow(row, longestCells); + }); + + return output; + + /** + * Outputs a row of correctly padded cells. + */ + function outputRow(row, longestCells) { + let rowOutput = verticalBorder; + row.forEach(function(cell, index) { + rowOutput += " " + cell + " ".repeat(longestCells[index] - cell.length) + " " + verticalBorder; + }); + rowOutput += "\n"; + return rowOutput; + } + + } + } } From 8e5735430749f09ce93baa9b965da802beb9356e Mon Sep 17 00:00:00 2001 From: samgbell <32361770+samgbell@users.noreply.github.com> Date: Thu, 13 Oct 2022 12:03:22 +0200 Subject: [PATCH 02/74] Fixing some eslint and JSDoc issues --- src/core/operations/ToTable.mjs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/core/operations/ToTable.mjs b/src/core/operations/ToTable.mjs index 114d5e21..ff69023f 100644 --- a/src/core/operations/ToTable.mjs +++ b/src/core/operations/ToTable.mjs @@ -68,8 +68,7 @@ class ToTable extends Operation { case "HTML": return htmlOutput(tableData); case "Markdown": - return markdownOutput(tableData); - + return markdownOutput(tableData); default: return htmlOutput(tableData); } @@ -188,6 +187,12 @@ class ToTable extends Operation { } } + /** + * Outputs an array of data as a Markdown table. + * + * @param {string[][]} tableData + * @returns {string} + */ function markdownOutput(tableData) { const headerDivider = "-"; const verticalBorder = "|"; From fa30f597ad616fd072b3ace052b60978fa5477cd Mon Sep 17 00:00:00 2001 From: MikeCAT Date: Thu, 27 Oct 2022 20:02:49 +0900 Subject: [PATCH 03/74] GenerateQRCode.mjs: set default margin to 4 modules --- src/core/operations/GenerateQRCode.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/operations/GenerateQRCode.mjs b/src/core/operations/GenerateQRCode.mjs index 080a24da..d3e1ee3b 100644 --- a/src/core/operations/GenerateQRCode.mjs +++ b/src/core/operations/GenerateQRCode.mjs @@ -44,7 +44,7 @@ class GenerateQRCode extends Operation { { "name": "Margin (num modules)", "type": "number", - "value": 2, + "value": 4, "min": 0 }, { From d5ffbbb14cea7a3a97390333f217eda752c816a7 Mon Sep 17 00:00:00 2001 From: MikeCAT Date: Fri, 28 Oct 2022 21:33:56 +0900 Subject: [PATCH 04/74] ParseASN1HexString.mjs: fix the name of option to use --- src/core/operations/ParseASN1HexString.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/operations/ParseASN1HexString.mjs b/src/core/operations/ParseASN1HexString.mjs index a19ca70c..b3e0f652 100644 --- a/src/core/operations/ParseASN1HexString.mjs +++ b/src/core/operations/ParseASN1HexString.mjs @@ -46,7 +46,7 @@ class ParseASN1HexString extends Operation { run(input, args) { const [index, truncateLen] = args; return r.ASN1HEX.dump(input.replace(/\s/g, ""), { - "ommitLongOctet": truncateLen + "ommit_long_octet": truncateLen }, index); } From 3ac2ed20d285defedf4baab1ba21def6a8b10d1a Mon Sep 17 00:00:00 2001 From: MikeCAT Date: Sat, 29 Oct 2022 03:09:41 +0900 Subject: [PATCH 05/74] add operation "Rabbit Stream Cipher" --- src/core/config/Categories.json | 3 +- src/core/operations/RabbitStreamCipher.mjs | 247 ++++++++++++++++++ tests/operations/index.mjs | 1 + tests/operations/tests/RabbitStreamCipher.mjs | 177 +++++++++++++ 4 files changed, 427 insertions(+), 1 deletion(-) create mode 100644 src/core/operations/RabbitStreamCipher.mjs create mode 100644 tests/operations/tests/RabbitStreamCipher.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 43d5dc4e..430f6196 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -131,7 +131,8 @@ "Typex", "Lorenz", "Colossus", - "SIGABA" + "SIGABA", + "Rabbit Stream Cipher" ] }, { diff --git a/src/core/operations/RabbitStreamCipher.mjs b/src/core/operations/RabbitStreamCipher.mjs new file mode 100644 index 00000000..b106cb4d --- /dev/null +++ b/src/core/operations/RabbitStreamCipher.mjs @@ -0,0 +1,247 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast } from "../lib/Hex.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Rabbit Stream Cipher operation + */ +class RabbitStreamCipher extends Operation { + + /** + * RabbitStreamCipher constructor + */ + constructor() { + super(); + + this.name = "Rabbit Stream Cipher"; + this.module = "Ciphers"; + this.description = "Rabbit Stream Cipher, a stream cipher algorithm defined in RFC4503.

The cipher uses a 128-bit key and an optional 64-bit initialization vector (IV).

big-endian: based on RFC4503 and RFC3447
little-endian: compatible with Crypto++"; + this.infoURL = "https://wikipedia.org/wiki/Rabbit_(cipher)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Endianness", + "type": "option", + "value": ["Big", "Little"] + }, + { + "name": "Input", + "type": "option", + "value": ["Raw", "Hex"] + }, + { + "name": "Output", + "type": "option", + "value": ["Raw", "Hex"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string, args[0].option), + iv = Utils.convertToByteArray(args[1].string, args[1].option), + endianness = args[2], + inputType = args[3], + outputType = args[4]; + + const littleEndian = endianness === "Little"; + + if (key.length !== 16) { + throw new OperationError(`Invalid key length: ${key.length} bytes (expected: 16)`); + } + if (iv.length !== 0 && iv.length !== 8) { + throw new OperationError(`Invalid IV length: ${iv.length} bytes (expected: 0 or 8)`); + } + + // Inner State + const X = [], C = []; + let b = 0; + + // Counter System + const A = [ + 0x4d34d34d, 0xd34d34d3, 0x34d34d34, 0x4d34d34d, + 0xd34d34d3, 0x34d34d34, 0x4d34d34d, 0xd34d34d3 + ]; + const counterUpdate = function() { + for (let j = 0; j < 8; j++) { + const temp = C[j] + A[j] + b; + b = (temp / ((1 << 30) * 4)) >>> 0; + C[j] = temp >>> 0; + } + }; + + // Next-State Function + const g = function(u, v) { + const uv = (u + v) >>> 0; + const upper = uv >>> 16, lower = uv & 0xffff; + const upperUpper = upper * upper; + const upperLower2 = 2 * upper * lower; + const lowerLower = lower * lower; + const mswTemp = upperUpper + ((upperLower2 / (1 << 16)) >>> 0); + const lswTemp = lowerLower + (upperLower2 & 0xffff) * (1 << 16); + const msw = mswTemp + ((lswTemp / ((1 << 30) * 4)) >>> 0); + const lsw = lswTemp >>> 0; + return (lsw ^ msw) >>> 0; + }; + const leftRotate = function(value, width) { + return (value << width) | (value >>> (32 - width)); + }; + const nextStateHelper1 = function(v0, v1, v2) { + return (v0 + leftRotate(v1, 16) + leftRotate(v2, 16)) >>> 0; + }; + const nextStateHelper2 = function(v0, v1, v2) { + return (v0 + leftRotate(v1, 8) + v2) >>> 0; + }; + const G = new Array(8); + const nextState = function() { + for (let j = 0; j < 8; j++) { + G[j] = g(X[j], C[j]); + } + X[0] = nextStateHelper1(G[0], G[7], G[6]); + X[1] = nextStateHelper2(G[1], G[0], G[7]); + X[2] = nextStateHelper1(G[2], G[1], G[0]); + X[3] = nextStateHelper2(G[3], G[2], G[1]); + X[4] = nextStateHelper1(G[4], G[3], G[2]); + X[5] = nextStateHelper2(G[5], G[4], G[3]); + X[6] = nextStateHelper1(G[6], G[5], G[4]); + X[7] = nextStateHelper2(G[7], G[6], G[5]); + }; + + // Key Setup Scheme + const K = []; + if (littleEndian) { + for (let i = 0; i < 8; i++) { + K.push((key[1 + 2 * i] << 8) | key[2 * i]); + } + } else { + for (let i = 0; i < 8; i++) { + K.push((key[14 - 2 * i] << 8) | key[15 - 2 * i]); + } + } + for (let j = 0; j < 8; j++) { + if (j % 2 === 0) { + X.push(((K[(j + 1) % 8] << 16) | K[j]) >>> 0); + C.push(((K[(j + 4) % 8] << 16) | K[(j + 5) % 8]) >>> 0); + } else { + X.push(((K[(j + 5) % 8] << 16) | K[(j + 4) % 8]) >>> 0); + C.push(((K[j] << 16) | K[(j + 1) % 8]) >>> 0); + } + } + for (let i = 0; i < 4; i++) { + counterUpdate(); + nextState(); + } + for (let j = 0; j < 8; j++) { + C[j] = (C[j] ^ X[(j + 4) % 8]) >>> 0; + } + + // IV Setup Scheme + if (iv.length === 8) { + const getIVValue = function(a, b, c, d) { + if (littleEndian) { + return ((iv[a] << 24) | (iv[b] << 16) | + (iv[c] << 8) | iv[d]) >>> 0; + } else { + return ((iv[7 - a] << 24) | (iv[7 - b] << 16) | + (iv[7 - c] << 8) | iv[7 - d]) >>> 0; + } + }; + C[0] = (C[0] ^ getIVValue(3, 2, 1, 0)) >>> 0; + C[1] = (C[1] ^ getIVValue(7, 6, 3, 2)) >>> 0; + C[2] = (C[2] ^ getIVValue(7, 6, 5, 4)) >>> 0; + C[3] = (C[3] ^ getIVValue(5, 4, 1, 0)) >>> 0; + C[4] = (C[4] ^ getIVValue(3, 2, 1, 0)) >>> 0; + C[5] = (C[5] ^ getIVValue(7, 6, 3, 2)) >>> 0; + C[6] = (C[6] ^ getIVValue(7, 6, 5, 4)) >>> 0; + C[7] = (C[7] ^ getIVValue(5, 4, 1, 0)) >>> 0; + for (let i = 0; i < 4; i++) { + counterUpdate(); + nextState(); + } + } + + // Extraction Scheme + const S = new Array(16); + const extract = function() { + let pos = 0; + const addPart = function(value) { + S[pos++] = value >>> 8; + S[pos++] = value & 0xff; + }; + counterUpdate(); + nextState(); + addPart((X[6] >>> 16) ^ (X[1] & 0xffff)); + addPart((X[6] & 0xffff) ^ (X[3] >>> 16)); + addPart((X[4] >>> 16) ^ (X[7] & 0xffff)); + addPart((X[4] & 0xffff) ^ (X[1] >>> 16)); + addPart((X[2] >>> 16) ^ (X[5] & 0xffff)); + addPart((X[2] & 0xffff) ^ (X[7] >>> 16)); + addPart((X[0] >>> 16) ^ (X[3] & 0xffff)); + addPart((X[0] & 0xffff) ^ (X[5] >>> 16)); + if (littleEndian) { + for (let i = 0, j = S.length - 1; i < j;) { + const temp = S[i]; + S[i] = S[j]; + S[j] = temp; + i++; + j--; + } + } + }; + + const data = Utils.convertToByteString(input, inputType); + const result = new Uint8Array(data.length); + for (let i = 0; i <= data.length - 16; i += 16) { + extract(); + for (let j = 0; j < 16; j++) { + result[i + j] = data.charCodeAt(i + j) ^ S[j]; + } + } + if (data.length % 16 !== 0) { + const offset = data.length - data.length % 16; + const length = data.length - offset; + extract(); + if (littleEndian) { + for (let j = 0; j < length; j++) { + result[offset + j] = data.charCodeAt(offset + j) ^ S[j]; + } + } else { + for (let j = 0; j < length; j++) { + result[offset + j] = data.charCodeAt(offset + j) ^ S[16 - length + j]; + } + } + } + if (outputType === "Hex") { + return toHexFast(result); + } + return Utils.byteArrayToChars(result); + } + +} + +export default RabbitStreamCipher; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 19e70970..2d303dcb 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -124,6 +124,7 @@ import "./tests/UnescapeString.mjs"; import "./tests/LS47.mjs"; import "./tests/LZString.mjs"; import "./tests/NTLM.mjs"; +import "./tests/RabbitStreamCipher.mjs"; // Cannot test operations that use the File type yet // import "./tests/SplitColourChannels.mjs"; diff --git a/tests/operations/tests/RabbitStreamCipher.mjs b/tests/operations/tests/RabbitStreamCipher.mjs new file mode 100644 index 00000000..fd22ffbd --- /dev/null +++ b/tests/operations/tests/RabbitStreamCipher.mjs @@ -0,0 +1,177 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Rabbit Stream Cipher: RFC Test vector, without IV 1", + input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedOutput: "b15754f036a5d6ecf56b45261c4af70288e8d815c59c0c397b696c4789c68aa7f416a1c3700cd451da68d1881673d696", + recipeConfig: [ + { + "op": "Rabbit Stream Cipher", + "args": [ + {"option": "Hex", "string": "00000000000000000000000000000000"}, + {"option": "Hex", "string": ""}, + "Big", "Hex", "Hex" + ] + } + ] + }, + { + name: "Rabbit Stream Cipher: RFC Test vector, without IV 2", + input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedOutput: "3d2df3c83ef627a1e97fc38487e2519cf576cd61f4405b8896bf53aa8554fc19e5547473fbdb43508ae53b20204d4c5e", + recipeConfig: [ + { + "op": "Rabbit Stream Cipher", + "args": [ + {"option": "Hex", "string": "912813292e3d36fe3bfc62f1dc51c3ac"}, + {"option": "Hex", "string": ""}, + "Big", "Hex", "Hex" + ] + } + ] + }, + { + name: "Rabbit Stream Cipher: RFC Test vector, without IV 3", + input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedOutput: "0cb10dcda041cdac32eb5cfd02d0609b95fc9fca0f17015a7b7092114cff3ead9649e5de8bfc7f3f924147ad3a947428", + recipeConfig: [ + { + "op": "Rabbit Stream Cipher", + "args": [ + {"option": "Hex", "string": "8395741587e0c733e9e9ab01c09b0043"}, + {"option": "Hex", "string": ""}, + "Big", "Hex", "Hex" + ] + } + ] + }, + { + name: "Rabbit Stream Cipher: RFC Test vector, with IV 1", + input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedOutput: "c6a7275ef85495d87ccd5d376705b7ed5f29a6ac04f5efd47b8f293270dc4a8d2ade822b29de6c1ee52bdb8a47bf8f66", + recipeConfig: [ + { + "op": "Rabbit Stream Cipher", + "args": [ + {"option": "Hex", "string": "00000000000000000000000000000000"}, + {"option": "Hex", "string": "0000000000000000"}, + "Big", "Hex", "Hex" + ] + } + ] + }, + { + name: "Rabbit Stream Cipher: RFC Test vector, with IV 2", + input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedOutput: "1fcd4eb9580012e2e0dccc9222017d6da75f4e10d12125017b2499ffed936f2eebc112c393e738392356bdd012029ba7", + recipeConfig: [ + { + "op": "Rabbit Stream Cipher", + "args": [ + {"option": "Hex", "string": "00000000000000000000000000000000"}, + {"option": "Hex", "string": "c373f575c1267e59"}, + "Big", "Hex", "Hex" + ] + } + ] + }, + { + name: "Rabbit Stream Cipher: RFC Test vector, with IV 3", + input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedOutput: "445ad8c805858dbf70b6af23a151104d96c8f27947f42c5baeae67c6acc35b039fcbfc895fa71c17313df034f01551cb", + recipeConfig: [ + { + "op": "Rabbit Stream Cipher", + "args": [ + {"option": "Hex", "string": "00000000000000000000000000000000"}, + {"option": "Hex", "string": "a6eb561ad2f41727"}, + "Big", "Hex", "Hex" + ] + } + ] + }, + { + name: "Rabbit Stream Cipher: generated stream should be XORed with the input", + input: "cedda96c054e3ddd93da7ed05e2a4b7bdb0c00fe214f03502e2708b2c2bfc77aa2311b0b9af8aa78d119f92b26db0a6b", + expectedOutput: "7f8afd9c33ebeb3166b13bf64260bc7953e4d8ebe4d30f69554e64f54b794ddd5627bac8eaf47e290b7128a330a8dcfd", + recipeConfig: [ + { + "op": "Rabbit Stream Cipher", + "args": [ + {"option": "Hex", "string": "00000000000000000000000000000000"}, + {"option": "Hex", "string": ""}, + "Big", "Hex", "Hex" + ] + } + ] + }, + { + name: "Rabbit Stream Cipher: least significant bits should be used for the last block", + input: "0000000000000000", + expectedOutput: "f56b45261c4af702", + recipeConfig: [ + { + "op": "Rabbit Stream Cipher", + "args": [ + {"option": "Hex", "string": "00000000000000000000000000000000"}, + {"option": "Hex", "string": ""}, + "Big", "Hex", "Hex" + ] + } + ] + }, + { + name: "Rabbit Stream Cipher: invalid key length", + input: "", + expectedOutput: "Invalid key length: 8 bytes (expected: 16)", + recipeConfig: [ + { + "op": "Rabbit Stream Cipher", + "args": [ + {"option": "Hex", "string": "0000000000000000"}, + {"option": "Hex", "string": ""}, + "Big", "Hex", "Hex" + ] + } + ] + }, + { + name: "Rabbit Stream Cipher: invalid IV length", + input: "", + expectedOutput: "Invalid IV length: 4 bytes (expected: 0 or 8)", + recipeConfig: [ + { + "op": "Rabbit Stream Cipher", + "args": [ + {"option": "Hex", "string": "00000000000000000000000000000000"}, + {"option": "Hex", "string": "00000000"}, + "Big", "Hex", "Hex" + ] + } + ] + }, + { + // this testcase is taken from the first example on Crypto++ Wiki + // https://www.cryptopp.com/wiki/Rabbit + name: "Rabbit Stream Cipher: little-endian mode (Crypto++ compatible)", + input: "Rabbit stream cipher test", + expectedOutput: "1ae2d4edcf9b6063b00fd6fda0b223aded157e77031cf0440b", + recipeConfig: [ + { + "op": "Rabbit Stream Cipher", + "args": [ + {"option": "Hex", "string": "23c2731e8b5469fd8dabb5bc592a0f3a"}, + {"option": "Hex", "string": "712906405ef03201"}, + "Little", "Raw", "Hex" + ] + } + ] + }, +]); From d23b88e2b8faf8f909dabb72c3a5e2e75b032169 Mon Sep 17 00:00:00 2001 From: MikeCAT Date: Sat, 29 Oct 2022 03:33:30 +0900 Subject: [PATCH 06/74] use typed arrays for status of Rabbit instead of normal arrays --- src/core/operations/RabbitStreamCipher.mjs | 54 +++++++++++----------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/core/operations/RabbitStreamCipher.mjs b/src/core/operations/RabbitStreamCipher.mjs index b106cb4d..7d030a6f 100644 --- a/src/core/operations/RabbitStreamCipher.mjs +++ b/src/core/operations/RabbitStreamCipher.mjs @@ -79,7 +79,7 @@ class RabbitStreamCipher extends Operation { } // Inner State - const X = [], C = []; + const X = new Uint32Array(8), C = new Uint32Array(8); let b = 0; // Counter System @@ -91,7 +91,7 @@ class RabbitStreamCipher extends Operation { for (let j = 0; j < 8; j++) { const temp = C[j] + A[j] + b; b = (temp / ((1 << 30) * 4)) >>> 0; - C[j] = temp >>> 0; + C[j] = temp; } }; @@ -106,18 +106,18 @@ class RabbitStreamCipher extends Operation { const lswTemp = lowerLower + (upperLower2 & 0xffff) * (1 << 16); const msw = mswTemp + ((lswTemp / ((1 << 30) * 4)) >>> 0); const lsw = lswTemp >>> 0; - return (lsw ^ msw) >>> 0; + return lsw ^ msw; }; const leftRotate = function(value, width) { return (value << width) | (value >>> (32 - width)); }; const nextStateHelper1 = function(v0, v1, v2) { - return (v0 + leftRotate(v1, 16) + leftRotate(v2, 16)) >>> 0; + return v0 + leftRotate(v1, 16) + leftRotate(v2, 16); }; const nextStateHelper2 = function(v0, v1, v2) { - return (v0 + leftRotate(v1, 8) + v2) >>> 0; + return v0 + leftRotate(v1, 8) + v2; }; - const G = new Array(8); + const G = new Uint32Array(8); const nextState = function() { for (let j = 0; j < 8; j++) { G[j] = g(X[j], C[j]); @@ -133,23 +133,23 @@ class RabbitStreamCipher extends Operation { }; // Key Setup Scheme - const K = []; + const K = new Uint16Array(8); if (littleEndian) { for (let i = 0; i < 8; i++) { - K.push((key[1 + 2 * i] << 8) | key[2 * i]); + K[i] = (key[1 + 2 * i] << 8) | key[2 * i]; } } else { for (let i = 0; i < 8; i++) { - K.push((key[14 - 2 * i] << 8) | key[15 - 2 * i]); + K[i] = (key[14 - 2 * i] << 8) | key[15 - 2 * i]; } } for (let j = 0; j < 8; j++) { if (j % 2 === 0) { - X.push(((K[(j + 1) % 8] << 16) | K[j]) >>> 0); - C.push(((K[(j + 4) % 8] << 16) | K[(j + 5) % 8]) >>> 0); + X[j] = (K[(j + 1) % 8] << 16) | K[j]; + C[j] = (K[(j + 4) % 8] << 16) | K[(j + 5) % 8]; } else { - X.push(((K[(j + 5) % 8] << 16) | K[(j + 4) % 8]) >>> 0); - C.push(((K[j] << 16) | K[(j + 1) % 8]) >>> 0); + X[j] = (K[(j + 5) % 8] << 16) | K[(j + 4) % 8]; + C[j] = (K[j] << 16) | K[(j + 1) % 8]; } } for (let i = 0; i < 4; i++) { @@ -157,28 +157,28 @@ class RabbitStreamCipher extends Operation { nextState(); } for (let j = 0; j < 8; j++) { - C[j] = (C[j] ^ X[(j + 4) % 8]) >>> 0; + C[j] = C[j] ^ X[(j + 4) % 8]; } // IV Setup Scheme if (iv.length === 8) { const getIVValue = function(a, b, c, d) { if (littleEndian) { - return ((iv[a] << 24) | (iv[b] << 16) | - (iv[c] << 8) | iv[d]) >>> 0; + return (iv[a] << 24) | (iv[b] << 16) | + (iv[c] << 8) | iv[d]; } else { - return ((iv[7 - a] << 24) | (iv[7 - b] << 16) | - (iv[7 - c] << 8) | iv[7 - d]) >>> 0; + return (iv[7 - a] << 24) | (iv[7 - b] << 16) | + (iv[7 - c] << 8) | iv[7 - d]; } }; - C[0] = (C[0] ^ getIVValue(3, 2, 1, 0)) >>> 0; - C[1] = (C[1] ^ getIVValue(7, 6, 3, 2)) >>> 0; - C[2] = (C[2] ^ getIVValue(7, 6, 5, 4)) >>> 0; - C[3] = (C[3] ^ getIVValue(5, 4, 1, 0)) >>> 0; - C[4] = (C[4] ^ getIVValue(3, 2, 1, 0)) >>> 0; - C[5] = (C[5] ^ getIVValue(7, 6, 3, 2)) >>> 0; - C[6] = (C[6] ^ getIVValue(7, 6, 5, 4)) >>> 0; - C[7] = (C[7] ^ getIVValue(5, 4, 1, 0)) >>> 0; + C[0] = C[0] ^ getIVValue(3, 2, 1, 0); + C[1] = C[1] ^ getIVValue(7, 6, 3, 2); + C[2] = C[2] ^ getIVValue(7, 6, 5, 4); + C[3] = C[3] ^ getIVValue(5, 4, 1, 0); + C[4] = C[4] ^ getIVValue(3, 2, 1, 0); + C[5] = C[5] ^ getIVValue(7, 6, 3, 2); + C[6] = C[6] ^ getIVValue(7, 6, 5, 4); + C[7] = C[7] ^ getIVValue(5, 4, 1, 0); for (let i = 0; i < 4; i++) { counterUpdate(); nextState(); @@ -186,7 +186,7 @@ class RabbitStreamCipher extends Operation { } // Extraction Scheme - const S = new Array(16); + const S = new Uint8Array(16); const extract = function() { let pos = 0; const addPart = function(value) { From 5a507aa1ba80c10576af4583c22489377d046451 Mon Sep 17 00:00:00 2001 From: MikeCAT Date: Sun, 30 Oct 2022 08:25:31 +0900 Subject: [PATCH 07/74] have "Parse X.509 certificate" emit user-friendly message on certificate load error --- src/core/operations/ParseX509Certificate.mjs | 38 +++++++++++--------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/src/core/operations/ParseX509Certificate.mjs b/src/core/operations/ParseX509Certificate.mjs index aeb5f677..9a0eb272 100644 --- a/src/core/operations/ParseX509Certificate.mjs +++ b/src/core/operations/ParseX509Certificate.mjs @@ -57,23 +57,29 @@ class ParseX509Certificate extends Operation { const cert = new r.X509(), inputFormat = args[0]; - switch (inputFormat) { - case "DER Hex": - input = input.replace(/\s/g, ""); - cert.readCertHex(input); - break; - case "PEM": - cert.readCertPEM(input); - break; - case "Base64": - cert.readCertHex(toHex(fromBase64(input, null, "byteArray"), "")); - break; - case "Raw": - cert.readCertHex(toHex(Utils.strToByteArray(input), "")); - break; - default: - throw "Undefined input format"; + let undefinedInputFormat = false; + try { + switch (inputFormat) { + case "DER Hex": + input = input.replace(/\s/g, ""); + cert.readCertHex(input); + break; + case "PEM": + cert.readCertPEM(input); + break; + case "Base64": + cert.readCertHex(toHex(fromBase64(input, null, "byteArray"), "")); + break; + case "Raw": + cert.readCertHex(toHex(Utils.strToByteArray(input), "")); + break; + default: + undefinedInputFormat = true; + } + } catch (e) { + throw "Certificate load error (non-certificate input?)"; } + if (undefinedInputFormat) throw "Undefined input format"; const sn = cert.getSerialNumberHex(), issuer = cert.getIssuer(), From cb023089bbadd609ae9daefd426bdb86ea735887 Mon Sep 17 00:00:00 2001 From: Didier Stevens Date: Sun, 30 Oct 2022 15:33:11 +0100 Subject: [PATCH 08/74] Operation Sort: added value Length to option Order --- src/core/lib/Sort.mjs | 12 ++++++++++++ src/core/operations/Sort.mjs | 6 ++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/core/lib/Sort.mjs b/src/core/lib/Sort.mjs index 46bbebd9..adb30d71 100644 --- a/src/core/lib/Sort.mjs +++ b/src/core/lib/Sort.mjs @@ -103,3 +103,15 @@ export function hexadecimalSort(a, b) { return a.localeCompare(b); } + +/** + * Comparison operation for sorting of lines by length + * + * @param {string} a + * @param {string} b + * @returns {number} + */ +export function lengthSort(a, b) { + return a.length - b.length; +} + diff --git a/src/core/operations/Sort.mjs b/src/core/operations/Sort.mjs index 19e4cbb2..7a4714be 100644 --- a/src/core/operations/Sort.mjs +++ b/src/core/operations/Sort.mjs @@ -7,7 +7,7 @@ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs"; -import {caseInsensitiveSort, ipSort, numericSort, hexadecimalSort} from "../lib/Sort.mjs"; +import {caseInsensitiveSort, ipSort, numericSort, hexadecimalSort, lengthSort} from "../lib/Sort.mjs"; /** * Sort operation @@ -39,7 +39,7 @@ class Sort extends Operation { { "name": "Order", "type": "option", - "value": ["Alphabetical (case sensitive)", "Alphabetical (case insensitive)", "IP address", "Numeric", "Numeric (hexadecimal)"] + "value": ["Alphabetical (case sensitive)", "Alphabetical (case insensitive)", "IP address", "Numeric", "Numeric (hexadecimal)", "Length"] } ]; } @@ -65,6 +65,8 @@ class Sort extends Operation { sorted = sorted.sort(numericSort); } else if (order === "Numeric (hexadecimal)") { sorted = sorted.sort(hexadecimalSort); + } else if (order === "Length") { + sorted = sorted.sort(lengthSort); } if (sortReverse) sorted.reverse(); From c6b79cd1c62a3b6936323dce5acdc2fe7adb135b Mon Sep 17 00:00:00 2001 From: MikeCAT Date: Tue, 1 Nov 2022 00:35:27 +0900 Subject: [PATCH 09/74] add new operations: AES Key Wrap/Unwrap --- src/core/config/Categories.json | 4 +- src/core/operations/AESKeyUnwrap.mjs | 128 ++++++++++ src/core/operations/AESKeyWrap.mjs | 115 +++++++++ tests/operations/index.mjs | 1 + tests/operations/tests/AESKeyWrap.mjs | 324 ++++++++++++++++++++++++++ 5 files changed, 571 insertions(+), 1 deletion(-) create mode 100644 src/core/operations/AESKeyUnwrap.mjs create mode 100644 src/core/operations/AESKeyWrap.mjs create mode 100644 tests/operations/tests/AESKeyWrap.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 43d5dc4e..0567edd9 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -131,7 +131,9 @@ "Typex", "Lorenz", "Colossus", - "SIGABA" + "SIGABA", + "AES Key Wrap", + "AES Key Unwrap" ] }, { diff --git a/src/core/operations/AESKeyUnwrap.mjs b/src/core/operations/AESKeyUnwrap.mjs new file mode 100644 index 00000000..1558847a --- /dev/null +++ b/src/core/operations/AESKeyUnwrap.mjs @@ -0,0 +1,128 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast } from "../lib/Hex.mjs"; +import forge from "node-forge"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * AES Key Unwrap operation + */ +class AESKeyUnwrap extends Operation { + + /** + * AESKeyUnwrap constructor + */ + constructor() { + super(); + + this.name = "AES Key Unwrap"; + this.module = "Ciphers"; + this.description = "Decryptor for a key wrapping algorithm defined in RFC3394, which is used to protect keys in untrusted storage or communications, using AES.

This algorithm uses an AES key (KEK: key-encryption key) and a 64-bit IV to decrypt 64-bit blocks."; + this.infoURL = "https://wikipedia.org/wiki/Key_wrap"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key (KEK)", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "a6a6a6a6a6a6a6a6", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Input", + "type": "option", + "value": ["Hex", "Raw"] + }, + { + "name": "Output", + "type": "option", + "value": ["Hex", "Raw"] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const kek = Utils.convertToByteString(args[0].string, args[0].option), + iv = Utils.convertToByteString(args[1].string, args[1].option), + inputType = args[2], + outputType = args[3]; + + if (kek.length !== 16 && kek.length !== 24 && kek.length !== 32) { + throw new OperationError("KEK must be either 16, 24, or 32 bytes (currently " + kek.length + " bytes)"); + } + if (iv.length !== 8) { + throw new OperationError("IV must be 8 bytes (currently " + iv.length + " bytes)"); + } + const inputData = Utils.convertToByteString(input, inputType); + if (inputData.length % 8 !== 0 || inputData.length < 24) { + throw new OperationError("input must be 8n (n>=3) bytes (currently " + inputData.length + " bytes)"); + } + + const cipher = forge.cipher.createCipher("AES-ECB", kek); + cipher.start(); + cipher.update(forge.util.createBuffer("")); + cipher.finish(); + const paddingBlock = cipher.output.getBytes(); + + const decipher = forge.cipher.createDecipher("AES-ECB", kek); + + let A = inputData.substring(0, 8); + const R = []; + for (let i = 8; i < inputData.length; i += 8) { + R.push(inputData.substring(i, i + 8)); + } + let cntLower = R.length >>> 0; + let cntUpper = (R.length / ((1 << 30) * 4)) >>> 0; + cntUpper = cntUpper * 6 + ((cntLower * 6 / ((1 << 30) * 4)) >>> 0); + cntLower = cntLower * 6 >>> 0; + for (let j = 5; j >= 0; j--) { + for (let i = R.length - 1; i >= 0; i--) { + const aBuffer = Utils.strToArrayBuffer(A); + const aView = new DataView(aBuffer); + aView.setUint32(0, aView.getUint32(0) ^ cntUpper); + aView.setUint32(4, aView.getUint32(4) ^ cntLower); + A = Utils.arrayBufferToStr(aBuffer, false); + decipher.start(); + decipher.update(forge.util.createBuffer(A + R[i] + paddingBlock)); + decipher.finish(); + const B = decipher.output.getBytes(); + A = B.substring(0, 8); + R[i] = B.substring(8, 16); + cntLower--; + if (cntLower < 0) { + cntUpper--; + cntLower = 0xffffffff; + } + } + } + if (A !== iv) { + throw new OperationError("IV mismatch"); + } + const P = R.join(""); + + if (outputType === "Hex") { + return toHexFast(Utils.strToArrayBuffer(P)); + } + return P; + } + +} + +export default AESKeyUnwrap; diff --git a/src/core/operations/AESKeyWrap.mjs b/src/core/operations/AESKeyWrap.mjs new file mode 100644 index 00000000..38867156 --- /dev/null +++ b/src/core/operations/AESKeyWrap.mjs @@ -0,0 +1,115 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast } from "../lib/Hex.mjs"; +import forge from "node-forge"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * AES Key Wrap operation + */ +class AESKeyWrap extends Operation { + + /** + * AESKeyWrap constructor + */ + constructor() { + super(); + + this.name = "AES Key Wrap"; + this.module = "Ciphers"; + this.description = "A key wrapping algorithm defined in RFC3394, which is used to protect keys in untrusted storage or communications, using AES.

This algorithm uses an AES key (KEK: key-encryption key) and a 64-bit IV to encrypt 64-bit blocks."; + this.infoURL = "https://wikipedia.org/wiki/Key_wrap"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key (KEK)", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "a6a6a6a6a6a6a6a6", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Input", + "type": "option", + "value": ["Hex", "Raw"] + }, + { + "name": "Output", + "type": "option", + "value": ["Hex", "Raw"] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const kek = Utils.convertToByteString(args[0].string, args[0].option), + iv = Utils.convertToByteString(args[1].string, args[1].option), + inputType = args[2], + outputType = args[3]; + + if (kek.length !== 16 && kek.length !== 24 && kek.length !== 32) { + throw new OperationError("KEK must be either 16, 24, or 32 bytes (currently " + kek.length + " bytes)"); + } + if (iv.length !== 8) { + throw new OperationError("IV must be 8 bytes (currently " + iv.length + " bytes)"); + } + const inputData = Utils.convertToByteString(input, inputType); + if (inputData.length % 8 !== 0 || inputData.length < 16) { + throw new OperationError("input must be 8n (n>=2) bytes (currently " + inputData.length + " bytes)"); + } + + const cipher = forge.cipher.createCipher("AES-ECB", kek); + + let A = iv; + const R = []; + for (let i = 0; i < inputData.length; i += 8) { + R.push(inputData.substring(i, i + 8)); + } + let cntLower = 1, cntUpper = 0; + for (let j = 0; j < 6; j++) { + for (let i = 0; i < R.length; i++) { + cipher.start(); + cipher.update(forge.util.createBuffer(A + R[i])); + cipher.finish(); + const B = cipher.output.getBytes(); + const msbBuffer = Utils.strToArrayBuffer(B.substring(0, 8)); + const msbView = new DataView(msbBuffer); + msbView.setUint32(0, msbView.getUint32(0) ^ cntUpper); + msbView.setUint32(4, msbView.getUint32(4) ^ cntLower); + A = Utils.arrayBufferToStr(msbBuffer, false); + R[i] = B.substring(8, 16); + cntLower++; + if (cntLower > 0xffffffff) { + cntUpper++; + cntLower = 0; + } + } + } + const C = A + R.join(""); + + if (outputType === "Hex") { + return toHexFast(Utils.strToArrayBuffer(C)); + } + return C; + } + +} + +export default AESKeyWrap; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 19e70970..f6eacddd 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -124,6 +124,7 @@ import "./tests/UnescapeString.mjs"; import "./tests/LS47.mjs"; import "./tests/LZString.mjs"; import "./tests/NTLM.mjs"; +import "./tests/AESKeyWrap.mjs"; // Cannot test operations that use the File type yet // import "./tests/SplitColourChannels.mjs"; diff --git a/tests/operations/tests/AESKeyWrap.mjs b/tests/operations/tests/AESKeyWrap.mjs new file mode 100644 index 00000000..bca36a40 --- /dev/null +++ b/tests/operations/tests/AESKeyWrap.mjs @@ -0,0 +1,324 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + "name": "AES Key Wrap: RFC Test Vector, 128-bit data, 128-bit KEK", + "input": "00112233445566778899aabbccddeeff", + "expectedOutput": "1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5", + "recipeConfig": [ + { + "op": "AES Key Wrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Wrap: RFC Test Vector, 128-bit data, 192-bit KEK", + "input": "00112233445566778899aabbccddeeff", + "expectedOutput": "96778b25ae6ca435f92b5b97c050aed2468ab8a17ad84e5d", + "recipeConfig": [ + { + "op": "AES Key Wrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f1011121314151617"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Wrap: RFC Test Vector, 128-bit data, 256-bit KEK", + "input": "00112233445566778899aabbccddeeff", + "expectedOutput": "64e8c3f9ce0f5ba263e9777905818a2a93c8191e7d6e8ae7", + "recipeConfig": [ + { + "op": "AES Key Wrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Wrap: RFC Test Vector, 192-bit data, 192-bit KEK", + "input": "00112233445566778899aabbccddeeff0001020304050607", + "expectedOutput": "031d33264e15d33268f24ec260743edce1c6c7ddee725a936ba814915c6762d2", + "recipeConfig": [ + { + "op": "AES Key Wrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f1011121314151617"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Wrap: RFC Test Vector, 192-bit data, 256-bit KEK", + "input": "00112233445566778899aabbccddeeff0001020304050607", + "expectedOutput": "a8f9bc1612c68b3ff6e6f4fbe30e71e4769c8b80a32cb8958cd5d17d6b254da1", + "recipeConfig": [ + { + "op": "AES Key Wrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Wrap: RFC Test Vector, 256-bit data, 256-bit KEK", + "input": "00112233445566778899aabbccddeeff000102030405060708090a0b0c0d0e0f", + "expectedOutput": "28c9f404c4b810f4cbccb35cfb87f8263f5786e2d80ed326cbc7f0e71a99f43bfb988b9b7a02dd21", + "recipeConfig": [ + { + "op": "AES Key Wrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: RFC Test Vector, 128-bit data, 128-bit KEK", + "input": "1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5", + "expectedOutput": "00112233445566778899aabbccddeeff", + "recipeConfig": [ + { + "op": "AES Key Unwrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: RFC Test Vector, 128-bit data, 192-bit KEK", + "input": "96778b25ae6ca435f92b5b97c050aed2468ab8a17ad84e5d", + "expectedOutput": "00112233445566778899aabbccddeeff", + "recipeConfig": [ + { + "op": "AES Key Unwrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f1011121314151617"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: RFC Test Vector, 128-bit data, 256-bit KEK", + "input": "64e8c3f9ce0f5ba263e9777905818a2a93c8191e7d6e8ae7", + "expectedOutput": "00112233445566778899aabbccddeeff", + "recipeConfig": [ + { + "op": "AES Key Unwrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: RFC Test Vector, 192-bit data, 192-bit KEK", + "input": "031d33264e15d33268f24ec260743edce1c6c7ddee725a936ba814915c6762d2", + "expectedOutput": "00112233445566778899aabbccddeeff0001020304050607", + "recipeConfig": [ + { + "op": "AES Key Unwrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f1011121314151617"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: RFC Test Vector, 192-bit data, 256-bit KEK", + "input": "a8f9bc1612c68b3ff6e6f4fbe30e71e4769c8b80a32cb8958cd5d17d6b254da1", + "expectedOutput": "00112233445566778899aabbccddeeff0001020304050607", + "recipeConfig": [ + { + "op": "AES Key Unwrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: RFC Test Vector, 256-bit data, 256-bit KEK", + "input": "28c9f404c4b810f4cbccb35cfb87f8263f5786e2d80ed326cbc7f0e71a99f43bfb988b9b7a02dd21", + "expectedOutput": "00112233445566778899aabbccddeeff000102030405060708090a0b0c0d0e0f", + "recipeConfig": [ + { + "op": "AES Key Unwrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Wrap: invalid KEK length", + "input": "00112233445566778899aabbccddeeff", + "expectedOutput": "KEK must be either 16, 24, or 32 bytes (currently 10 bytes)", + "recipeConfig": [ + { + "op": "AES Key Wrap", + "args": [ + {"option": "Hex", "string": "00010203040506070809"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Wrap: invalid IV length", + "input": "00112233445566778899aabbccddeeff", + "expectedOutput": "IV must be 8 bytes (currently 6 bytes)", + "recipeConfig": [ + { + "op": "AES Key Wrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Wrap: input length not multiple of 8", + "input": "00112233445566778899aabbccddeeff0102", + "expectedOutput": "input must be 8n (n>=2) bytes (currently 18 bytes)", + "recipeConfig": [ + { + "op": "AES Key Wrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Wrap: input too short", + "input": "0011223344556677", + "expectedOutput": "input must be 8n (n>=2) bytes (currently 8 bytes)", + "recipeConfig": [ + { + "op": "AES Key Wrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: invalid KEK length", + "input": "1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5", + "expectedOutput": "KEK must be either 16, 24, or 32 bytes (currently 10 bytes)", + "recipeConfig": [ + { + "op": "AES Key Unwrap", + "args": [ + {"option": "Hex", "string": "00010203040506070809"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: invalid IV length", + "input": "1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5", + "expectedOutput": "IV must be 8 bytes (currently 6 bytes)", + "recipeConfig": [ + { + "op": "AES Key Unwrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: input length not multiple of 8", + "input": "1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5e621", + "expectedOutput": "input must be 8n (n>=3) bytes (currently 26 bytes)", + "recipeConfig": [ + { + "op": "AES Key Unwrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: input too short", + "input": "1fa68b0a8112b447aef34bd8fb5a7b82", + "expectedOutput": "input must be 8n (n>=3) bytes (currently 16 bytes)", + "recipeConfig": [ + { + "op": "AES Key Unwrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: corrupted input", + "input": "1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe6", + "expectedOutput": "IV mismatch", + "recipeConfig": [ + { + "op": "AES Key Unwrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, +]); From c0bd6645ce85084236fb63cc476e3779fac4bfeb Mon Sep 17 00:00:00 2001 From: MikeCAT Date: Wed, 2 Nov 2022 02:07:16 +0900 Subject: [PATCH 10/74] add new operation: CMAC --- src/core/config/Categories.json | 1 + src/core/operations/CMAC.mjs | 141 ++++++++++++++ tests/operations/index.mjs | 1 + tests/operations/tests/CMAC.mjs | 314 ++++++++++++++++++++++++++++++++ 4 files changed, 457 insertions(+) create mode 100644 src/core/operations/CMAC.mjs create mode 100644 tests/operations/tests/CMAC.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 43d5dc4e..79f3c19c 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -365,6 +365,7 @@ "Compare SSDEEP hashes", "Compare CTPH hashes", "HMAC", + "CMAC", "Bcrypt", "Bcrypt compare", "Bcrypt parse", diff --git a/src/core/operations/CMAC.mjs b/src/core/operations/CMAC.mjs new file mode 100644 index 00000000..8c365362 --- /dev/null +++ b/src/core/operations/CMAC.mjs @@ -0,0 +1,141 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import forge from "node-forge"; +import { toHexFast } from "../lib/Hex.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * CMAC operation + */ +class CMAC extends Operation { + + /** + * CMAC constructor + */ + constructor() { + super(); + + this.name = "CMAC"; + this.module = "Crypto"; + this.description = "CMAC is a block-cipher based message authentication code algorithm.

RFC4493 defines AES-CMAC that uses AES encryption with a 128-bit key.
NIST SP 800-38B suggests usages of AES with other key lengths and Triple DES."; + this.infoURL = "https://wikipedia.org/wiki/CMAC"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Encryption algorithm", + "type": "option", + "value": ["AES", "Triple DES"] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteString(args[0].string, args[0].option); + const info = (function() { + switch (args[1]) { + case "AES": + if (key.length !== 16 && key.length !== 24 && key.length !== 32) { + throw new OperationError("the key for AES must be either 16, 24, or 32 bytes (currently " + key.length + " bytes)"); + } + return { + "algorithm": "AES-ECB", + "blockSize": 16, + "Rb": new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x87]), + }; + case "Triple DES": + if (key.length !== 24) { + throw new OperationError("the key for Triple DES must be 24 bytes (currently " + key.length + " bytes)"); + } + return { + "algorithm": "3DES-ECB", + "blockSize": 8, + "Rb": new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0x1b]), + }; + default: + throw new OperationError("undefined encryption algorithm"); + } + })(); + const xor = function(a, b, out) { + if (!out) out = new Uint8Array(a.length); + for (let i = 0; i < a.length; i++) { + out[i] = a[i] ^ b[i]; + } + return out; + }; + const leftShift1 = function(a) { + const out = new Uint8Array(a.length); + let carry = 0; + for (let i = a.length - 1; i >= 0; i--) { + out[i] = (a[i] << 1) | carry; + carry = a[i] >> 7; + } + return out; + }; + const cipher = forge.cipher.createCipher(info.algorithm, key); + const encrypt = function(a, out) { + if (!out) out = new Uint8Array(a.length); + cipher.start(); + cipher.update(forge.util.createBuffer(a)); + cipher.finish(); + const cipherText = cipher.output.getBytes(); + for (let i = 0; i < a.length; i++) { + out[i] = cipherText.charCodeAt(i); + } + return out; + }; + + const L = encrypt(new Uint8Array(info.blockSize)); + const K1 = leftShift1(L); + if (L[0] & 0x80) xor(K1, info.Rb, K1); + const K2 = leftShift1(K1); + if (K1[0] & 0x80) xor(K2, info.Rb, K2); + + const n = Math.ceil(input.byteLength / info.blockSize); + const lastBlock = (function() { + if (n === 0) { + const data = new Uint8Array(K2); + data[0] ^= 0x80; + return data; + } + const inputLast = new Uint8Array(input, info.blockSize * (n - 1)); + if (inputLast.length === info.blockSize) { + return xor(inputLast, K1, inputLast); + } else { + const data = new Uint8Array(info.blockSize); + data.set(inputLast, 0); + data[inputLast.length] = 0x80; + return xor(data, K2, data); + } + })(); + const X = new Uint8Array(info.blockSize); + const Y = new Uint8Array(info.blockSize); + for (let i = 0; i < n - 1; i++) { + xor(X, new Uint8Array(input, info.blockSize * i, info.blockSize), Y); + encrypt(Y, X); + } + xor(lastBlock, X, Y); + const T = encrypt(Y); + return toHexFast(T); + } + +} + +export default CMAC; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 19e70970..18a1b59f 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -124,6 +124,7 @@ import "./tests/UnescapeString.mjs"; import "./tests/LS47.mjs"; import "./tests/LZString.mjs"; import "./tests/NTLM.mjs"; +import "./tests/CMAC.mjs"; // Cannot test operations that use the File type yet // import "./tests/SplitColourChannels.mjs"; diff --git a/tests/operations/tests/CMAC.mjs b/tests/operations/tests/CMAC.mjs new file mode 100644 index 00000000..e5e3b40b --- /dev/null +++ b/tests/operations/tests/CMAC.mjs @@ -0,0 +1,314 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +// values in "NIST's CSRC" testcases are taken from here: +// https://csrc.nist.gov/projects/cryptographic-standards-and-guidelines/example-values + +TestRegister.addTests([ + { + "name": "CMAC-AES128 NIST's CSRC Example #1", + "input": "", + "expectedOutput": "bb1d6929e95937287fa37d129b756746", + "recipeConfig": [ + { + "op": "CMAC", + "args": [{"option": "Hex", "string": "2b7e151628aed2a6abf7158809cf4f3c"}, "AES"] + }, + ] + }, + { + "name": "CMAC-AES128 NIST's CSRC Example #2", + "input": "6bc1bee22e409f96e93d7e117393172a", + "expectedOutput": "070a16b46b4d4144f79bdd9dd04a287c", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["None"] + }, + { + "op": "CMAC", + "args": [{"option": "Hex", "string": "2b7e151628aed2a6abf7158809cf4f3c"}, "AES"] + }, + ] + }, + { + "name": "CMAC-AES128 NIST's CSRC Example #3", + "input": "6bc1bee22e409f96e93d7e117393172aae2d8a57", + "expectedOutput": "7d85449ea6ea19c823a7bf78837dfade", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["None"] + }, + { + "op": "CMAC", + "args": [{"option": "Hex", "string": "2b7e151628aed2a6abf7158809cf4f3c"}, "AES"] + }, + ] + }, + { + "name": "CMAC-AES128 NIST's CSRC Example #4", + "input": "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + "expectedOutput": "51f0bebf7e3b9d92fc49741779363cfe", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["None"] + }, + { + "op": "CMAC", + "args": [{"option": "Hex", "string": "2b7e151628aed2a6abf7158809cf4f3c"}, "AES"] + }, + ] + }, + { + "name": "CMAC-AES192 NIST's CSRC Example #1", + "input": "", + "expectedOutput": "d17ddf46adaacde531cac483de7a9367", + "recipeConfig": [ + { + "op": "CMAC", + "args": [{"option": "Hex", "string": "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b"}, "AES"] + }, + ] + }, + { + "name": "CMAC-AES192 NIST's CSRC Example #2", + "input": "6bc1bee22e409f96e93d7e117393172a", + "expectedOutput": "9e99a7bf31e710900662f65e617c5184", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["None"] + }, + { + "op": "CMAC", + "args": [{"option": "Hex", "string": "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b"}, "AES"] + }, + ] + }, + { + "name": "CMAC-AES192 NIST's CSRC Example #3", + "input": "6bc1bee22e409f96e93d7e117393172aae2d8a57", + "expectedOutput": "3d75c194ed96070444a9fa7ec740ecf8", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["None"] + }, + { + "op": "CMAC", + "args": [{"option": "Hex", "string": "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b"}, "AES"] + }, + ] + }, + { + "name": "CMAC-AES192 NIST's CSRC Example #4", + "input": "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + "expectedOutput": "a1d5df0eed790f794d77589659f39a11", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["None"] + }, + { + "op": "CMAC", + "args": [{"option": "Hex", "string": "8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b"}, "AES"] + }, + ] + }, + { + "name": "CMAC-AES256 NIST's CSRC Example #1", + "input": "", + "expectedOutput": "028962f61b7bf89efc6b551f4667d983", + "recipeConfig": [ + { + "op": "CMAC", + "args": [{"option": "Hex", "string": "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"}, "AES"] + }, + ] + }, + { + "name": "CMAC-AES256 NIST's CSRC Example #2", + "input": "6bc1bee22e409f96e93d7e117393172a", + "expectedOutput": "28a7023f452e8f82bd4bf28d8c37c35c", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["None"] + }, + { + "op": "CMAC", + "args": [{"option": "Hex", "string": "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"}, "AES"] + }, + ] + }, + { + "name": "CMAC-AES256 NIST's CSRC Example #3", + "input": "6bc1bee22e409f96e93d7e117393172aae2d8a57", + "expectedOutput": "156727dc0878944a023c1fe03bad6d93", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["None"] + }, + { + "op": "CMAC", + "args": [{"option": "Hex", "string": "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"}, "AES"] + }, + ] + }, + { + "name": "CMAC-AES256 NIST's CSRC Example #4", + "input": "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e5130c81c46a35ce411e5fbc1191a0a52eff69f2445df4f9b17ad2b417be66c3710", + "expectedOutput": "e1992190549f6ed5696a2c056c315410", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["None"] + }, + { + "op": "CMAC", + "args": [{"option": "Hex", "string": "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"}, "AES"] + }, + ] + }, + { + "name": "CMAC-TDES (1) NIST's CSRC Sample #1", + "input": "", + "expectedOutput": "7db0d37df936c550", + "recipeConfig": [ + { + "op": "CMAC", + "args": [{"option": "Hex", "string": "0123456789abcdef23456789abcdef01456789abcdef0123"}, "Triple DES"] + }, + ] + }, + { + "name": "CMAC-TDES (1) NIST's CSRC Sample #2", + "input": "6bc1bee22e409f96e93d7e117393172a", + "expectedOutput": "30239cf1f52e6609", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["None"] + }, + { + "op": "CMAC", + "args": [{"option": "Hex", "string": "0123456789abcdef23456789abcdef01456789abcdef0123"}, "Triple DES"] + }, + ] + }, + { + "name": "CMAC-TDES (1) NIST's CSRC Sample #3", + "input": "6bc1bee22e409f96e93d7e117393172aae2d8a57", + "expectedOutput": "6c9f3ee4923f6be2", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["None"] + }, + { + "op": "CMAC", + "args": [{"option": "Hex", "string": "0123456789abcdef23456789abcdef01456789abcdef0123"}, "Triple DES"] + }, + ] + }, + { + "name": "CMAC-TDES (1) NIST's CSRC Sample #4", + "input": "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51", + "expectedOutput": "99429bd0bf7904e5", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["None"] + }, + { + "op": "CMAC", + "args": [{"option": "Hex", "string": "0123456789abcdef23456789abcdef01456789abcdef0123"}, "Triple DES"] + }, + ] + }, + { + "name": "CMAC-TDES (2) NIST's CSRC Sample #1", + "input": "", + "expectedOutput": "79ce52a7f786a960", + "recipeConfig": [ + { + "op": "CMAC", + "args": [{"option": "Hex", "string": "0123456789abcdef23456789abcdef010123456789abcdef"}, "Triple DES"] + }, + ] + }, + { + "name": "CMAC-TDES (2) NIST's CSRC Sample #2", + "input": "6bc1bee22e409f96e93d7e117393172a", + "expectedOutput": "cc18a0b79af2413b", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["None"] + }, + { + "op": "CMAC", + "args": [{"option": "Hex", "string": "0123456789abcdef23456789abcdef010123456789abcdef"}, "Triple DES"] + }, + ] + }, + { + "name": "CMAC-TDES (2) NIST's CSRC Sample #3", + "input": "6bc1bee22e409f96e93d7e117393172aae2d8a57", + "expectedOutput": "c06d377ecd101969", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["None"] + }, + { + "op": "CMAC", + "args": [{"option": "Hex", "string": "0123456789abcdef23456789abcdef010123456789abcdef"}, "Triple DES"] + }, + ] + }, + { + "name": "CMAC-TDES (2) NIST's CSRC Sample #4", + "input": "6bc1bee22e409f96e93d7e117393172aae2d8a571e03ac9c9eb76fac45af8e51", + "expectedOutput": "9cd33580f9b64dfb", + "recipeConfig": [ + { + "op": "From Hex", + "args": ["None"] + }, + { + "op": "CMAC", + "args": [{"option": "Hex", "string": "0123456789abcdef23456789abcdef010123456789abcdef"}, "Triple DES"] + }, + ] + }, + { + "name": "CMAC-AES: invalid key length", + "input": "", + "expectedOutput": "the key for AES must be either 16, 24, or 32 bytes (currently 20 bytes)", + "recipeConfig": [ + { + "op": "CMAC", + "args": [{"option": "Hex", "string": "00112233445566778899aabbccddeeff01234567"}, "AES"] + }, + ] + }, + { + "name": "CMAC-TDES: invalid key length", + "input": "", + "expectedOutput": "the key for Triple DES must be 24 bytes (currently 20 bytes)", + "recipeConfig": [ + { + "op": "CMAC", + "args": [{"option": "Hex", "string": "00112233445566778899aabbccddeeff01234567"}, "Triple DES"] + }, + ] + }, +]); From 58b1fb8de5fc91ac866ce478e96a9b42c5ec877a Mon Sep 17 00:00:00 2001 From: MikeCAT Date: Wed, 2 Nov 2022 08:29:26 +0900 Subject: [PATCH 11/74] ViewBitPlane.mjs: use byteLength instead of length to check validity of ArrayBuffer --- src/core/operations/ViewBitPlane.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/operations/ViewBitPlane.mjs b/src/core/operations/ViewBitPlane.mjs index 859e6868..e1e5f6df 100644 --- a/src/core/operations/ViewBitPlane.mjs +++ b/src/core/operations/ViewBitPlane.mjs @@ -90,7 +90,7 @@ class ViewBitPlane extends Operation { * @returns {html} */ present(data) { - if (!data.length) return ""; + if (!data.byteLength) return ""; const type = isImage(data); return ``; From 5b134d7e9ec2b7cd036c8a2f4e52ddc39a6135d7 Mon Sep 17 00:00:00 2001 From: MikeCAT Date: Wed, 2 Nov 2022 21:54:45 +0900 Subject: [PATCH 12/74] fix Fletcher-32/64 Checksum * Operate on words, not bytes * Add tests --- src/core/operations/Fletcher32Checksum.mjs | 14 ++- src/core/operations/Fletcher64Checksum.mjs | 18 +++- tests/operations/index.mjs | 1 + tests/operations/tests/FletcherChecksum.mjs | 108 +++++++++++++++++++ tests/operations/tests/GenerateAllHashes.mjs | 4 +- 5 files changed, 137 insertions(+), 8 deletions(-) create mode 100644 tests/operations/tests/FletcherChecksum.mjs diff --git a/src/core/operations/Fletcher32Checksum.mjs b/src/core/operations/Fletcher32Checksum.mjs index 29c74535..7b41ce55 100644 --- a/src/core/operations/Fletcher32Checksum.mjs +++ b/src/core/operations/Fletcher32Checksum.mjs @@ -35,10 +35,18 @@ class Fletcher32Checksum extends Operation { run(input, args) { let a = 0, b = 0; - input = new Uint8Array(input); + if (ArrayBuffer.isView(input)) { + input = new DataView(input.buffer, input.byteOffset, input.byteLength); + } else { + input = new DataView(input); + } - for (let i = 0; i < input.length; i++) { - a = (a + input[i]) % 0xffff; + for (let i = 0; i < input.byteLength - 1; i += 2) { + a = (a + input.getUint16(i, true)) % 0xffff; + b = (b + a) % 0xffff; + } + if (input.byteLength % 2 !== 0) { + a = (a + input.getUint8(input.byteLength - 1)) % 0xffff; b = (b + a) % 0xffff; } diff --git a/src/core/operations/Fletcher64Checksum.mjs b/src/core/operations/Fletcher64Checksum.mjs index 1d0d5bd9..68328ea3 100644 --- a/src/core/operations/Fletcher64Checksum.mjs +++ b/src/core/operations/Fletcher64Checksum.mjs @@ -35,10 +35,22 @@ class Fletcher64Checksum extends Operation { run(input, args) { let a = 0, b = 0; - input = new Uint8Array(input); + if (ArrayBuffer.isView(input)) { + input = new DataView(input.buffer, input.byteOffset, input.byteLength); + } else { + input = new DataView(input); + } - for (let i = 0; i < input.length; i++) { - a = (a + input[i]) % 0xffffffff; + for (let i = 0; i < input.byteLength - 3; i += 4) { + a = (a + input.getUint32(i, true)) % 0xffffffff; + b = (b + a) % 0xffffffff; + } + if (input.byteLength % 4 !== 0) { + let lastValue = 0; + for (let i = 0; i < input.byteLength % 4; i++) { + lastValue = (lastValue << 8) | input.getUint8(input.byteLength - 1 - i); + } + a = (a + lastValue) % 0xffffffff; b = (b + a) % 0xffffffff; } diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 19e70970..fe1c4e00 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -124,6 +124,7 @@ import "./tests/UnescapeString.mjs"; import "./tests/LS47.mjs"; import "./tests/LZString.mjs"; import "./tests/NTLM.mjs"; +import "./tests/FletcherChecksum.mjs"; // Cannot test operations that use the File type yet // import "./tests/SplitColourChannels.mjs"; diff --git a/tests/operations/tests/FletcherChecksum.mjs b/tests/operations/tests/FletcherChecksum.mjs new file mode 100644 index 00000000..de23abdc --- /dev/null +++ b/tests/operations/tests/FletcherChecksum.mjs @@ -0,0 +1,108 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Fletcher-16 Checksum: abcde", + input: "abcde", + expectedOutput: "c8f0", + recipeConfig: [ + { + op: "Fletcher-16 Checksum", + args: [], + }, + ], + }, + { + name: "Fletcher-16 Checksum: abcdef", + input: "abcdef", + expectedOutput: "2057", + recipeConfig: [ + { + op: "Fletcher-16 Checksum", + args: [], + }, + ], + }, + { + name: "Fletcher-16 Checksum: abcdefgh", + input: "abcdefgh", + expectedOutput: "0627", + recipeConfig: [ + { + op: "Fletcher-16 Checksum", + args: [], + }, + ], + }, + { + name: "Fletcher-32 Checksum: abcde", + input: "abcde", + expectedOutput: "f04fc729", + recipeConfig: [ + { + op: "Fletcher-32 Checksum", + args: [], + }, + ], + }, + { + name: "Fletcher-32 Checksum: abcdef", + input: "abcdef", + expectedOutput: "56502d2a", + recipeConfig: [ + { + op: "Fletcher-32 Checksum", + args: [], + }, + ], + }, + { + name: "Fletcher-32 Checksum: abcdefgh", + input: "abcdefgh", + expectedOutput: "ebe19591", + recipeConfig: [ + { + op: "Fletcher-32 Checksum", + args: [], + }, + ], + }, + { + name: "Fletcher-64 Checksum: abcde", + input: "abcde", + expectedOutput: "c8c6c527646362c6", + recipeConfig: [ + { + op: "Fletcher-64 Checksum", + args: [], + }, + ], + }, + { + name: "Fletcher-64 Checksum: abcdef", + input: "abcdef", + expectedOutput: "c8c72b276463c8c6", + recipeConfig: [ + { + op: "Fletcher-64 Checksum", + args: [], + }, + ], + }, + { + name: "Fletcher-64 Checksum: abcdefgh", + input: "abcdefgh", + expectedOutput: "312e2b28cccac8c6", + recipeConfig: [ + { + op: "Fletcher-64 Checksum", + args: [], + }, + ], + }, +]); diff --git a/tests/operations/tests/GenerateAllHashes.mjs b/tests/operations/tests/GenerateAllHashes.mjs index 28707bde..8e4a849a 100644 --- a/tests/operations/tests/GenerateAllHashes.mjs +++ b/tests/operations/tests/GenerateAllHashes.mjs @@ -58,8 +58,8 @@ CTPH: A:E:E Checksums: Fletcher-8: 3d Fletcher-16: 5dc1 -Fletcher-32: 045901c0 -Fletcher-64: 00000459000001c0 +Fletcher-32: 3f5cd9e7 +Fletcher-64: 7473657474736574 Adler-32: 045d01c1 CRC-8: b9 CRC-16: f82e From 3700780d14e76d6d089b3a470f194a84e3b542e3 Mon Sep 17 00:00:00 2001 From: MikeCAT Date: Wed, 2 Nov 2022 22:37:09 +0900 Subject: [PATCH 13/74] improve "Reverse" operation * Make "Character" option actually reverse characters * Add new option "Byte" that behaves as previous "Character" option --- src/core/operations/Reverse.mjs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/core/operations/Reverse.mjs b/src/core/operations/Reverse.mjs index 895d6723..49c752a8 100644 --- a/src/core/operations/Reverse.mjs +++ b/src/core/operations/Reverse.mjs @@ -5,6 +5,7 @@ */ import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; /** * Reverse operation @@ -26,7 +27,8 @@ class Reverse extends Operation { { "name": "By", "type": "option", - "value": ["Character", "Line"] + "value": ["Byte", "Character", "Line"], + "defaultIndex": 1 } ]; } @@ -57,6 +59,24 @@ class Reverse extends Operation { result.push(0x0a); } return result.slice(0, input.length); + } else if (args[0] === "Character") { + const inputString = Utils.byteArrayToUtf8(input); + let result = ""; + for (let i = inputString.length - 1; i >= 0; i--) { + const c = inputString.charCodeAt(i); + if (i > 0 && 0xdc00 <= c && c <= 0xdfff) { + const c2 = inputString.charCodeAt(i - 1); + if (0xd800 <= c2 && c2 <= 0xdbff) { + // surrogates + result += inputString.charAt(i - 1); + result += inputString.charAt(i); + i--; + continue; + } + } + result += inputString.charAt(i); + } + return Utils.strToUtf8ByteArray(result); } else { return input.reverse(); } From 3086c250791038be2d92ac97d6e241e0674896ca Mon Sep 17 00:00:00 2001 From: MikeCAT Date: Wed, 2 Nov 2022 23:14:48 +0900 Subject: [PATCH 14/74] improve treatment of Hex(little endian) for Windows Filetime converter --- src/core/operations/UNIXTimestampToWindowsFiletime.mjs | 3 +++ src/core/operations/WindowsFiletimeToUNIXTimestamp.mjs | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/core/operations/UNIXTimestampToWindowsFiletime.mjs b/src/core/operations/UNIXTimestampToWindowsFiletime.mjs index 1ccd3298..ad88fb97 100644 --- a/src/core/operations/UNIXTimestampToWindowsFiletime.mjs +++ b/src/core/operations/UNIXTimestampToWindowsFiletime.mjs @@ -79,6 +79,9 @@ class UNIXTimestampToWindowsFiletime extends Operation { flipped += result.charAt(i); flipped += result.charAt(i + 1); } + if (result.length % 2 !== 0) { + flipped += "0" + result.charAt(0); + } result = flipped; } diff --git a/src/core/operations/WindowsFiletimeToUNIXTimestamp.mjs b/src/core/operations/WindowsFiletimeToUNIXTimestamp.mjs index 7411a6e9..786cbcce 100644 --- a/src/core/operations/WindowsFiletimeToUNIXTimestamp.mjs +++ b/src/core/operations/WindowsFiletimeToUNIXTimestamp.mjs @@ -52,7 +52,10 @@ class WindowsFiletimeToUNIXTimestamp extends Operation { if (format === "Hex (little endian)") { // Swap endianness let result = ""; - for (let i = input.length - 2; i >= 0; i -= 2) { + if (input.length % 2 !== 0) { + result += input.charAt(input.length - 1); + } + for (let i = input.length - input.length % 2 - 2; i >= 0; i -= 2) { result += input.charAt(i); result += input.charAt(i + 1); } From c046cf569578f42d548686d9af2893aa4a6ae6bc Mon Sep 17 00:00:00 2001 From: MikeCAT Date: Thu, 3 Nov 2022 00:21:20 +0900 Subject: [PATCH 15/74] have "From Hex" treat the delimiter as delimiter, not what to erase --- src/core/lib/Hex.mjs | 12 ++++++++---- tests/node/tests/operations.mjs | 12 ++++++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/core/lib/Hex.mjs b/src/core/lib/Hex.mjs index b7e8e908..78e1ad58 100644 --- a/src/core/lib/Hex.mjs +++ b/src/core/lib/Hex.mjs @@ -105,13 +105,17 @@ export function fromHex(data, delim="Auto", byteLen=2) { throw new OperationError("Byte length must be a positive integer"); if (delim !== "None") { - const delimRegex = delim === "Auto" ? /[^a-f\d]|(0x)/gi : Utils.regexRep(delim); - data = data.replace(delimRegex, ""); + const delimRegex = delim === "Auto" ? /[^a-f\d]|0x/gi : Utils.regexRep(delim); + data = data.split(delimRegex); + } else { + data = [data]; } const output = []; - for (let i = 0; i < data.length; i += byteLen) { - output.push(parseInt(data.substr(i, byteLen), 16)); + for (let i = 0; i < data.length; i++) { + for (let j = 0; j < data[i].length; j += byteLen) { + output.push(parseInt(data[i].substr(j, byteLen), 16)); + } } return output; } diff --git a/tests/node/tests/operations.mjs b/tests/node/tests/operations.mjs index 783bd00a..8611ecb4 100644 --- a/tests/node/tests/operations.mjs +++ b/tests/node/tests/operations.mjs @@ -45,10 +45,10 @@ TestRegister.addApiTests([ const result = chef.ADD("sample input", { key: { string: "some key", - option: "Hex" + option: "utf8" } }); - assert.equal(result.toString(), "aO[^ZS\u000eW\\^cb"); + assert.equal(result.toString(), "\xe6\xd0\xda\xd5\x8c\xd0\x85\xe2\xe1\xdf\xe2\xd9"); }), @@ -121,10 +121,10 @@ Tiger-128`; const result = chef.AND("Scot-free", { key: { string: "Raining Cats and Dogs", - option: "Hex", + option: "utf8", } }); - assert.strictEqual(result.toString(), "\u0000\"M$(D E"); + assert.strictEqual(result.toString(), "Raid)fb A"); }), it("atBash Cipher", () => { @@ -371,10 +371,10 @@ color: white; }, salt: { string: "Market", - option: "Hex", + option: "utf8", }, }); - assert.strictEqual(result.toString(), "7c21a9f5063a4d62fb1050068245c181"); + assert.strictEqual(result.toString(), "4930d5d200e80f18c96b5550d13c6af8"); }), it("Derive PBKDF2 Key", () => { From 2255c5b360a50645e7e533d1369f11c9391bbb40 Mon Sep 17 00:00:00 2001 From: MikeCAT Date: Thu, 3 Nov 2022 01:12:01 +0900 Subject: [PATCH 16/74] allow 16-byte keys for Triple DES --- src/core/operations/TripleDESDecrypt.mjs | 5 +++-- src/core/operations/TripleDESEncrypt.mjs | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/core/operations/TripleDESDecrypt.mjs b/src/core/operations/TripleDESDecrypt.mjs index 7626eeab..8487509f 100644 --- a/src/core/operations/TripleDESDecrypt.mjs +++ b/src/core/operations/TripleDESDecrypt.mjs @@ -70,7 +70,7 @@ class TripleDESDecrypt extends Operation { inputType = args[3], outputType = args[4]; - if (key.length !== 24) { + if (key.length !== 24 && key.length !== 16) { throw new OperationError(`Invalid key length: ${key.length} bytes Triple DES uses a key length of 24 bytes (192 bits). @@ -85,7 +85,8 @@ Make sure you have specified the type correctly (e.g. Hex vs UTF8).`); input = Utils.convertToByteString(input, inputType); - const decipher = forge.cipher.createDecipher("3DES-" + mode, key); + const decipher = forge.cipher.createDecipher("3DES-" + mode, + key.length === 16 ? key + key.substring(0, 8) : key); /* Allow for a "no padding" mode */ if (noPadding) { diff --git a/src/core/operations/TripleDESEncrypt.mjs b/src/core/operations/TripleDESEncrypt.mjs index 237020a2..720d155d 100644 --- a/src/core/operations/TripleDESEncrypt.mjs +++ b/src/core/operations/TripleDESEncrypt.mjs @@ -69,7 +69,7 @@ class TripleDESEncrypt extends Operation { inputType = args[3], outputType = args[4]; - if (key.length !== 24) { + if (key.length !== 24 && key.length !== 16) { throw new OperationError(`Invalid key length: ${key.length} bytes Triple DES uses a key length of 24 bytes (192 bits). @@ -84,7 +84,8 @@ Make sure you have specified the type correctly (e.g. Hex vs UTF8).`); input = Utils.convertToByteString(input, inputType); - const cipher = forge.cipher.createCipher("3DES-" + mode, key); + const cipher = forge.cipher.createCipher("3DES-" + mode, + key.length === 16 ? key + key.substring(0, 8) : key); cipher.start({iv: iv}); cipher.update(forge.util.createBuffer(input)); cipher.finish(); From fe9eb08648bd3edaa82f9438db3fdf33dcf3ed36 Mon Sep 17 00:00:00 2001 From: MikeCAT Date: Thu, 3 Nov 2022 01:20:37 +0900 Subject: [PATCH 17/74] allow 16-byte keys for Triple DES in CMAC operation --- src/core/operations/CMAC.mjs | 8 +++++--- tests/operations/tests/CMAC.mjs | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/core/operations/CMAC.mjs b/src/core/operations/CMAC.mjs index 8c365362..12c4080c 100644 --- a/src/core/operations/CMAC.mjs +++ b/src/core/operations/CMAC.mjs @@ -57,15 +57,17 @@ class CMAC extends Operation { } return { "algorithm": "AES-ECB", + "key": key, "blockSize": 16, "Rb": new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x87]), }; case "Triple DES": - if (key.length !== 24) { - throw new OperationError("the key for Triple DES must be 24 bytes (currently " + key.length + " bytes)"); + if (key.length !== 16 && key.length !== 24) { + throw new OperationError("the key for Triple DES must be 16 or 24 bytes (currently " + key.length + " bytes)"); } return { "algorithm": "3DES-ECB", + "key": key.length === 16 ? key + key.substring(0, 8) : key, "blockSize": 8, "Rb": new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0x1b]), }; @@ -89,7 +91,7 @@ class CMAC extends Operation { } return out; }; - const cipher = forge.cipher.createCipher(info.algorithm, key); + const cipher = forge.cipher.createCipher(info.algorithm, info.key); const encrypt = function(a, out) { if (!out) out = new Uint8Array(a.length); cipher.start(); diff --git a/tests/operations/tests/CMAC.mjs b/tests/operations/tests/CMAC.mjs index e5e3b40b..92224968 100644 --- a/tests/operations/tests/CMAC.mjs +++ b/tests/operations/tests/CMAC.mjs @@ -303,7 +303,7 @@ TestRegister.addTests([ { "name": "CMAC-TDES: invalid key length", "input": "", - "expectedOutput": "the key for Triple DES must be 24 bytes (currently 20 bytes)", + "expectedOutput": "the key for Triple DES must be 16 or 24 bytes (currently 20 bytes)", "recipeConfig": [ { "op": "CMAC", From 1e83e0e9355b23f7f3e3339efb95ac5415b3d071 Mon Sep 17 00:00:00 2001 From: MikeCAT Date: Thu, 3 Nov 2022 21:43:24 +0900 Subject: [PATCH 18/74] convert hex string to lower before parsing as ASN.1 --- src/core/operations/ParseASN1HexString.mjs | 2 +- src/core/operations/ParseX509Certificate.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/operations/ParseASN1HexString.mjs b/src/core/operations/ParseASN1HexString.mjs index a19ca70c..5e1753a4 100644 --- a/src/core/operations/ParseASN1HexString.mjs +++ b/src/core/operations/ParseASN1HexString.mjs @@ -45,7 +45,7 @@ class ParseASN1HexString extends Operation { */ run(input, args) { const [index, truncateLen] = args; - return r.ASN1HEX.dump(input.replace(/\s/g, ""), { + return r.ASN1HEX.dump(input.replace(/\s/g, "").toLowerCase(), { "ommitLongOctet": truncateLen }, index); } diff --git a/src/core/operations/ParseX509Certificate.mjs b/src/core/operations/ParseX509Certificate.mjs index aeb5f677..28c9569f 100644 --- a/src/core/operations/ParseX509Certificate.mjs +++ b/src/core/operations/ParseX509Certificate.mjs @@ -59,7 +59,7 @@ class ParseX509Certificate extends Operation { switch (inputFormat) { case "DER Hex": - input = input.replace(/\s/g, ""); + input = input.replace(/\s/g, "").toLowerCase(); cert.readCertHex(input); break; case "PEM": From ebe2a2954389ac4538af6b0a08bcc002c2c15d2f Mon Sep 17 00:00:00 2001 From: Joost Rijneveld Date: Thu, 3 Nov 2022 14:47:40 +0100 Subject: [PATCH 19/74] Add ChaCha stream cipher operation --- src/core/Utils.mjs | 61 ++++++++ src/core/config/Categories.json | 1 + src/core/operations/ChaCha.mjs | 236 ++++++++++++++++++++++++++++++ tests/operations/index.mjs | 1 + tests/operations/tests/ChaCha.mjs | 151 +++++++++++++++++++ 5 files changed, 450 insertions(+) create mode 100644 src/core/operations/ChaCha.mjs create mode 100644 tests/operations/tests/ChaCha.mjs diff --git a/src/core/Utils.mjs b/src/core/Utils.mjs index 31e17d1a..2457c5b7 100755 --- a/src/core/Utils.mjs +++ b/src/core/Utils.mjs @@ -381,6 +381,67 @@ class Utils { } } + /** + * Converts a byte array to an integer. + * + * @param {byteArray} byteArray + * @param {string} byteorder - "little" or "big" + * @returns {integer} + * + * @example + * // returns 67305985 + * Utils.byteArrayToInt([ 1, 2, 3, 4], "little"); + * + * // returns 16909060 + * Utils.byteArrayToInt([ 1, 2, 3, 4], "big"); + */ + static byteArrayToInt(byteArray, byteorder) { + let value = 0; + if (byteorder === "big") { + for (let i = 0; i < byteArray.length; i++) { + value = (value * 256) + byteArray[i]; + } + } else { + for (let i = byteArray.length - 1; i >= 0; i--) { + value = (value * 256) + byteArray[i]; + } + } + return value; + } + + /** + * Converts an integer to a byte array of {length} bytes. + * + * @param {integer} value + * @param {integer} length + * @param {string} byteorder - "little" or "big" + * @returns {byteArray} + * + * @example + * // returns [ 5, 255, 109, 1 ] + * Utils.intToByteArray(23985925, 4, "little"); + * + * // returns [ 1, 109, 255, 5 ] + * Utils.intToByteArray(23985925, 4, "big"); + * + * // returns [ 0, 0, 0, 0, 1, 109, 255, 5 ] + * Utils.intToByteArray(23985925, 8, "big"); + */ + static intToByteArray(value, length, byteorder) { + const arr = new Array(length); + if (byteorder === "little") { + for (let i = 0; i < length; i++) { + arr[i] = value & 0xFF; + value = value >>> 8; + } + } else { + for (let i = length - 1; i >= 0; i--) { + arr[i] = value & 0xFF; + value = value >>> 8; + } + } + return arr; + } /** * Converts a string to an ArrayBuffer. diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 43d5dc4e..3a23a920 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -75,6 +75,7 @@ "AES Decrypt", "Blowfish Encrypt", "Blowfish Decrypt", + "ChaCha", "DES Encrypt", "DES Decrypt", "Triple DES Encrypt", diff --git a/src/core/operations/ChaCha.mjs b/src/core/operations/ChaCha.mjs new file mode 100644 index 00000000..1f253c68 --- /dev/null +++ b/src/core/operations/ChaCha.mjs @@ -0,0 +1,236 @@ +/** + * @author joostrijneveld [joost@joostrijneveld.nl] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHex } from "../lib/Hex.mjs"; + +/** + * Computes the ChaCha block function + * + * @param {byteArray} key + * @param {byteArray} nonce + * @param {byteArray} counter + * @param {integer} rounds + * @returns {byteArray} + */ +function chacha(key, nonce, counter, rounds) { + const tau = "expand 16-byte k"; + const sigma = "expand 32-byte k"; + + let state, c; + if (key.length === 16) { + c = Utils.strToByteArray(tau); + state = c.concat(key).concat(key); + } else { + c = Utils.strToByteArray(sigma); + state = c.concat(key); + } + state = state.concat(counter).concat(nonce); + + const x = Array(); + for (let i = 0; i < 64; i += 4) { + x.push(Utils.byteArrayToInt(state.slice(i, i + 4), "little")); + } + const a = [...x]; + + /** + * Macro to compute a 32-bit rotate-left operation + * + * @param {integer} x + * @param {integer} n + * @returns {integer} + */ + function ROL32(x, n) { + return ((x << n) & 0xFFFFFFFF) | (x >>> (32 - n)); + } + + /** + * Macro to compute a single ChaCha quarterround operation + * + * @param {integer} x + * @param {integer} a + * @param {integer} b + * @param {integer} c + * @param {integer} d + * @returns {integer} + */ + function quarterround(x, a, b, c, d) { + x[a] = ((x[a] + x[b]) & 0xFFFFFFFF); x[d] = ROL32(x[d] ^ x[a], 16); + x[c] = ((x[c] + x[d]) & 0xFFFFFFFF); x[b] = ROL32(x[b] ^ x[c], 12); + x[a] = ((x[a] + x[b]) & 0xFFFFFFFF); x[d] = ROL32(x[d] ^ x[a], 8); + x[c] = ((x[c] + x[d]) & 0xFFFFFFFF); x[b] = ROL32(x[b] ^ x[c], 7); + } + + for (let i = 0; i < rounds / 2; i++) { + quarterround(x, 0, 4, 8, 12); + quarterround(x, 1, 5, 9, 13); + quarterround(x, 2, 6, 10, 14); + quarterround(x, 3, 7, 11, 15); + quarterround(x, 0, 5, 10, 15); + quarterround(x, 1, 6, 11, 12); + quarterround(x, 2, 7, 8, 13); + quarterround(x, 3, 4, 9, 14); + } + + for (let i = 0; i < 16; i++) { + x[i] = (x[i] + a[i]) & 0xFFFFFFFF; + } + + let output = Array(); + for (let i = 0; i < 16; i++) { + output = output.concat(Utils.intToByteArray(x[i], 4, "little")); + } + return output; +} + +/** + * ChaCha operation + */ +class ChaCha extends Operation { + + /** + * ChaCha constructor + */ + constructor() { + super(); + + this.name = "ChaCha"; + this.module = "Default"; + this.description = "ChaCha is a stream cipher designed by Daniel J. Bernstein. It is a variant of the Salsa stream cipher. Several parameterizations exist; 'ChaCha' may refer to the original construction, or to the variant as described in RFC-8439. ChaCha is often used with Poly1305, in the ChaCha20-Poly1305 AEAD construction.

Key: ChaCha uses a key of 16 or 32 bytes (128 or 256 bits).

Nonce: ChaCha uses a nonce of 8 or 12 bytes (64 or 96 bits).

Counter: ChaCha uses a counter of 4 or 8 bytes (32 or 64 bits); together, the nonce and counter must add up to 16 bytes. The counter starts at zero at the start of the keystream, and is incremented at every 64 bytes."; + this.infoURL = "https://wikipedia.org/wiki/Salsa20#ChaCha_variant"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Nonce", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64", "Integer"] + }, + { + "name": "Counter", + "type": "number", + "value": 0, + "min": 0 + }, + { + "name": "Rounds", + "type": "option", + "value": ["20", "12", "8"] + }, + { + "name": "Input", + "type": "option", + "value": ["Hex", "Raw"] + }, + { + "name": "Output", + "type": "option", + "value": ["Raw", "Hex"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string, args[0].option), + nonceType = args[1].option, + rounds = parseInt(args[3], 10), + inputType = args[4], + outputType = args[5]; + + if (key.length !== 16 && key.length !== 32) { + throw new OperationError(`Invalid key length: ${key.length} bytes. + +ChaCha uses a key of 16 or 32 bytes (128 or 256 bits).`); + } + let counter, nonce; + let counterLength; + if (nonceType !== "Integer") { + nonce = Utils.convertToByteArray(args[1].string, args[1].option); + if (!(nonce.length === 12 || nonce.length === 8)) { + throw new OperationError(`Invalid nonce length: ${nonce.length} bytes. + +ChaCha uses a nonce of 8 or 12 bytes (64 or 96 bits).`); + } + counterLength = 16 - nonce.length; + counter = Utils.intToByteArray(args[2], counterLength, "little"); + } + if (nonceType === "Integer") { + nonce = Utils.intToByteArray(parseInt(args[1].string, 10), 12, "little"); + counterLength = 4; + counter = Utils.intToByteArray(args[2], counterLength, "little"); + } + + const output = new Array(); + input = Utils.convertToByteArray(input, inputType); + + let counterAsInt = Utils.byteArrayToInt(counter, "little"); + for (let i = 0; i < input.length; i += 64) { + counter = Utils.intToByteArray(counterAsInt, counterLength, "little"); + const stream = chacha(key, nonce, counter, rounds); + for (let j = 0; j < 64 && i + j < input.length; j++) { + output.push(input[i + j] ^ stream[j]); + } + counterAsInt++; + } + + if (outputType === "Hex") { + return toHex(output); + } else { + return Utils.arrayBufferToStr(output); + } + } + + /** + * Highlight ChaCha + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + const inputType = args[4], + outputType = args[5]; + if (inputType === "Raw" && outputType === "Raw") { + return pos; + } + } + + /** + * Highlight ChaCha in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + const inputType = args[4], + outputType = args[5]; + if (inputType === "Raw" && outputType === "Raw") { + return pos; + } + } + +} + +export default ChaCha; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 19e70970..43fbfd82 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -30,6 +30,7 @@ import "./tests/ByteRepr.mjs"; import "./tests/CartesianProduct.mjs"; import "./tests/CetaceanCipherEncode.mjs"; import "./tests/CetaceanCipherDecode.mjs"; +import "./tests/ChaCha.mjs"; import "./tests/CharEnc.mjs"; import "./tests/ChangeIPFormat.mjs"; import "./tests/Charts.mjs"; diff --git a/tests/operations/tests/ChaCha.mjs b/tests/operations/tests/ChaCha.mjs new file mode 100644 index 00000000..44b39a67 --- /dev/null +++ b/tests/operations/tests/ChaCha.mjs @@ -0,0 +1,151 @@ +/** + * ChaCha tests. + * + * @author joostrijneveld [joost@joostrijneveld.nl] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "ChaCha: no key", + input: "", + expectedOutput: `Invalid key length: 0 bytes. + +ChaCha uses a key of 16 or 32 bytes (128 or 256 bits).`, + recipeConfig: [ + { + "op": "ChaCha", + "args": [ + {"option": "Hex", "string": ""}, + {"option": "Hex", "string": ""}, + 0, "20", "Hex", "Hex", + ] + } + ], + }, + { + name: "ChaCha: no nonce", + input: "", + expectedOutput: `Invalid nonce length: 0 bytes. + +ChaCha uses a nonce of 8 or 12 bytes (64 or 96 bits).`, + recipeConfig: [ + { + "op": "ChaCha", + "args": [ + {"option": "Hex", "string": "00000000000000000000000000000000"}, + {"option": "Hex", "string": ""}, + 0, "20", "Hex", "Hex", + ] + } + ], + }, + { + name: "ChaCha: RFC8439", + input: "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.", + expectedOutput: "6e 2e 35 9a 25 68 f9 80 41 ba 07 28 dd 0d 69 81 e9 7e 7a ec 1d 43 60 c2 0a 27 af cc fd 9f ae 0b f9 1b 65 c5 52 47 33 ab 8f 59 3d ab cd 62 b3 57 16 39 d6 24 e6 51 52 ab 8f 53 0c 35 9f 08 61 d8 07 ca 0d bf 50 0d 6a 61 56 a3 8e 08 8a 22 b6 5e 52 bc 51 4d 16 cc f8 06 81 8c e9 1a b7 79 37 36 5a f9 0b bf 74 a3 5b e6 b4 0b 8e ed f2 78 5e 42 87 4d", + recipeConfig: [ + { + "op": "ChaCha", + "args": [ + {"option": "Hex", "string": "00:01:02:03:04:05:06:07:08:09:0a:0b:0c:0d:0e:0f:10:11:12:13:14:15:16:17:18:19:1a:1b:1c:1d:1e:1f"}, + {"option": "Hex", "string": "00:00:00:00:00:00:00:4a:00:00:00:00"}, + 1, "20", "Raw", "Hex", + ] + } + ], + }, + { + name: "ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.1", + input: "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", + expectedOutput: "29 56 0d 28 0b 45 28 40 0a 8f 4b 79 53 69 fb 3a 01 10 55 99 e9 f1 ed 58 27 9c fc 9e ce 2d c5 f9 9f 1c 2e 52 c9 82 38 f5 42 a5 c0 a8 81 d8 50 b6 15 d3 ac d9 fb db 02 6e 93 68 56 5d a5 0e 0d 49 dd 5b e8 ef 74 24 8b 3e 25 1d 96 5d 8f cb 21 e7 cf e2 04 d4 00 78 06 fb ee 3c e9 4c 74 bf ba d2 c1 1c 62 1b a0 48 14 7c 5c aa 94 d1 82 cc ff 6f d5 cf 44 ad f9 6e 3d 68 28 1b b4 96 76 af 87 e7", + recipeConfig: [ + { + "op": "ChaCha", + "args": [ + {"option": "Hex", "string": "00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff"}, + {"option": "Hex", "string": "0f 1e 2d 3c 4b 5a 69 78"}, + 0, "8", "Hex", "Hex", + ] + } + ], + }, + { + name: "ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.2", + input: "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", + expectedOutput: "5e dd c2 d9 42 8f ce ee c5 0a 52 a9 64 ea e0 ff b0 4b 2d e0 06 a9 b0 4c ff 36 8f fa 92 11 16 b2 e8 e2 64 ba bd 2e fa 0d e4 3e f2 e3 b6 d0 65 e8 f7 c0 a1 78 37 b0 a4 0e b0 e2 c7 a3 74 2c 87 53 ed e5 f3 f6 d1 9b e5 54 67 5e 50 6a 77 5c 63 f0 94 d4 96 5c 31 93 19 dc d7 50 6f 45 7b 11 7b 84 b1 0b 24 6e 95 6c 2d a8 89 8a 65 6c ee f3 f7 b7 16 45 b1 9f 70 1d b8 44 85 ce 51 21 f0 f6 17 ef", + recipeConfig: [ + { + "op": "ChaCha", + "args": [ + {"option": "Hex", "string": "00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff"}, + {"option": "Hex", "string": "0f 1e 2d 3c 4b 5a 69 78"}, + 0, "12", "Hex", "Hex", + ] + } + ], + }, + { + name: "ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.3", + input: "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", + expectedOutput: "d1 ab f6 30 46 7e b4 f6 7f 1c fb 47 cd 62 6a ae 8a fe db be 4f f8 fc 5f e9 cf ae 30 7e 74 ed 45 1f 14 04 42 5a d2 b5 45 69 d5 f1 81 48 93 99 71 ab b8 fa fc 88 ce 4a c7 fe 1c 3d 1f 7a 1e b7 ca e7 6c a8 7b 61 a9 71 35 41 49 77 60 dd 9a e0 59 35 0c ad 0d ce df aa 80 a8 83 11 9a 1a 6f 98 7f d1 ce 91 fd 8e e0 82 80 34 b4 11 20 0a 97 45 a2 85 55 44 75 d1 2a fc 04 88 7f ef 35 16 d1 2a 2c", + recipeConfig: [ + { + "op": "ChaCha", + "args": [ + {"option": "Hex", "string": "00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff"}, + {"option": "Hex", "string": "0f 1e 2d 3c 4b 5a 69 78"}, + 0, "20", "Hex", "Hex", + ] + } + ], + }, + { + name: "ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.4", + input: "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", + expectedOutput: "db 43 ad 9d 1e 84 2d 12 72 e4 53 0e 27 6b 3f 56 8f 88 59 b3 f7 cf 6d 9d 2c 74 fa 53 80 8c b5 15 7a 8e bf 46 ad 3d cc 4b 6c 7d ad de 13 17 84 b0 12 0e 0e 22 f6 d5 f9 ff a7 40 7d 4a 21 b6 95 d9 c5 dd 30 bf 55 61 2f ab 9b dd 11 89 20 c1 98 16 47 0c 7f 5d cd 42 32 5d bb ed 8c 57 a5 62 81 c1 44 cb 0f 03 e8 1b 30 04 62 4e 06 50 a1 ce 5a fa f9 a7 cd 81 63 f6 db d7 26 02 25 7d d9 6e 47 1e", + recipeConfig: [ + { + "op": "ChaCha", + "args": [ + {"option": "Hex", "string": "00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff ff ee dd cc bb aa 99 88 77 66 55 44 33 22 11 00"}, + {"option": "Hex", "string": "0f 1e 2d 3c 4b 5a 69 78"}, + 0, "8", "Hex", "Hex", + ] + } + ], + }, + { + name: "ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.5", + input: "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", + expectedOutput: "7e d1 2a 3a 63 91 2a e9 41 ba 6d 4c 0d 5e 86 2e 56 8b 0e 55 89 34 69 35 50 5f 06 4b 8c 26 98 db f7 d8 50 66 7d 8e 67 be 63 9f 3b 4f 6a 16 f9 2e 65 ea 80 f6 c7 42 94 45 da 1f c2 c1 b9 36 50 40 e3 2e 50 c4 10 6f 3b 3d a1 ce 7c cb 1e 71 40 b1 53 49 3c 0f 3a d9 a9 bc ff 07 7e c4 59 6f 1d 0f 29 bf 9c ba a5 02 82 0f 73 2a f5 a9 3c 49 ee e3 3d 1c 4f 12 af 3b 42 97 af 91 fe 41 ea 9e 94 a2", + recipeConfig: [ + { + "op": "ChaCha", + "args": [ + {"option": "Hex", "string": "00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff ff ee dd cc bb aa 99 88 77 66 55 44 33 22 11 00"}, + {"option": "Hex", "string": "0f 1e 2d 3c 4b 5a 69 78"}, + 0, "12", "Hex", "Hex", + ] + } + ], + }, + { + name: "ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.6", + input: "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", + expectedOutput: "9f ad f4 09 c0 08 11 d0 04 31 d6 7e fb d8 8f ba 59 21 8d 5d 67 08 b1 d6 85 86 3f ab bb 0e 96 1e ea 48 0f d6 fb 53 2b fd 49 4b 21 51 01 50 57 42 3a b6 0a 63 fe 4f 55 f7 a2 12 e2 16 7c ca b9 31 fb fd 29 cf 7b c1 d2 79 ed df 25 dd 31 6b b8 84 3d 6e de e0 bd 1e f1 21 d1 2f a1 7c bc 2c 57 4c cc ab 5e 27 51 67 b0 8b d6 86 f8 a0 9d f8 7e c3 ff b3 53 61 b9 4e bf a1 3f ec 0e 48 89 d1 8d a5", + recipeConfig: [ + { + "op": "ChaCha", + "args": [ + {"option": "Hex", "string": "00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff ff ee dd cc bb aa 99 88 77 66 55 44 33 22 11 00"}, + {"option": "Hex", "string": "0f 1e 2d 3c 4b 5a 69 78"}, + 0, "20", "Hex", "Hex", + ] + } + ], + }, +]); From 39143fa6a1e2550c904d1d59cb865b18bcd5825d Mon Sep 17 00:00:00 2001 From: MikeCAT Date: Fri, 11 Nov 2022 22:26:41 +0900 Subject: [PATCH 20/74] add Shuffle operation --- src/core/config/Categories.json | 1 + src/core/operations/Shuffle.mjs | 157 +++++++++++++++++++++++++++++ tests/operations/index.mjs | 1 + tests/operations/tests/Shuffle.mjs | 92 +++++++++++++++++ 4 files changed, 251 insertions(+) create mode 100644 src/core/operations/Shuffle.mjs create mode 100644 tests/operations/tests/Shuffle.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 43d5dc4e..c629c085 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -249,6 +249,7 @@ "To Table", "Reverse", "Sort", + "Shuffle", "Unique", "Split", "Filter", diff --git a/src/core/operations/Shuffle.mjs b/src/core/operations/Shuffle.mjs new file mode 100644 index 00000000..99cbd072 --- /dev/null +++ b/src/core/operations/Shuffle.mjs @@ -0,0 +1,157 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * Shuffle operation + */ +class Shuffle extends Operation { + + /** + * Shuffle constructor + */ + constructor() { + super(); + + this.name = "Shuffle"; + this.module = "Default"; + this.description = "Randomly reorders input elements."; + this.infoURL = "https://wikipedia.org/wiki/Shuffling"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + "name": "By", + "type": "option", + "value": ["Byte", "Character", "Line"], + "defaultIndex": 1 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const type = args[0]; + if (input.byteLength === 0) return input; + if (ArrayBuffer.isView(input)) { + if (input.byteOffset === 0 && input.byteLength === input.buffer.byteLength) { + input = input.buffer; + } else { + input = input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength); + } + } + const inputBytes = new Uint8Array(input); + + // return a random number in [0, 1) + const rng = (typeof crypto) !== "undefined" && crypto.getRandomValues ? (function() { + const buf = new Uint32Array(2); + return function() { + // generate 53-bit random integer: 21 + 32 bits + crypto.getRandomValues(buf); + const value = (buf[0] >>> (32 - 21)) * ((1 << 30) * 4) + buf[1]; + return value / ((1 << 23) * (1 << 30)); + }; + })() : Math.random; + + // return a random integer in [0, max) + const randint = function(max) { + return Math.floor(rng() * max); + }; + + const toShuffle = []; + let addLastNewLine = false; + switch (type) { + case "Character": + // split input into UTF-8 code points + for (let i = 0; i < inputBytes.length;) { + const charLength = (function() { + if (inputBytes[i] < 0xc0) return 1; + if (inputBytes[i] < 0xe0) return 2; + if (inputBytes[i] < 0xf0) return 3; + if (inputBytes[i] < 0xf8) return 4; + return 1; + })(); + if (i + charLength <= inputBytes.length) { + let elementLength = charLength; + for (let j = 1; j < charLength; j++) { + if ((inputBytes[i + j] & 0xc0) !== 0x80) { + elementLength = 1; + break; + } + } + toShuffle.push([i, elementLength]); + i += elementLength; + } else { + toShuffle.push([i, 1]); + i++; + } + } + break; + case "Line": + { + // split input by newline characters + let lineBegin = 0; + for (let i = 0; i < inputBytes.length; i++) { + if (inputBytes[i] === 0xd || inputBytes[i] === 0xa) { + if (i + 1 < inputBytes.length && inputBytes[i] === 0xd && inputBytes[i + 1] === 0xa) { + i++; + } + toShuffle.push([lineBegin, i - lineBegin + 1]); + lineBegin = i + 1; + } + } + if (lineBegin < inputBytes.length) { + toShuffle.push([lineBegin, inputBytes.length - lineBegin]); + addLastNewLine = true; + } + } + break; + default: + { + // Creating element information for each bytes looks very wasteful. + // Therefore, directly shuffle here. + const outputBytes = new Uint8Array(inputBytes); + for (let i = outputBytes.length - 1; i > 0; i--) { + const idx = randint(i + 1); + const tmp = outputBytes[idx]; + outputBytes[idx] = outputBytes[i]; + outputBytes[i] = tmp; + } + return outputBytes.buffer; + } + } + + // shuffle elements + const lastStart = toShuffle[toShuffle.length - 1][0]; + for (let i = toShuffle.length - 1; i > 0; i--) { + const idx = randint(i + 1); + const tmp = toShuffle[idx]; + toShuffle[idx] = toShuffle[i]; + toShuffle[i] = tmp; + } + + // place shuffled elements + const outputBytes = new Uint8Array(inputBytes.length + (addLastNewLine ? 1 : 0)); + let outputPos = 0; + for (let i = 0; i < toShuffle.length; i++) { + outputBytes.set(new Uint8Array(input, toShuffle[i][0], toShuffle[i][1]), outputPos); + outputPos += toShuffle[i][1]; + if (addLastNewLine && toShuffle[i][0] === lastStart) { + outputBytes[outputPos] = 0xa; + outputPos++; + } + } + return outputBytes.buffer; + } + +} + +export default Shuffle; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 19e70970..2d9f7cf0 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -124,6 +124,7 @@ import "./tests/UnescapeString.mjs"; import "./tests/LS47.mjs"; import "./tests/LZString.mjs"; import "./tests/NTLM.mjs"; +import "./tests/Shuffle.mjs"; // Cannot test operations that use the File type yet // import "./tests/SplitColourChannels.mjs"; diff --git a/tests/operations/tests/Shuffle.mjs b/tests/operations/tests/Shuffle.mjs new file mode 100644 index 00000000..eadc615e --- /dev/null +++ b/tests/operations/tests/Shuffle.mjs @@ -0,0 +1,92 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + "name": "Shuffle empty", + "input": "", + "expectedOutput": "", + "recipeConfig": [ + { + "op": "Shuffle", + "args": ["Character"] + } + ] + }, + { + "name": "Shuffle bytes", + "input": "12345678", + "expectedOutput": "31 32 33 34 35 36 37 38", + "recipeConfig": [ + { + "op": "Shuffle", + "args": ["Byte"] + }, + { + "op": "To Hex", + "args": ["Space", 0] + }, + { + "op": "Sort", + "args": ["Space", false, "Alphabetical (case sensitive)"] + } + ] + }, + { + "name": "Shuffle characters", + "input": "1234\uff15\uff16\uff17\uff18", + "expectedOutput": " 0031 0032 0033 0034 FF15 FF16 FF17 FF18", + "recipeConfig": [ + { + "op": "Shuffle", + "args": ["Character"] + }, + { + "op": "Escape Unicode Characters", + "args": ["%u", true, 4, true] + }, + { + "op": "Split", + "args": ["%u", " "] + }, + { + "op": "Sort", + "args": ["Space", false, "Alphabetical (case sensitive)"] + } + ] + }, + { + "name": "Shuffle lines", + "input": "1\n2\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\ne\nf\n", + "expectedOutput": "\n1\n2\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\ne\nf", + "recipeConfig": [ + { + "op": "Shuffle", + "args": ["Line"] + }, + { + "op": "Sort", + "args": ["Line feed", false, "Alphabetical (case sensitive)"] + } + ] + }, + { + "name": "Shuffle lines (last character is not newline)", + "input": "1\n2\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\ne\nf", + "expectedOutput": "\n1\n2\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\ne\nf", + "recipeConfig": [ + { + "op": "Shuffle", + "args": ["Line"] + }, + { + "op": "Sort", + "args": ["Line feed", false, "Alphabetical (case sensitive)"] + } + ] + }, +]); From 31a7f83b82e78927f89689f323fcb9185144d6ff Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 11 Nov 2022 16:27:14 +0000 Subject: [PATCH 21/74] Added 'LZ4 Compress' and 'LZ4 Decompress' operations. Closes #1116 --- package-lock.json | 11 +++++++ package.json | 1 + src/core/config/Categories.json | 4 ++- src/core/operations/LZ4Compress.mjs | 43 +++++++++++++++++++++++++++ src/core/operations/LZ4Decompress.mjs | 43 +++++++++++++++++++++++++++ tests/operations/tests/Compress.mjs | 30 +++++++++++++++++++ 6 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 src/core/operations/LZ4Compress.mjs create mode 100644 src/core/operations/LZ4Decompress.mjs diff --git a/package-lock.json b/package-lock.json index cbb61cc9..300fc6a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,6 +58,7 @@ "loglevel": "^1.8.0", "loglevel-message-prefix": "^3.0.0", "lz-string": "^1.4.4", + "lz4js": "^0.2.0", "markdown-it": "^13.0.1", "moment": "^2.29.3", "moment-timezone": "^0.5.34", @@ -9608,6 +9609,11 @@ "lz-string": "bin/bin.js" } }, + "node_modules/lz4js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/lz4js/-/lz4js-0.2.0.tgz", + "integrity": "sha512-gY2Ia9Lm7Ep8qMiuGRhvUq0Q7qUereeldZPP1PMEJxPtEWHJLqw9pgX68oHajBH0nzJK4MaZEA/YNV3jT8u8Bg==" + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -21609,6 +21615,11 @@ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.4.4.tgz", "integrity": "sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ==" }, + "lz4js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/lz4js/-/lz4js-0.2.0.tgz", + "integrity": "sha512-gY2Ia9Lm7Ep8qMiuGRhvUq0Q7qUereeldZPP1PMEJxPtEWHJLqw9pgX68oHajBH0nzJK4MaZEA/YNV3jT8u8Bg==" + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", diff --git a/package.json b/package.json index 9f7ab0f7..3c34ebcc 100644 --- a/package.json +++ b/package.json @@ -134,6 +134,7 @@ "loglevel": "^1.8.0", "loglevel-message-prefix": "^3.0.0", "lz-string": "^1.4.4", + "lz4js": "^0.2.0", "markdown-it": "^13.0.1", "moment": "^2.29.3", "moment-timezone": "^0.5.34", diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 43d5dc4e..211eae64 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -333,7 +333,9 @@ "LZString Decompress", "LZString Compress", "LZMA Decompress", - "LZMA Compress" + "LZMA Compress", + "LZ4 Decompress", + "LZ4 Compress" ] }, { diff --git a/src/core/operations/LZ4Compress.mjs b/src/core/operations/LZ4Compress.mjs new file mode 100644 index 00000000..1f43785f --- /dev/null +++ b/src/core/operations/LZ4Compress.mjs @@ -0,0 +1,43 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import lz4 from "lz4js"; + +/** + * LZ4 Compress operation + */ +class LZ4Compress extends Operation { + + /** + * LZ4Compress constructor + */ + constructor() { + super(); + + this.name = "LZ4 Compress"; + this.module = "Compression"; + this.description = "LZ4 is a lossless data compression algorithm that is focused on compression and decompression speed. It belongs to the LZ77 family of byte-oriented compression schemes."; + this.infoURL = "https://wikipedia.org/wiki/LZ4_(compression_algorithm)"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const inBuf = new Uint8Array(input); + const compressed = lz4.compress(inBuf); + return compressed.buffer; + } + +} + +export default LZ4Compress; diff --git a/src/core/operations/LZ4Decompress.mjs b/src/core/operations/LZ4Decompress.mjs new file mode 100644 index 00000000..2ba50416 --- /dev/null +++ b/src/core/operations/LZ4Decompress.mjs @@ -0,0 +1,43 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import lz4 from "lz4js"; + +/** + * LZ4 Decompress operation + */ +class LZ4Decompress extends Operation { + + /** + * LZ4Decompress constructor + */ + constructor() { + super(); + + this.name = "LZ4 Decompress"; + this.module = "Compression"; + this.description = "LZ4 is a lossless data compression algorithm that is focused on compression and decompression speed. It belongs to the LZ77 family of byte-oriented compression schemes."; + this.infoURL = "https://wikipedia.org/wiki/LZ4_(compression_algorithm)"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const inBuf = new Uint8Array(input); + const decompressed = lz4.decompress(inBuf); + return decompressed.buffer; + } + +} + +export default LZ4Decompress; diff --git a/tests/operations/tests/Compress.mjs b/tests/operations/tests/Compress.mjs index 015277b1..60117c67 100644 --- a/tests/operations/tests/Compress.mjs +++ b/tests/operations/tests/Compress.mjs @@ -75,4 +75,34 @@ TestRegister.addTests([ } ], }, + { + name: "LZ4 Compress", + input: "The cat sat on the mat.", + expectedOutput: "04224d184070df170000805468652063617420736174206f6e20746865206d61742e00000000", + recipeConfig: [ + { + "op": "LZ4 Compress", + "args": [] + }, + { + "op": "To Hex", + "args": ["None", 0] + } + ], + }, + { + name: "LZ4 Decompress", + input: "04224d184070df170000805468652063617420736174206f6e20746865206d61742e00000000", + expectedOutput: "The cat sat on the mat.", + recipeConfig: [ + { + "op": "From Hex", + "args": ["None"] + }, + { + "op": "LZ4 Decompress", + "args": [] + } + ], + }, ]); From 6c5433b22695f8a531db2379eb8b405869d50b37 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 11 Nov 2022 16:29:03 +0000 Subject: [PATCH 22/74] Updated CHANGELOG --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4d21a41..37f7297f 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.49.0] - 2022-11-11 +- Added 'LZ4 Compress' and 'LZ4 Decompress' operations [@n1474335] | [31a7f83] + ### [9.48.0] - 2022-10-14 - Added 'LM Hash' and 'NT Hash' operations [@n1474335] [@brun0ne] | [#1427] @@ -321,6 +324,7 @@ All major and minor version changes will be documented in this file. Details of +[9.49.0]: https://github.com/gchq/CyberChef/releases/tag/v9.49.0 [9.48.0]: https://github.com/gchq/CyberChef/releases/tag/v9.48.0 [9.47.0]: https://github.com/gchq/CyberChef/releases/tag/v9.47.0 [9.46.0]: https://github.com/gchq/CyberChef/releases/tag/v9.46.0 @@ -466,6 +470,7 @@ All major and minor version changes will be documented in this file. Details of [e9ca4dc]: https://github.com/gchq/CyberChef/commit/e9ca4dc9caf98f33fd986431cd400c88082a42b8 [dd18e52]: https://github.com/gchq/CyberChef/commit/dd18e529939078b89867297b181a584e8b2cc7da [a895d1d]: https://github.com/gchq/CyberChef/commit/a895d1d82a2f92d440a0c5eca2bc7c898107b737 +[31a7f83]: https://github.com/gchq/CyberChef/commit/31a7f83b82e78927f89689f323fcb9185144d6ff [#95]: https://github.com/gchq/CyberChef/pull/299 [#173]: https://github.com/gchq/CyberChef/pull/173 From 72889d1c20e722b4775de5b8ba45e901eab7f57e Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 11 Nov 2022 16:29:13 +0000 Subject: [PATCH 23/74] 9.49.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 300fc6a2..487b3b27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cyberchef", - "version": "9.48.0", + "version": "9.49.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "9.48.0", + "version": "9.49.0", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 3c34ebcc..d993047c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "9.48.0", + "version": "9.49.0", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", From 9c3ddca2694a75c6cae734372feff833e0085328 Mon Sep 17 00:00:00 2001 From: Samuele Facenda Date: Sun, 13 Nov 2022 14:37:19 +0100 Subject: [PATCH 24/74] Added ignoreCase feature in Substitute operation. --- src/core/operations/Substitute.mjs | 58 +++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/src/core/operations/Substitute.mjs b/src/core/operations/Substitute.mjs index 1afe1f42..1dac1c53 100644 --- a/src/core/operations/Substitute.mjs +++ b/src/core/operations/Substitute.mjs @@ -34,10 +34,49 @@ class Substitute extends Operation { "name": "Ciphertext", "type": "binaryString", "value": "XYZABCDEFGHIJKLMNOPQRSTUVW" + }, + { + "name": "Ignore case", + "type": "boolean", + "value": false } ]; } + /** + * Convert a single character using the dictionary, if ignoreCase is true then + * check in the dictionary for both upper and lower case versions of the character. + * In output the input character case is preserved. + * @param {string} char + * @param {Object} dict + * @param {boolean} ignoreCase + * @returns {string} + */ + cipherSingleChar(char, dict, ignoreCase) { + if (!ignoreCase) + return dict[char] || char; + + const isUpperCase = char === char.toUpperCase(); + + // convert using the dictionary keeping the case of the input character + + if (dict[char] !== undefined) + // if the character is in the dictionary return the value with the input case + return isUpperCase ? dict[char].toUpperCase() : dict[char].toLowerCase(); + + // check for the other case, if it is in the dictionary return the value with the right case + if (isUpperCase) { + if (dict[char.toLowerCase()] !== undefined) + return dict[char.toLowerCase()].toUpperCase(); + } else { + if(dict[char.toUpperCase()] !== undefined) + return dict[char.toUpperCase()].toLowerCase(); + } + + return char; + } + + /** * @param {string} input * @param {Object[]} args @@ -46,17 +85,20 @@ class Substitute extends Operation { run(input, args) { const plaintext = Utils.expandAlphRange([...args[0]]), ciphertext = Utils.expandAlphRange([...args[1]]); - let output = "", - index = -1; + let output = ""; + const ignoreCase = args[2]; - if (plaintext.length !== ciphertext.length) { + if (plaintext.length !== ciphertext.length) output = "Warning: Plaintext and Ciphertext lengths differ\n\n"; - } - for (const character of input) { - index = plaintext.indexOf(character); - output += index > -1 && index < ciphertext.length ? ciphertext[index] : character; - } + // create dictionary for conversion + const dict = {}; + for (let i = 0; i < Math.min(ciphertext.length, plaintext.length); i++) + dict[plaintext[i]] = ciphertext[i]; + + // map every letter with the conversion function + for (const character of input) + output += this.cipherSingleChar(character, dict, ignoreCase); return output; } From 1a9833132d32170dde9b289131507d2176a94eb4 Mon Sep 17 00:00:00 2001 From: Samuele Facenda Date: Sun, 13 Nov 2022 14:41:01 +0100 Subject: [PATCH 25/74] Added ignoreCase feature in Substitute operation. --- src/core/operations/Substitute.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/operations/Substitute.mjs b/src/core/operations/Substitute.mjs index 1dac1c53..a8bf2291 100644 --- a/src/core/operations/Substitute.mjs +++ b/src/core/operations/Substitute.mjs @@ -69,7 +69,7 @@ class Substitute extends Operation { if (dict[char.toLowerCase()] !== undefined) return dict[char.toLowerCase()].toUpperCase(); } else { - if(dict[char.toUpperCase()] !== undefined) + if (dict[char.toUpperCase()] !== undefined) return dict[char.toUpperCase()].toLowerCase(); } From c04f409d2346ffe4f3fee41b57f0ff0323244643 Mon Sep 17 00:00:00 2001 From: MikeCAT Date: Thu, 17 Nov 2022 20:24:54 +0900 Subject: [PATCH 26/74] PseudoRandomNumberGenerator: support larger output than 65536 bytes --- src/core/operations/PseudoRandomNumberGenerator.mjs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/core/operations/PseudoRandomNumberGenerator.mjs b/src/core/operations/PseudoRandomNumberGenerator.mjs index 033aa859..53150566 100644 --- a/src/core/operations/PseudoRandomNumberGenerator.mjs +++ b/src/core/operations/PseudoRandomNumberGenerator.mjs @@ -52,8 +52,12 @@ class PseudoRandomNumberGenerator extends Operation { let bytes; if (isWorkerEnvironment() && self.crypto) { - bytes = self.crypto.getRandomValues(new Uint8Array(numBytes)); - bytes = Utils.arrayBufferToStr(bytes.buffer); + bytes = new ArrayBuffer(numBytes); + const CHUNK_SIZE = 65536; + for (let i = 0; i < numBytes; i += CHUNK_SIZE) { + self.crypto.getRandomValues(new Uint8Array(bytes, i, Math.min(numBytes - i, CHUNK_SIZE))); + } + bytes = Utils.arrayBufferToStr(bytes); } else { bytes = forge.random.getBytesSync(numBytes); } From 66cbc6908a4c0009a01670be78962f4eb3a76090 Mon Sep 17 00:00:00 2001 From: TheSavageTeddy <51810476+TheSavageTeddy@users.noreply.github.com> Date: Fri, 25 Nov 2022 10:36:08 +0800 Subject: [PATCH 27/74] Better delimeter parsing for From Base85 --- src/core/operations/FromBase85.mjs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/core/operations/FromBase85.mjs b/src/core/operations/FromBase85.mjs index f9b37c74..0ffae967 100644 --- a/src/core/operations/FromBase85.mjs +++ b/src/core/operations/FromBase85.mjs @@ -91,8 +91,11 @@ class FromBase85 extends Operation { // Remove non-alphabet characters if (removeNonAlphChars) { - const re = new RegExp("[^" + alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g"); + const re = new RegExp("[^" + "z~" +alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g"); input = input.replace(re, ""); + // Remove delimiters again if present (incase of non-alphabet characters in front/behind delimiters) + const matches = input.match(/^<~(.+?)~>$/); + if (matches !== null) input = matches[1]; } if (input.length === 0) return []; @@ -111,7 +114,11 @@ class FromBase85 extends Operation { .map((chr, idx) => { const digit = alphabet.indexOf(chr); if (digit < 0 || digit > 84) { - throw `Invalid character '${chr}' at index ${i + idx}`; + if (chr === "z"){ + // Pass (Ignore character) + }else{ + throw `Invalid character '${chr}' at index ${i + idx}`; + } } return digit; }); From c79bf5caafe4fc802ff2961892d4a5184e1ba01f Mon Sep 17 00:00:00 2001 From: TheSavageTeddy <51810476+TheSavageTeddy@users.noreply.github.com> Date: Fri, 25 Nov 2022 10:49:56 +0800 Subject: [PATCH 28/74] fixed linting --- src/core/operations/FromBase85.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/operations/FromBase85.mjs b/src/core/operations/FromBase85.mjs index 0ffae967..3904479d 100644 --- a/src/core/operations/FromBase85.mjs +++ b/src/core/operations/FromBase85.mjs @@ -114,9 +114,9 @@ class FromBase85 extends Operation { .map((chr, idx) => { const digit = alphabet.indexOf(chr); if (digit < 0 || digit > 84) { - if (chr === "z"){ + if (chr === "z") { // Pass (Ignore character) - }else{ + } else { throw `Invalid character '${chr}' at index ${i + idx}`; } } From 78d35ecec3760c8f4f35b9a0be076b8a43780af3 Mon Sep 17 00:00:00 2001 From: TheSavageTeddy <51810476+TheSavageTeddy@users.noreply.github.com> Date: Fri, 25 Nov 2022 10:59:04 +0800 Subject: [PATCH 29/74] better boolean statement --- src/core/operations/FromBase85.mjs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/core/operations/FromBase85.mjs b/src/core/operations/FromBase85.mjs index 3904479d..aaa1dc94 100644 --- a/src/core/operations/FromBase85.mjs +++ b/src/core/operations/FromBase85.mjs @@ -113,12 +113,8 @@ class FromBase85 extends Operation { .split("") .map((chr, idx) => { const digit = alphabet.indexOf(chr); - if (digit < 0 || digit > 84) { - if (chr === "z") { - // Pass (Ignore character) - } else { + if ((digit < 0 || digit > 84) && chr !== "z") { throw `Invalid character '${chr}' at index ${i + idx}`; - } } return digit; }); From d77f8ba747020aadf9f9a29a31b78eaea65c7feb Mon Sep 17 00:00:00 2001 From: TheSavageTeddy <51810476+TheSavageTeddy@users.noreply.github.com> Date: Fri, 25 Nov 2022 11:08:01 +0800 Subject: [PATCH 30/74] fix linting again --- src/core/operations/FromBase85.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/operations/FromBase85.mjs b/src/core/operations/FromBase85.mjs index aaa1dc94..afbd6af3 100644 --- a/src/core/operations/FromBase85.mjs +++ b/src/core/operations/FromBase85.mjs @@ -114,7 +114,7 @@ class FromBase85 extends Operation { .map((chr, idx) => { const digit = alphabet.indexOf(chr); if ((digit < 0 || digit > 84) && chr !== "z") { - throw `Invalid character '${chr}' at index ${i + idx}`; + throw `Invalid character '${chr}' at index ${i + idx}`; } return digit; }); From 0658836f87bf4ecf6f207d5413ba3ce5ef807869 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 11:15:16 +0000 Subject: [PATCH 31/74] 9.49.1 --- 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 487b3b27..c35598b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cyberchef", - "version": "9.49.0", + "version": "9.49.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "9.49.0", + "version": "9.49.1", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index d993047c..ae646440 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "9.49.0", + "version": "9.49.1", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", From d7561ec208084dff2993474a16581747456e49b6 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 11:23:32 +0000 Subject: [PATCH 32/74] Tidied Substitute --- src/core/operations/Substitute.mjs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/core/operations/Substitute.mjs b/src/core/operations/Substitute.mjs index a8bf2291..4a8cdc5d 100644 --- a/src/core/operations/Substitute.mjs +++ b/src/core/operations/Substitute.mjs @@ -60,9 +60,10 @@ class Substitute extends Operation { // convert using the dictionary keeping the case of the input character - if (dict[char] !== undefined) + if (dict[char] !== undefined) { // if the character is in the dictionary return the value with the input case return isUpperCase ? dict[char].toUpperCase() : dict[char].toLowerCase(); + } // check for the other case, if it is in the dictionary return the value with the right case if (isUpperCase) { @@ -84,21 +85,24 @@ class Substitute extends Operation { */ run(input, args) { const plaintext = Utils.expandAlphRange([...args[0]]), - ciphertext = Utils.expandAlphRange([...args[1]]); + ciphertext = Utils.expandAlphRange([...args[1]]), + ignoreCase = args[2]; let output = ""; - const ignoreCase = args[2]; - if (plaintext.length !== ciphertext.length) + if (plaintext.length !== ciphertext.length) { output = "Warning: Plaintext and Ciphertext lengths differ\n\n"; + } // create dictionary for conversion const dict = {}; - for (let i = 0; i < Math.min(ciphertext.length, plaintext.length); i++) + for (let i = 0; i < Math.min(ciphertext.length, plaintext.length); i++) { dict[plaintext[i]] = ciphertext[i]; + } // map every letter with the conversion function - for (const character of input) + for (const character of input) { output += this.cipherSingleChar(character, dict, ignoreCase); + } return output; } From 9fa82150ee80836bb5a4f8b870daf55438359713 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 11:23:38 +0000 Subject: [PATCH 33/74] 9.49.2 --- 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 c35598b2..cbf7c6e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cyberchef", - "version": "9.49.1", + "version": "9.49.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "9.49.1", + "version": "9.49.2", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index ae646440..09fb5303 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "9.49.1", + "version": "9.49.2", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", From 59fe8d1c4b533e3e80fa318b4672f7e76e353eb2 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 11:50:27 +0000 Subject: [PATCH 34/74] Simplified 'Shuffle' operation to work in the same way as 'Sort' and 'Unique' --- src/core/operations/Shuffle.mjs | 107 ++++------------------------- tests/operations/tests/Shuffle.mjs | 46 ++----------- 2 files changed, 18 insertions(+), 135 deletions(-) diff --git a/src/core/operations/Shuffle.mjs b/src/core/operations/Shuffle.mjs index 99cbd072..14c4ffab 100644 --- a/src/core/operations/Shuffle.mjs +++ b/src/core/operations/Shuffle.mjs @@ -5,6 +5,8 @@ */ import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs"; /** * Shuffle operation @@ -21,34 +23,25 @@ class Shuffle extends Operation { this.module = "Default"; this.description = "Randomly reorders input elements."; this.infoURL = "https://wikipedia.org/wiki/Shuffling"; - this.inputType = "ArrayBuffer"; - this.outputType = "ArrayBuffer"; + this.inputType = "string"; + this.outputType = "string"; this.args = [ { - "name": "By", - "type": "option", - "value": ["Byte", "Character", "Line"], - "defaultIndex": 1 + name: "Delimiter", + type: "option", + value: INPUT_DELIM_OPTIONS } ]; } /** - * @param {ArrayBuffer} input + * @param {string} input * @param {Object[]} args - * @returns {ArrayBuffer} + * @returns {string} */ run(input, args) { - const type = args[0]; - if (input.byteLength === 0) return input; - if (ArrayBuffer.isView(input)) { - if (input.byteOffset === 0 && input.byteLength === input.buffer.byteLength) { - input = input.buffer; - } else { - input = input.buffer.slice(input.byteOffset, input.byteOffset + input.byteLength); - } - } - const inputBytes = new Uint8Array(input); + const delim = Utils.charRep(args[0]); + if (input.length === 0) return input; // return a random number in [0, 1) const rng = (typeof crypto) !== "undefined" && crypto.getRandomValues ? (function() { @@ -66,71 +59,10 @@ class Shuffle extends Operation { return Math.floor(rng() * max); }; - const toShuffle = []; - let addLastNewLine = false; - switch (type) { - case "Character": - // split input into UTF-8 code points - for (let i = 0; i < inputBytes.length;) { - const charLength = (function() { - if (inputBytes[i] < 0xc0) return 1; - if (inputBytes[i] < 0xe0) return 2; - if (inputBytes[i] < 0xf0) return 3; - if (inputBytes[i] < 0xf8) return 4; - return 1; - })(); - if (i + charLength <= inputBytes.length) { - let elementLength = charLength; - for (let j = 1; j < charLength; j++) { - if ((inputBytes[i + j] & 0xc0) !== 0x80) { - elementLength = 1; - break; - } - } - toShuffle.push([i, elementLength]); - i += elementLength; - } else { - toShuffle.push([i, 1]); - i++; - } - } - break; - case "Line": - { - // split input by newline characters - let lineBegin = 0; - for (let i = 0; i < inputBytes.length; i++) { - if (inputBytes[i] === 0xd || inputBytes[i] === 0xa) { - if (i + 1 < inputBytes.length && inputBytes[i] === 0xd && inputBytes[i + 1] === 0xa) { - i++; - } - toShuffle.push([lineBegin, i - lineBegin + 1]); - lineBegin = i + 1; - } - } - if (lineBegin < inputBytes.length) { - toShuffle.push([lineBegin, inputBytes.length - lineBegin]); - addLastNewLine = true; - } - } - break; - default: - { - // Creating element information for each bytes looks very wasteful. - // Therefore, directly shuffle here. - const outputBytes = new Uint8Array(inputBytes); - for (let i = outputBytes.length - 1; i > 0; i--) { - const idx = randint(i + 1); - const tmp = outputBytes[idx]; - outputBytes[idx] = outputBytes[i]; - outputBytes[i] = tmp; - } - return outputBytes.buffer; - } - } + // Split input into shuffleable sections + const toShuffle = input.split(delim); // shuffle elements - const lastStart = toShuffle[toShuffle.length - 1][0]; for (let i = toShuffle.length - 1; i > 0; i--) { const idx = randint(i + 1); const tmp = toShuffle[idx]; @@ -138,18 +70,7 @@ class Shuffle extends Operation { toShuffle[i] = tmp; } - // place shuffled elements - const outputBytes = new Uint8Array(inputBytes.length + (addLastNewLine ? 1 : 0)); - let outputPos = 0; - for (let i = 0; i < toShuffle.length; i++) { - outputBytes.set(new Uint8Array(input, toShuffle[i][0], toShuffle[i][1]), outputPos); - outputPos += toShuffle[i][1]; - if (addLastNewLine && toShuffle[i][0] === lastStart) { - outputBytes[outputPos] = 0xa; - outputPos++; - } - } - return outputBytes.buffer; + return toShuffle.join(delim); } } diff --git a/tests/operations/tests/Shuffle.mjs b/tests/operations/tests/Shuffle.mjs index eadc615e..21c878f1 100644 --- a/tests/operations/tests/Shuffle.mjs +++ b/tests/operations/tests/Shuffle.mjs @@ -13,7 +13,7 @@ TestRegister.addTests([ "recipeConfig": [ { "op": "Shuffle", - "args": ["Character"] + "args": ["Comma"] } ] }, @@ -24,7 +24,7 @@ TestRegister.addTests([ "recipeConfig": [ { "op": "Shuffle", - "args": ["Byte"] + "args": ["Nothing (separate chars)"] }, { "op": "To Hex", @@ -36,29 +36,6 @@ TestRegister.addTests([ } ] }, - { - "name": "Shuffle characters", - "input": "1234\uff15\uff16\uff17\uff18", - "expectedOutput": " 0031 0032 0033 0034 FF15 FF16 FF17 FF18", - "recipeConfig": [ - { - "op": "Shuffle", - "args": ["Character"] - }, - { - "op": "Escape Unicode Characters", - "args": ["%u", true, 4, true] - }, - { - "op": "Split", - "args": ["%u", " "] - }, - { - "op": "Sort", - "args": ["Space", false, "Alphabetical (case sensitive)"] - } - ] - }, { "name": "Shuffle lines", "input": "1\n2\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\ne\nf\n", @@ -66,27 +43,12 @@ TestRegister.addTests([ "recipeConfig": [ { "op": "Shuffle", - "args": ["Line"] + "args": ["Line feed"] }, { "op": "Sort", "args": ["Line feed", false, "Alphabetical (case sensitive)"] } ] - }, - { - "name": "Shuffle lines (last character is not newline)", - "input": "1\n2\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\ne\nf", - "expectedOutput": "\n1\n2\n3\n4\n5\n6\n7\n8\n9\na\nb\nc\nd\ne\nf", - "recipeConfig": [ - { - "op": "Shuffle", - "args": ["Line"] - }, - { - "op": "Sort", - "args": ["Line feed", false, "Alphabetical (case sensitive)"] - } - ] - }, + } ]); From 2b02c44ca4636559d727e3a69fc6eb6645b7e660 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 11:51:22 +0000 Subject: [PATCH 35/74] Updated CHANGELOG --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 37f7297f..4d5c3c19 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.50.0] - 2022-11-25 +- Added 'Shuffle' operation [@mikecat] | [#1472] + ### [9.49.0] - 2022-11-11 - Added 'LZ4 Compress' and 'LZ4 Decompress' operations [@n1474335] | [31a7f83] @@ -324,6 +327,7 @@ All major and minor version changes will be documented in this file. Details of +[9.50.0]: https://github.com/gchq/CyberChef/releases/tag/v9.50.0 [9.49.0]: https://github.com/gchq/CyberChef/releases/tag/v9.49.0 [9.48.0]: https://github.com/gchq/CyberChef/releases/tag/v9.48.0 [9.47.0]: https://github.com/gchq/CyberChef/releases/tag/v9.47.0 @@ -568,4 +572,5 @@ All major and minor version changes will be documented in this file. Details of [#1308]: https://github.com/gchq/CyberChef/pull/1308 [#1421]: https://github.com/gchq/CyberChef/pull/1421 [#1427]: https://github.com/gchq/CyberChef/pull/1427 +[#1472]: https://github.com/gchq/CyberChef/pull/1472 From bc27cd27727d35d3c4798c1e46b9dc2ad5776118 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 11:51:34 +0000 Subject: [PATCH 36/74] 9.50.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 cbf7c6e2..dfca428e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cyberchef", - "version": "9.49.2", + "version": "9.50.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "9.49.2", + "version": "9.50.0", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 09fb5303..75baa382 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "9.49.2", + "version": "9.50.0", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", From c6935e040d95f42ab41c2d9ad5e811c8dd35db49 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 11:55:48 +0000 Subject: [PATCH 37/74] Updated newMinorVersion script --- src/core/config/scripts/newMinorVersion.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/config/scripts/newMinorVersion.mjs b/src/core/config/scripts/newMinorVersion.mjs index adfdf6f7..67754890 100644 --- a/src/core/config/scripts/newMinorVersion.mjs +++ b/src/core/config/scripts/newMinorVersion.mjs @@ -136,7 +136,7 @@ const getFeature = function() { fs.writeFileSync(path.join(process.cwd(), "CHANGELOG.md"), changelogData); - console.log("Written CHANGELOG.md"); + console.log("Written CHANGELOG.md\nCommit changes and then run `npm version minor`."); } }); }; From c1368c4ecba07cf2cc5eeb56ffcf540c2a320298 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 11:55:53 +0000 Subject: [PATCH 38/74] 9.50.1 --- 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 dfca428e..cf13395d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cyberchef", - "version": "9.50.0", + "version": "9.50.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "9.50.0", + "version": "9.50.1", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 75baa382..d4b159ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "9.50.0", + "version": "9.50.1", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", From 59d8be511a348a2dbb976578c753e696319f3fe4 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 12:00:32 +0000 Subject: [PATCH 39/74] 9.50.2 --- 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 cf13395d..4a9d2b99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cyberchef", - "version": "9.50.1", + "version": "9.50.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "9.50.1", + "version": "9.50.2", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index d4b159ef..68b87da1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "9.50.1", + "version": "9.50.2", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", From 55a7981547613fed45e845d64d5a0b9d2a0b8ced Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 12:03:57 +0000 Subject: [PATCH 40/74] 9.50.3 --- 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 4a9d2b99..bf504fb2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cyberchef", - "version": "9.50.2", + "version": "9.50.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "9.50.2", + "version": "9.50.3", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 68b87da1..5a2b7f04 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "9.50.2", + "version": "9.50.3", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", From 9ccc1613cf9d63200f516d45e982ca1144e7e29e Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 12:05:32 +0000 Subject: [PATCH 41/74] 9.50.4 --- 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 bf504fb2..933c8368 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cyberchef", - "version": "9.50.3", + "version": "9.50.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "9.50.3", + "version": "9.50.4", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 5a2b7f04..d9c6108e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "9.50.3", + "version": "9.50.4", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", From 17cf154bc226865f10f3c94ff37799718ab2daf3 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 12:07:07 +0000 Subject: [PATCH 42/74] 9.50.5 --- 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 933c8368..862afebe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cyberchef", - "version": "9.50.4", + "version": "9.50.5", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "9.50.4", + "version": "9.50.5", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index d9c6108e..898dd0d1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "9.50.4", + "version": "9.50.5", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", From 51229d85cbae6da012922026299c9b93e57fcb2b Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 12:11:17 +0000 Subject: [PATCH 43/74] 9.50.6 --- 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 862afebe..6b16e0ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cyberchef", - "version": "9.50.5", + "version": "9.50.6", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "9.50.5", + "version": "9.50.6", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 898dd0d1..dcd69f10 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "9.50.5", + "version": "9.50.6", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", From d5f496866487d291be21e47e718e8d8210001fd2 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 12:18:33 +0000 Subject: [PATCH 44/74] 9.50.7 --- 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 6b16e0ab..da82e397 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cyberchef", - "version": "9.50.6", + "version": "9.50.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "9.50.6", + "version": "9.50.7", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index dcd69f10..fb0f4e15 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "9.50.6", + "version": "9.50.7", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", From 7589361e583727bbb73fa0046bec0eced8090b20 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 12:19:31 +0000 Subject: [PATCH 45/74] 9.50.8 --- 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 da82e397..2e3182ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cyberchef", - "version": "9.50.7", + "version": "9.50.8", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "9.50.7", + "version": "9.50.8", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index fb0f4e15..2b727c35 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "9.50.7", + "version": "9.50.8", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", From 9321b79d81f57f45ddf95a0d0afefc52babb0ef4 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 12:21:51 +0000 Subject: [PATCH 46/74] 9.50.9 --- 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 2e3182ed..0b3d5acb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cyberchef", - "version": "9.50.8", + "version": "9.50.9", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "9.50.8", + "version": "9.50.9", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 2b727c35..4231950f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "9.50.8", + "version": "9.50.9", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", From c25cf44d92f94bf3e57f2816444c26dd1489dc91 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 12:25:15 +0000 Subject: [PATCH 47/74] 9.50.10 --- 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 0b3d5acb..5fdab2b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cyberchef", - "version": "9.50.9", + "version": "9.50.10", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "9.50.9", + "version": "9.50.10", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 4231950f..860a4f62 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "9.50.9", + "version": "9.50.10", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", From 468071ee98e05ae259d2e199dbbed2fd56baf632 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 12:28:26 +0000 Subject: [PATCH 48/74] Tidied JSDoc comment --- src/core/lib/Sort.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/lib/Sort.mjs b/src/core/lib/Sort.mjs index adb30d71..c8998f61 100644 --- a/src/core/lib/Sort.mjs +++ b/src/core/lib/Sort.mjs @@ -105,7 +105,7 @@ export function hexadecimalSort(a, b) { } /** - * Comparison operation for sorting of lines by length + * Comparison operation for sorting by length * * @param {string} a * @param {string} b From 1f57f1f000e5e37f15f03210aaf27df6bbd6600a Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 12:28:33 +0000 Subject: [PATCH 49/74] 9.50.11 --- 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 5fdab2b0..4e264ca3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cyberchef", - "version": "9.50.10", + "version": "9.50.11", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "9.50.10", + "version": "9.50.11", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 860a4f62..59f02769 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "9.50.10", + "version": "9.50.11", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", From a19aea15162fca4cf68c27a93f8589312c434865 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 12:38:03 +0000 Subject: [PATCH 50/74] 9.50.12 --- 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 4e264ca3..976db25d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cyberchef", - "version": "9.50.11", + "version": "9.50.12", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "9.50.11", + "version": "9.50.12", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 59f02769..af43b84d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "9.50.11", + "version": "9.50.12", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", From 130e9d819215e9e0d9f5a81bbacd7a2102dca273 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 12:46:15 +0000 Subject: [PATCH 51/74] Lint --- src/core/operations/CMAC.mjs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/core/operations/CMAC.mjs b/src/core/operations/CMAC.mjs index 12c4080c..d6491362 100644 --- a/src/core/operations/CMAC.mjs +++ b/src/core/operations/CMAC.mjs @@ -49,11 +49,13 @@ class CMAC extends Operation { */ run(input, args) { const key = Utils.convertToByteString(args[0].string, args[0].option); + const algo = args[1]; + const info = (function() { - switch (args[1]) { + switch (algo) { case "AES": if (key.length !== 16 && key.length !== 24 && key.length !== 32) { - throw new OperationError("the key for AES must be either 16, 24, or 32 bytes (currently " + key.length + " bytes)"); + throw new OperationError("The key for AES must be either 16, 24, or 32 bytes (currently " + key.length + " bytes)"); } return { "algorithm": "AES-ECB", @@ -63,7 +65,7 @@ class CMAC extends Operation { }; case "Triple DES": if (key.length !== 16 && key.length !== 24) { - throw new OperationError("the key for Triple DES must be 16 or 24 bytes (currently " + key.length + " bytes)"); + throw new OperationError("The key for Triple DES must be 16 or 24 bytes (currently " + key.length + " bytes)"); } return { "algorithm": "3DES-ECB", @@ -72,9 +74,10 @@ class CMAC extends Operation { "Rb": new Uint8Array([0, 0, 0, 0, 0, 0, 0, 0x1b]), }; default: - throw new OperationError("undefined encryption algorithm"); + throw new OperationError("Undefined encryption algorithm"); } })(); + const xor = function(a, b, out) { if (!out) out = new Uint8Array(a.length); for (let i = 0; i < a.length; i++) { @@ -82,6 +85,7 @@ class CMAC extends Operation { } return out; }; + const leftShift1 = function(a) { const out = new Uint8Array(a.length); let carry = 0; @@ -91,6 +95,7 @@ class CMAC extends Operation { } return out; }; + const cipher = forge.cipher.createCipher(info.algorithm, info.key); const encrypt = function(a, out) { if (!out) out = new Uint8Array(a.length); @@ -127,6 +132,7 @@ class CMAC extends Operation { return xor(data, K2, data); } })(); + const X = new Uint8Array(info.blockSize); const Y = new Uint8Array(info.blockSize); for (let i = 0; i < n - 1; i++) { From 74d629c4910f985b4904cca4edfb9fd0aa97fcba Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 12:47:12 +0000 Subject: [PATCH 52/74] Updated CHANGELOG --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d5c3c19..2df01028 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.51.0] - 2022-11-25 +- Added 'CMAC' operation [@mikecat] | [#1457] + ### [9.50.0] - 2022-11-25 - Added 'Shuffle' operation [@mikecat] | [#1472] @@ -327,6 +330,7 @@ All major and minor version changes will be documented in this file. Details of +[9.51.0]: https://github.com/gchq/CyberChef/releases/tag/v9.51.0 [9.50.0]: https://github.com/gchq/CyberChef/releases/tag/v9.50.0 [9.49.0]: https://github.com/gchq/CyberChef/releases/tag/v9.49.0 [9.48.0]: https://github.com/gchq/CyberChef/releases/tag/v9.48.0 @@ -573,4 +577,5 @@ All major and minor version changes will be documented in this file. Details of [#1421]: https://github.com/gchq/CyberChef/pull/1421 [#1427]: https://github.com/gchq/CyberChef/pull/1427 [#1472]: https://github.com/gchq/CyberChef/pull/1472 +[#1457]: https://github.com/gchq/CyberChef/pull/1457 From 657f8940fa34f83e78b25b7718812d23d24f2be7 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 12:47:20 +0000 Subject: [PATCH 53/74] 9.51.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 976db25d..e769d8ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cyberchef", - "version": "9.50.12", + "version": "9.51.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "9.50.12", + "version": "9.51.0", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index af43b84d..9280d342 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "9.50.12", + "version": "9.51.0", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", From bf4e62b4b74133bf51a8aeb33d81a5e45117a3fe Mon Sep 17 00:00:00 2001 From: TheSavageTeddy <51810476+TheSavageTeddy@users.noreply.github.com> Date: Fri, 25 Nov 2022 21:36:17 +0800 Subject: [PATCH 54/74] added choice for base85 all-zero character --- src/core/operations/FromBase85.mjs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/core/operations/FromBase85.mjs b/src/core/operations/FromBase85.mjs index afbd6af3..aef16638 100644 --- a/src/core/operations/FromBase85.mjs +++ b/src/core/operations/FromBase85.mjs @@ -37,6 +37,11 @@ class FromBase85 extends Operation { type: "boolean", value: true }, + { + name: "All-zero group char", + type: "binaryShortString", + value: "z" + }, ]; this.checks = [ { @@ -78,6 +83,7 @@ class FromBase85 extends Operation { const alphabet = Utils.expandAlphRange(args[0]).join(""), encoding = alphabetName(alphabet), removeNonAlphChars = args[1], + allZeroGroupChar = args[2], result = []; if (alphabet.length !== 85 || @@ -91,7 +97,7 @@ class FromBase85 extends Operation { // Remove non-alphabet characters if (removeNonAlphChars) { - const re = new RegExp("[^" + "z~" +alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g"); + const re = new RegExp("[^~" + allZeroGroupChar +alphabet.replace(/[[\]\\\-^$]/g, "\\$&") + "]", "g"); input = input.replace(re, ""); // Remove delimiters again if present (incase of non-alphabet characters in front/behind delimiters) const matches = input.match(/^<~(.+?)~>$/); @@ -103,7 +109,7 @@ class FromBase85 extends Operation { let i = 0; let block, blockBytes; while (i < input.length) { - if (encoding === "Standard" && input[i] === "z") { + if (encoding === "Standard" && input[i] === allZeroGroupChar) { result.push(0, 0, 0, 0); i++; } else { @@ -113,7 +119,7 @@ class FromBase85 extends Operation { .split("") .map((chr, idx) => { const digit = alphabet.indexOf(chr); - if ((digit < 0 || digit > 84) && chr !== "z") { + if ((digit < 0 || digit > 84) && chr !== allZeroGroupChar) { throw `Invalid character '${chr}' at index ${i + idx}`; } return digit; From ba0dfe91c3f50868e8477973ed545d6575b48bd6 Mon Sep 17 00:00:00 2001 From: TheSavageTeddy <51810476+TheSavageTeddy@users.noreply.github.com> Date: Fri, 25 Nov 2022 22:08:51 +0800 Subject: [PATCH 55/74] added all-zero edge cases for standard alphabet --- tests/operations/tests/Base85.mjs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/operations/tests/Base85.mjs b/tests/operations/tests/Base85.mjs index 8e1d0a23..ffb8cd3a 100644 --- a/tests/operations/tests/Base85.mjs +++ b/tests/operations/tests/Base85.mjs @@ -16,6 +16,10 @@ DIb:@FD,*)+C]U=@3BN#EcYf8ATD3s@q?d$AftVqCh[NqF-FD5W8ARlolDIal(\ DIdu\ D.RTpAKYo'+CT/5+Cei#DII?(E,9)oF*2M7/c"; +const allZeroExample = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" + +const allZeroOutput = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" + TestRegister.addTests([ { name: "To Base85", @@ -45,4 +49,22 @@ TestRegister.addTests([ "args": ["!-u", false] } ] }, + { + name: "From Base85", + input: allZeroExample, + expectedOutput: allZeroOutput, + recipeConfig: [ + { "op": "From Base85", + "args": ["!-u", false] } + ] + }, + { + name: "From Base85", + input: allZeroExample, + expectedOutput: allZeroOutput, + recipeConfig: [ + { "op": "From Base85", + "args": ["!-u", true] } + ] + }, ]); From 0a3d219ad5cf041628f2e78dfa81910d49ea7f42 Mon Sep 17 00:00:00 2001 From: TheSavageTeddy <51810476+TheSavageTeddy@users.noreply.github.com> Date: Fri, 25 Nov 2022 22:13:53 +0800 Subject: [PATCH 56/74] fixed linting --- tests/operations/tests/Base85.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/operations/tests/Base85.mjs b/tests/operations/tests/Base85.mjs index ffb8cd3a..acdbd3cb 100644 --- a/tests/operations/tests/Base85.mjs +++ b/tests/operations/tests/Base85.mjs @@ -16,9 +16,9 @@ DIb:@FD,*)+C]U=@3BN#EcYf8ATD3s@q?d$AftVqCh[NqF-FD5W8ARlolDIal(\ DIdu\ D.RTpAKYo'+CT/5+Cei#DII?(E,9)oF*2M7/c"; -const allZeroExample = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" +const allZeroExample = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"; -const allZeroOutput = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" +const allZeroOutput = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"; TestRegister.addTests([ { From 897d2bef6e3106f0304510f4c9a9f117323009e5 Mon Sep 17 00:00:00 2001 From: TheSavageTeddy <51810476+TheSavageTeddy@users.noreply.github.com> Date: Fri, 25 Nov 2022 22:23:15 +0800 Subject: [PATCH 57/74] fix output --- tests/operations/tests/Base85.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/operations/tests/Base85.mjs b/tests/operations/tests/Base85.mjs index acdbd3cb..2405f63f 100644 --- a/tests/operations/tests/Base85.mjs +++ b/tests/operations/tests/Base85.mjs @@ -18,7 +18,7 @@ D.RTpAKYo'+CT/5+Cei#DII?(E,9)oF*2M7/c"; const allZeroExample = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"; -const allZeroOutput = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"; +const allZeroOutput = "zz!!*-'\"9eu7#RLhG$k3[W&.oNg'GVB\"(`=52*$$(B+<_pR,UFcb-n-Vr/1iJ-0JP==1c70M3&s#]4?Ykm5X@_(6q'R884cEH9MJ8X:f1+h<)lt#=BSg3>[:ZC?t!MSA7]@cBPD3sCi+'.E,fo>FEMbNG^4U^I!pHnJ:W<)KS>/9Ll%\"IN/`jYOHG]iPa.Q$R$jD4S=Q7DTV8*TUnsrdW2ZetXKAY/Yd(L?['d?O\\@K2_]Y2%o^qmn*`5Ta:aN;TJbg\"GZd*^:jeCE.%f\\,!5gtgiEi8N\\UjQ5OekiqBum-X60nF?)@o_%qPq\"ad`r;HWp"; TestRegister.addTests([ { From 2bcfb693b78426571a049cef79a2587c47d1baab Mon Sep 17 00:00:00 2001 From: TheSavageTeddy <51810476+TheSavageTeddy@users.noreply.github.com> Date: Fri, 25 Nov 2022 22:32:23 +0800 Subject: [PATCH 58/74] fix --- tests/operations/tests/Base85.mjs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/operations/tests/Base85.mjs b/tests/operations/tests/Base85.mjs index 2405f63f..36c65f4c 100644 --- a/tests/operations/tests/Base85.mjs +++ b/tests/operations/tests/Base85.mjs @@ -50,18 +50,18 @@ TestRegister.addTests([ ] }, { - name: "From Base85", + name: "To Base85", input: allZeroExample, expectedOutput: allZeroOutput, recipeConfig: [ { "op": "From Base85", - "args": ["!-u", false] } + "args": ["!-u"] } ] }, { name: "From Base85", - input: allZeroExample, - expectedOutput: allZeroOutput, + input: allZeroOutput, + expectedOutput: allZeroExample, recipeConfig: [ { "op": "From Base85", "args": ["!-u", true] } From f84c11f63d735f4d4c5d87551b93d44fa1d8a53b Mon Sep 17 00:00:00 2001 From: TheSavageTeddy <51810476+TheSavageTeddy@users.noreply.github.com> Date: Fri, 25 Nov 2022 22:40:40 +0800 Subject: [PATCH 59/74] im dumb, please pass the test now --- tests/operations/tests/Base85.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/operations/tests/Base85.mjs b/tests/operations/tests/Base85.mjs index 36c65f4c..44e12ee2 100644 --- a/tests/operations/tests/Base85.mjs +++ b/tests/operations/tests/Base85.mjs @@ -54,7 +54,7 @@ TestRegister.addTests([ input: allZeroExample, expectedOutput: allZeroOutput, recipeConfig: [ - { "op": "From Base85", + { "op": "To Base85", "args": ["!-u"] } ] }, @@ -64,7 +64,7 @@ TestRegister.addTests([ expectedOutput: allZeroExample, recipeConfig: [ { "op": "From Base85", - "args": ["!-u", true] } + "args": ["!-u", true, "z"] } ] }, ]); From 5c32c1bdaae9b2c9295957b7e67f5b953612bc5a Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 15:13:54 +0000 Subject: [PATCH 60/74] Fixed tests for CMAC --- tests/operations/tests/CMAC.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/operations/tests/CMAC.mjs b/tests/operations/tests/CMAC.mjs index 92224968..00fc7c9a 100644 --- a/tests/operations/tests/CMAC.mjs +++ b/tests/operations/tests/CMAC.mjs @@ -292,7 +292,7 @@ TestRegister.addTests([ { "name": "CMAC-AES: invalid key length", "input": "", - "expectedOutput": "the key for AES must be either 16, 24, or 32 bytes (currently 20 bytes)", + "expectedOutput": "The key for AES must be either 16, 24, or 32 bytes (currently 20 bytes)", "recipeConfig": [ { "op": "CMAC", @@ -303,7 +303,7 @@ TestRegister.addTests([ { "name": "CMAC-TDES: invalid key length", "input": "", - "expectedOutput": "the key for Triple DES must be 16 or 24 bytes (currently 20 bytes)", + "expectedOutput": "The key for Triple DES must be 16 or 24 bytes (currently 20 bytes)", "recipeConfig": [ { "op": "CMAC", From 3f85c32c7cd1e893c0b684eb94264ec1fcc32e87 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 15:30:32 +0000 Subject: [PATCH 61/74] Lint --- src/core/Utils.mjs | 13 ++++++++----- src/core/operations/ChaCha.mjs | 18 ++++++++---------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/core/Utils.mjs b/src/core/Utils.mjs index 2457c5b7..fe3b1a88 100755 --- a/src/core/Utils.mjs +++ b/src/core/Utils.mjs @@ -381,6 +381,7 @@ class Utils { } } + /** * Converts a byte array to an integer. * @@ -390,10 +391,10 @@ class Utils { * * @example * // returns 67305985 - * Utils.byteArrayToInt([ 1, 2, 3, 4], "little"); + * Utils.byteArrayToInt([1, 2, 3, 4], "little"); * * // returns 16909060 - * Utils.byteArrayToInt([ 1, 2, 3, 4], "big"); + * Utils.byteArrayToInt([1, 2, 3, 4], "big"); */ static byteArrayToInt(byteArray, byteorder) { let value = 0; @@ -409,6 +410,7 @@ class Utils { return value; } + /** * Converts an integer to a byte array of {length} bytes. * @@ -418,13 +420,13 @@ class Utils { * @returns {byteArray} * * @example - * // returns [ 5, 255, 109, 1 ] + * // returns [5, 255, 109, 1] * Utils.intToByteArray(23985925, 4, "little"); * - * // returns [ 1, 109, 255, 5 ] + * // returns [1, 109, 255, 5] * Utils.intToByteArray(23985925, 4, "big"); * - * // returns [ 0, 0, 0, 0, 1, 109, 255, 5 ] + * // returns [0, 0, 0, 0, 1, 109, 255, 5] * Utils.intToByteArray(23985925, 8, "big"); */ static intToByteArray(value, length, byteorder) { @@ -443,6 +445,7 @@ class Utils { return arr; } + /** * Converts a string to an ArrayBuffer. * Treats the string as UTF-8 if any values are over 255. diff --git a/src/core/operations/ChaCha.mjs b/src/core/operations/ChaCha.mjs index 1f253c68..166c1663 100644 --- a/src/core/operations/ChaCha.mjs +++ b/src/core/operations/ChaCha.mjs @@ -159,9 +159,12 @@ class ChaCha extends Operation { ChaCha uses a key of 16 or 32 bytes (128 or 256 bits).`); } - let counter, nonce; - let counterLength; - if (nonceType !== "Integer") { + + let counter, nonce, counterLength; + if (nonceType === "Integer") { + nonce = Utils.intToByteArray(parseInt(args[1].string, 10), 12, "little"); + counterLength = 4; + } else { nonce = Utils.convertToByteArray(args[1].string, args[1].option); if (!(nonce.length === 12 || nonce.length === 8)) { throw new OperationError(`Invalid nonce length: ${nonce.length} bytes. @@ -169,15 +172,10 @@ ChaCha uses a key of 16 or 32 bytes (128 or 256 bits).`); ChaCha uses a nonce of 8 or 12 bytes (64 or 96 bits).`); } counterLength = 16 - nonce.length; - counter = Utils.intToByteArray(args[2], counterLength, "little"); - } - if (nonceType === "Integer") { - nonce = Utils.intToByteArray(parseInt(args[1].string, 10), 12, "little"); - counterLength = 4; - counter = Utils.intToByteArray(args[2], counterLength, "little"); } + counter = Utils.intToByteArray(args[2], counterLength, "little"); - const output = new Array(); + const output = []; input = Utils.convertToByteArray(input, inputType); let counterAsInt = Utils.byteArrayToInt(counter, "little"); From fa238545a0e54fff9ca2fec42d7774aca2febeea Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 15:31:27 +0000 Subject: [PATCH 62/74] Updated CHANGELOG --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2df01028..de0e3da9 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.52.0] - 2022-11-25 +- Added 'ChaCha' operation [@joostrijneveld] | [#1466] + ### [9.51.0] - 2022-11-25 - Added 'CMAC' operation [@mikecat] | [#1457] @@ -330,6 +333,7 @@ All major and minor version changes will be documented in this file. Details of +[9.52.0]: https://github.com/gchq/CyberChef/releases/tag/v9.52.0 [9.51.0]: https://github.com/gchq/CyberChef/releases/tag/v9.51.0 [9.50.0]: https://github.com/gchq/CyberChef/releases/tag/v9.50.0 [9.49.0]: https://github.com/gchq/CyberChef/releases/tag/v9.49.0 @@ -471,6 +475,7 @@ All major and minor version changes will be documented in this file. Details of [@thomasleplus]: https://github.com/thomasleplus [@valdelaseras]: https://github.com/valdelaseras [@brun0ne]: https://github.com/brun0ne +[@joostrijneveld]: https://github.com/joostrijneveld [8ad18b]: https://github.com/gchq/CyberChef/commit/8ad18bc7db6d9ff184ba3518686293a7685bf7b7 [9a33498]: https://github.com/gchq/CyberChef/commit/9a33498fed26a8df9c9f35f39a78a174bf50a513 @@ -578,4 +583,5 @@ All major and minor version changes will be documented in this file. Details of [#1427]: https://github.com/gchq/CyberChef/pull/1427 [#1472]: https://github.com/gchq/CyberChef/pull/1472 [#1457]: https://github.com/gchq/CyberChef/pull/1457 +[#1466]: https://github.com/gchq/CyberChef/pull/1466 From 904c08c436cbe089d07df618ff3ac2e645e2e91c Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 15:31:31 +0000 Subject: [PATCH 63/74] 9.52.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 e769d8ba..5a7c29df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cyberchef", - "version": "9.51.0", + "version": "9.52.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "9.51.0", + "version": "9.52.0", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 9280d342..1a4fed79 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "9.51.0", + "version": "9.52.0", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", From 320f79fe0df6d19d6ce5ff6e90fd151291568cc0 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 16:11:14 +0000 Subject: [PATCH 64/74] Added maxLength property for string arguments --- src/core/Ingredient.mjs | 2 ++ src/core/Operation.mjs | 1 + src/core/operations/FromBase85.mjs | 16 ++++++++++------ src/web/HTMLIngredient.mjs | 10 +++++++--- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/core/Ingredient.mjs b/src/core/Ingredient.mjs index d64bf763..319dfb15 100755 --- a/src/core/Ingredient.mjs +++ b/src/core/Ingredient.mjs @@ -27,6 +27,7 @@ class Ingredient { this.toggleValues = []; this.target = null; this.defaultIndex = 0; + this.maxLength = null; this.min = null; this.max = null; this.step = 1; @@ -53,6 +54,7 @@ class Ingredient { this.toggleValues = ingredientConfig.toggleValues; this.target = typeof ingredientConfig.target !== "undefined" ? ingredientConfig.target : null; this.defaultIndex = typeof ingredientConfig.defaultIndex !== "undefined" ? ingredientConfig.defaultIndex : 0; + this.maxLength = ingredientConfig.maxLength || null; this.min = ingredientConfig.min; this.max = ingredientConfig.max; this.step = ingredientConfig.step; diff --git a/src/core/Operation.mjs b/src/core/Operation.mjs index 32ecff07..24739d3f 100755 --- a/src/core/Operation.mjs +++ b/src/core/Operation.mjs @@ -184,6 +184,7 @@ class Operation { if (ing.disabled) conf.disabled = ing.disabled; if (ing.target) conf.target = ing.target; if (ing.defaultIndex) conf.defaultIndex = ing.defaultIndex; + if (ing.maxLength) conf.maxLength = ing.maxLength; if (typeof ing.min === "number") conf.min = ing.min; if (typeof ing.max === "number") conf.max = ing.max; if (ing.step) conf.step = ing.step; diff --git a/src/core/operations/FromBase85.mjs b/src/core/operations/FromBase85.mjs index aef16638..d0c70da5 100644 --- a/src/core/operations/FromBase85.mjs +++ b/src/core/operations/FromBase85.mjs @@ -7,7 +7,7 @@ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; -import {alphabetName, ALPHABET_OPTIONS} from "../lib/Base85.mjs"; +import {ALPHABET_OPTIONS} from "../lib/Base85.mjs"; /** * From Base85 operation @@ -40,8 +40,9 @@ class FromBase85 extends Operation { { name: "All-zero group char", type: "binaryShortString", - value: "z" - }, + value: "z", + maxLength: 1 + } ]; this.checks = [ { @@ -81,9 +82,8 @@ class FromBase85 extends Operation { */ run(input, args) { const alphabet = Utils.expandAlphRange(args[0]).join(""), - encoding = alphabetName(alphabet), removeNonAlphChars = args[1], - allZeroGroupChar = args[2], + allZeroGroupChar = typeof args[2] === "string" ? args[2].slice(0, 1) : "", result = []; if (alphabet.length !== 85 || @@ -91,6 +91,10 @@ class FromBase85 extends Operation { throw new OperationError("Alphabet must be of length 85"); } + if (allZeroGroupChar && alphabet.includes(allZeroGroupChar)) { + throw new OperationError("The all-zero group char cannot appear in the alphabet"); + } + // Remove delimiters if present const matches = input.match(/^<~(.+?)~>$/); if (matches !== null) input = matches[1]; @@ -109,7 +113,7 @@ class FromBase85 extends Operation { let i = 0; let block, blockBytes; while (i < input.length) { - if (encoding === "Standard" && input[i] === allZeroGroupChar) { + if (input[i] === allZeroGroupChar) { result.push(0, 0, 0, 0); i++; } else { diff --git a/src/web/HTMLIngredient.mjs b/src/web/HTMLIngredient.mjs index 2c638405..6d9ccdbf 100755 --- a/src/web/HTMLIngredient.mjs +++ b/src/web/HTMLIngredient.mjs @@ -30,6 +30,7 @@ class HTMLIngredient { this.rows = config.rows || false; this.target = config.target; this.defaultIndex = config.defaultIndex || 0; + this.maxLength = config.maxLength || null; this.toggleValues = config.toggleValues; this.ingId = this.app.nextIngId(); this.id = "ing-" + this.ingId; @@ -63,7 +64,8 @@ class HTMLIngredient { tabindex="${this.tabIndex}" arg-name="${this.name}" value="${this.value}" - ${this.disabled ? "disabled" : ""}> + ${this.disabled ? "disabled" : ""} + ${this.maxLength ? `maxlength="${this.maxLength}"` : ""}> `; break; case "shortString": @@ -78,7 +80,8 @@ class HTMLIngredient { tabindex="${this.tabIndex}" arg-name="${this.name}" value="${this.value}" - ${this.disabled ? "disabled" : ""}> + ${this.disabled ? "disabled" : ""} + ${this.maxLength ? `maxlength="${this.maxLength}"` : ""}> `; break; case "toggleString": @@ -93,7 +96,8 @@ class HTMLIngredient { tabindex="${this.tabIndex}" arg-name="${this.name}" value="${this.value}" - ${this.disabled ? "disabled" : ""}> + ${this.disabled ? "disabled" : ""} + ${this.maxLength ? `maxlength="${this.maxLength}"` : ""}>
From 7ecde9fd2ac6ebdd07ab909e9bd199a34c8876e5 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 16:11:28 +0000 Subject: [PATCH 65/74] 9.52.1 --- 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 5a7c29df..5067e138 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cyberchef", - "version": "9.52.0", + "version": "9.52.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "9.52.0", + "version": "9.52.1", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 1a4fed79..ddc64f9c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "9.52.0", + "version": "9.52.1", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", From 04378c1f9129eaa9ddc14540ad68893a3983590f Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 16:28:15 +0000 Subject: [PATCH 66/74] Moved AES Key wrapping operations in Categories.json --- src/core/config/Categories.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index bad0bdde..4adaa5e8 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -125,6 +125,8 @@ "JWT Decode", "Citrix CTX1 Encode", "Citrix CTX1 Decode", + "AES Key Wrap", + "AES Key Unwrap", "Pseudo-Random Number Generator", "Enigma", "Bombe", @@ -132,9 +134,7 @@ "Typex", "Lorenz", "Colossus", - "SIGABA", - "AES Key Wrap", - "AES Key Unwrap" + "SIGABA" ] }, { From aa981ccd4a23213e89b8f79f563dcb67b07679d2 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 16:29:25 +0000 Subject: [PATCH 67/74] Updated CHANGELOG --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index de0e3da9..4ee1f67b 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.53.0] - 2022-11-25 +- Added 'AES Key Wrap' and 'AES Key Unwrap' operations [@mikecat] | [#1456] + ### [9.52.0] - 2022-11-25 - Added 'ChaCha' operation [@joostrijneveld] | [#1466] @@ -333,6 +336,7 @@ All major and minor version changes will be documented in this file. Details of +[9.53.0]: https://github.com/gchq/CyberChef/releases/tag/v9.53.0 [9.52.0]: https://github.com/gchq/CyberChef/releases/tag/v9.52.0 [9.51.0]: https://github.com/gchq/CyberChef/releases/tag/v9.51.0 [9.50.0]: https://github.com/gchq/CyberChef/releases/tag/v9.50.0 @@ -584,4 +588,5 @@ All major and minor version changes will be documented in this file. Details of [#1472]: https://github.com/gchq/CyberChef/pull/1472 [#1457]: https://github.com/gchq/CyberChef/pull/1457 [#1466]: https://github.com/gchq/CyberChef/pull/1466 +[#1456]: https://github.com/gchq/CyberChef/pull/1456 From faa25ee662ee237ca04e6bd31baee89f1e1ae0fe Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 16:29:31 +0000 Subject: [PATCH 68/74] 9.53.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 5067e138..4a7500f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cyberchef", - "version": "9.52.1", + "version": "9.53.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "9.52.1", + "version": "9.53.0", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index ddc64f9c..53ff50b7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "9.52.1", + "version": "9.53.0", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", From 7403666a111f0e8e96ad51789ecf2dc3d2672658 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 16:48:54 +0000 Subject: [PATCH 69/74] Tidied up Rabbit operations --- src/core/config/Categories.json | 6 +-- .../{RabbitStreamCipher.mjs => Rabbit.mjs} | 12 ++--- tests/operations/index.mjs | 2 +- .../{RabbitStreamCipher.mjs => Rabbit.mjs} | 44 +++++++++---------- 4 files changed, 32 insertions(+), 32 deletions(-) rename src/core/operations/{RabbitStreamCipher.mjs => Rabbit.mjs} (94%) rename tests/operations/tests/{RabbitStreamCipher.mjs => Rabbit.mjs} (81%) diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 43c23c62..307270d2 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -75,7 +75,6 @@ "AES Decrypt", "Blowfish Encrypt", "Blowfish Decrypt", - "ChaCha", "DES Encrypt", "DES Decrypt", "Triple DES Encrypt", @@ -86,6 +85,8 @@ "RC2 Decrypt", "RC4", "RC4 Drop", + "ChaCha", + "Rabbit", "SM4 Encrypt", "SM4 Decrypt", "ROT13", @@ -134,8 +135,7 @@ "Typex", "Lorenz", "Colossus", - "SIGABA", - "Rabbit Stream Cipher" + "SIGABA" ] }, { diff --git a/src/core/operations/RabbitStreamCipher.mjs b/src/core/operations/Rabbit.mjs similarity index 94% rename from src/core/operations/RabbitStreamCipher.mjs rename to src/core/operations/Rabbit.mjs index 7d030a6f..91ff24a3 100644 --- a/src/core/operations/RabbitStreamCipher.mjs +++ b/src/core/operations/Rabbit.mjs @@ -10,19 +10,19 @@ import { toHexFast } from "../lib/Hex.mjs"; import OperationError from "../errors/OperationError.mjs"; /** - * Rabbit Stream Cipher operation + * Rabbit operation */ -class RabbitStreamCipher extends Operation { +class Rabbit extends Operation { /** - * RabbitStreamCipher constructor + * Rabbit constructor */ constructor() { super(); - this.name = "Rabbit Stream Cipher"; + this.name = "Rabbit"; this.module = "Ciphers"; - this.description = "Rabbit Stream Cipher, a stream cipher algorithm defined in RFC4503.

The cipher uses a 128-bit key and an optional 64-bit initialization vector (IV).

big-endian: based on RFC4503 and RFC3447
little-endian: compatible with Crypto++"; + this.description = "Rabbit is a high-speed stream cipher introduced in 2003 and defined in RFC 4503.

The cipher uses a 128-bit key and an optional 64-bit initialization vector (IV).

big-endian: based on RFC4503 and RFC3447
little-endian: compatible with Crypto++"; this.infoURL = "https://wikipedia.org/wiki/Rabbit_(cipher)"; this.inputType = "string"; this.outputType = "string"; @@ -244,4 +244,4 @@ class RabbitStreamCipher extends Operation { } -export default RabbitStreamCipher; +export default Rabbit; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index cb410915..7a3361f2 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -129,7 +129,7 @@ import "./tests/Shuffle.mjs"; import "./tests/FletcherChecksum.mjs"; import "./tests/CMAC.mjs"; import "./tests/AESKeyWrap.mjs"; -import "./tests/RabbitStreamCipher.mjs"; +import "./tests/Rabbit.mjs"; // Cannot test operations that use the File type yet // import "./tests/SplitColourChannels.mjs"; diff --git a/tests/operations/tests/RabbitStreamCipher.mjs b/tests/operations/tests/Rabbit.mjs similarity index 81% rename from tests/operations/tests/RabbitStreamCipher.mjs rename to tests/operations/tests/Rabbit.mjs index fd22ffbd..ca3156fa 100644 --- a/tests/operations/tests/RabbitStreamCipher.mjs +++ b/tests/operations/tests/Rabbit.mjs @@ -8,12 +8,12 @@ import TestRegister from "../../lib/TestRegister.mjs"; TestRegister.addTests([ { - name: "Rabbit Stream Cipher: RFC Test vector, without IV 1", + name: "Rabbit: RFC Test vector, without IV 1", input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", expectedOutput: "b15754f036a5d6ecf56b45261c4af70288e8d815c59c0c397b696c4789c68aa7f416a1c3700cd451da68d1881673d696", recipeConfig: [ { - "op": "Rabbit Stream Cipher", + "op": "Rabbit", "args": [ {"option": "Hex", "string": "00000000000000000000000000000000"}, {"option": "Hex", "string": ""}, @@ -23,12 +23,12 @@ TestRegister.addTests([ ] }, { - name: "Rabbit Stream Cipher: RFC Test vector, without IV 2", + name: "Rabbit: RFC Test vector, without IV 2", input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", expectedOutput: "3d2df3c83ef627a1e97fc38487e2519cf576cd61f4405b8896bf53aa8554fc19e5547473fbdb43508ae53b20204d4c5e", recipeConfig: [ { - "op": "Rabbit Stream Cipher", + "op": "Rabbit", "args": [ {"option": "Hex", "string": "912813292e3d36fe3bfc62f1dc51c3ac"}, {"option": "Hex", "string": ""}, @@ -38,12 +38,12 @@ TestRegister.addTests([ ] }, { - name: "Rabbit Stream Cipher: RFC Test vector, without IV 3", + name: "Rabbit: RFC Test vector, without IV 3", input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", expectedOutput: "0cb10dcda041cdac32eb5cfd02d0609b95fc9fca0f17015a7b7092114cff3ead9649e5de8bfc7f3f924147ad3a947428", recipeConfig: [ { - "op": "Rabbit Stream Cipher", + "op": "Rabbit", "args": [ {"option": "Hex", "string": "8395741587e0c733e9e9ab01c09b0043"}, {"option": "Hex", "string": ""}, @@ -53,12 +53,12 @@ TestRegister.addTests([ ] }, { - name: "Rabbit Stream Cipher: RFC Test vector, with IV 1", + name: "Rabbit: RFC Test vector, with IV 1", input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", expectedOutput: "c6a7275ef85495d87ccd5d376705b7ed5f29a6ac04f5efd47b8f293270dc4a8d2ade822b29de6c1ee52bdb8a47bf8f66", recipeConfig: [ { - "op": "Rabbit Stream Cipher", + "op": "Rabbit", "args": [ {"option": "Hex", "string": "00000000000000000000000000000000"}, {"option": "Hex", "string": "0000000000000000"}, @@ -68,12 +68,12 @@ TestRegister.addTests([ ] }, { - name: "Rabbit Stream Cipher: RFC Test vector, with IV 2", + name: "Rabbit: RFC Test vector, with IV 2", input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", expectedOutput: "1fcd4eb9580012e2e0dccc9222017d6da75f4e10d12125017b2499ffed936f2eebc112c393e738392356bdd012029ba7", recipeConfig: [ { - "op": "Rabbit Stream Cipher", + "op": "Rabbit", "args": [ {"option": "Hex", "string": "00000000000000000000000000000000"}, {"option": "Hex", "string": "c373f575c1267e59"}, @@ -83,12 +83,12 @@ TestRegister.addTests([ ] }, { - name: "Rabbit Stream Cipher: RFC Test vector, with IV 3", + name: "Rabbit: RFC Test vector, with IV 3", input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", expectedOutput: "445ad8c805858dbf70b6af23a151104d96c8f27947f42c5baeae67c6acc35b039fcbfc895fa71c17313df034f01551cb", recipeConfig: [ { - "op": "Rabbit Stream Cipher", + "op": "Rabbit", "args": [ {"option": "Hex", "string": "00000000000000000000000000000000"}, {"option": "Hex", "string": "a6eb561ad2f41727"}, @@ -98,12 +98,12 @@ TestRegister.addTests([ ] }, { - name: "Rabbit Stream Cipher: generated stream should be XORed with the input", + name: "Rabbit: generated stream should be XORed with the input", input: "cedda96c054e3ddd93da7ed05e2a4b7bdb0c00fe214f03502e2708b2c2bfc77aa2311b0b9af8aa78d119f92b26db0a6b", expectedOutput: "7f8afd9c33ebeb3166b13bf64260bc7953e4d8ebe4d30f69554e64f54b794ddd5627bac8eaf47e290b7128a330a8dcfd", recipeConfig: [ { - "op": "Rabbit Stream Cipher", + "op": "Rabbit", "args": [ {"option": "Hex", "string": "00000000000000000000000000000000"}, {"option": "Hex", "string": ""}, @@ -113,12 +113,12 @@ TestRegister.addTests([ ] }, { - name: "Rabbit Stream Cipher: least significant bits should be used for the last block", + name: "Rabbit: least significant bits should be used for the last block", input: "0000000000000000", expectedOutput: "f56b45261c4af702", recipeConfig: [ { - "op": "Rabbit Stream Cipher", + "op": "Rabbit", "args": [ {"option": "Hex", "string": "00000000000000000000000000000000"}, {"option": "Hex", "string": ""}, @@ -128,12 +128,12 @@ TestRegister.addTests([ ] }, { - name: "Rabbit Stream Cipher: invalid key length", + name: "Rabbit: invalid key length", input: "", expectedOutput: "Invalid key length: 8 bytes (expected: 16)", recipeConfig: [ { - "op": "Rabbit Stream Cipher", + "op": "Rabbit", "args": [ {"option": "Hex", "string": "0000000000000000"}, {"option": "Hex", "string": ""}, @@ -143,12 +143,12 @@ TestRegister.addTests([ ] }, { - name: "Rabbit Stream Cipher: invalid IV length", + name: "Rabbit: invalid IV length", input: "", expectedOutput: "Invalid IV length: 4 bytes (expected: 0 or 8)", recipeConfig: [ { - "op": "Rabbit Stream Cipher", + "op": "Rabbit", "args": [ {"option": "Hex", "string": "00000000000000000000000000000000"}, {"option": "Hex", "string": "00000000"}, @@ -160,12 +160,12 @@ TestRegister.addTests([ { // this testcase is taken from the first example on Crypto++ Wiki // https://www.cryptopp.com/wiki/Rabbit - name: "Rabbit Stream Cipher: little-endian mode (Crypto++ compatible)", + name: "Rabbit: little-endian mode (Crypto++ compatible)", input: "Rabbit stream cipher test", expectedOutput: "1ae2d4edcf9b6063b00fd6fda0b223aded157e77031cf0440b", recipeConfig: [ { - "op": "Rabbit Stream Cipher", + "op": "Rabbit", "args": [ {"option": "Hex", "string": "23c2731e8b5469fd8dabb5bc592a0f3a"}, {"option": "Hex", "string": "712906405ef03201"}, From 249af2ded165397ed4b2b84ed74de0afcb6f43b6 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 16:49:37 +0000 Subject: [PATCH 70/74] Updated CHANGELOG --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ee1f67b..0691ac8f 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.54.0] - 2022-11-25 +- Added 'Rabbit' operation [@mikecat] | [#1450] + ### [9.53.0] - 2022-11-25 - Added 'AES Key Wrap' and 'AES Key Unwrap' operations [@mikecat] | [#1456] @@ -336,6 +339,7 @@ All major and minor version changes will be documented in this file. Details of +[9.54.0]: https://github.com/gchq/CyberChef/releases/tag/v9.54.0 [9.53.0]: https://github.com/gchq/CyberChef/releases/tag/v9.53.0 [9.52.0]: https://github.com/gchq/CyberChef/releases/tag/v9.52.0 [9.51.0]: https://github.com/gchq/CyberChef/releases/tag/v9.51.0 @@ -589,4 +593,5 @@ All major and minor version changes will be documented in this file. Details of [#1457]: https://github.com/gchq/CyberChef/pull/1457 [#1466]: https://github.com/gchq/CyberChef/pull/1466 [#1456]: https://github.com/gchq/CyberChef/pull/1456 +[#1450]: https://github.com/gchq/CyberChef/pull/1450 From ba12ad8e7c885d0a33aa6d04d4657a5a7988a839 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 25 Nov 2022 16:49:41 +0000 Subject: [PATCH 71/74] 9.54.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 4a7500f2..f8ed2965 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cyberchef", - "version": "9.53.0", + "version": "9.54.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "9.53.0", + "version": "9.54.0", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index 53ff50b7..ddbacd8f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "9.53.0", + "version": "9.54.0", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", From 760eff49b5307aaa3104c5e5b437ffe62299acd1 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 9 Dec 2022 11:39:22 +0000 Subject: [PATCH 72/74] Added 'AMF Encode' and 'AMF Decode' operations --- package-lock.json | 45 ++++++++++++++++++++++++++ package.json | 2 ++ src/core/config/Categories.json | 2 ++ src/core/operations/AMFDecode.mjs | 52 +++++++++++++++++++++++++++++++ src/core/operations/AMFEncode.mjs | 52 +++++++++++++++++++++++++++++++ 5 files changed, 153 insertions(+) create mode 100644 src/core/operations/AMFDecode.mjs create mode 100644 src/core/operations/AMFEncode.mjs diff --git a/package-lock.json b/package-lock.json index f8ed2965..b84ba6b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { + "@astronautlabs/amf": "^0.0.6", "@babel/polyfill": "^7.12.1", "@blu3r4y/lzma": "^2.3.3", "arrive": "^2.4.1", @@ -75,6 +76,7 @@ "process": "^0.11.10", "protobufjs": "^6.11.3", "qr-image": "^3.2.0", + "reflect-metadata": "^0.1.13", "scryptsy": "^2.1.0", "snackbarjs": "^1.1.0", "sortablejs": "^1.15.0", @@ -151,6 +153,25 @@ "node": ">=6.0.0" } }, + "node_modules/@astronautlabs/amf": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@astronautlabs/amf/-/amf-0.0.6.tgz", + "integrity": "sha512-cJgbXW45TIDLQf2hiHqDoRfmeRy5u9Z4npr7sZfBThvbp5cbqDieTWaJTu91cUAj35/u87OHZijLTbMO18ZIow==", + "dependencies": { + "@astronautlabs/bitstream": "^4.0.0" + }, + "engines": { + "node": "^14" + } + }, + "node_modules/@astronautlabs/bitstream": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@astronautlabs/bitstream/-/bitstream-4.1.3.tgz", + "integrity": "sha512-4X5cmrB5I5g/ifKXwoVc5JwAYgn372kS0AsTdVQYY+OzlSZ92ANEHj6W5MW5haYSQbbBZ9XK55rdy6NnXOyRgA==", + "peerDependencies": { + "reflect-metadata": "^0.1.13" + } + }, "node_modules/@babel/code-frame": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", @@ -11914,6 +11935,11 @@ "node": ">= 0.10" } }, + "node_modules/reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -14346,6 +14372,20 @@ "@jridgewell/trace-mapping": "^0.3.0" } }, + "@astronautlabs/amf": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@astronautlabs/amf/-/amf-0.0.6.tgz", + "integrity": "sha512-cJgbXW45TIDLQf2hiHqDoRfmeRy5u9Z4npr7sZfBThvbp5cbqDieTWaJTu91cUAj35/u87OHZijLTbMO18ZIow==", + "requires": { + "@astronautlabs/bitstream": "^4.0.0" + } + }, + "@astronautlabs/bitstream": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@astronautlabs/bitstream/-/bitstream-4.1.3.tgz", + "integrity": "sha512-4X5cmrB5I5g/ifKXwoVc5JwAYgn372kS0AsTdVQYY+OzlSZ92ANEHj6W5MW5haYSQbbBZ9XK55rdy6NnXOyRgA==", + "requires": {} + }, "@babel/code-frame": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", @@ -23359,6 +23399,11 @@ "resolve": "^1.9.0" } }, + "reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", diff --git a/package.json b/package.json index ddbacd8f..d76f2094 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "worker-loader": "^3.0.8" }, "dependencies": { + "@astronautlabs/amf": "^0.0.6", "@babel/polyfill": "^7.12.1", "@blu3r4y/lzma": "^2.3.3", "arrive": "^2.4.1", @@ -151,6 +152,7 @@ "process": "^0.11.10", "protobufjs": "^6.11.3", "qr-image": "^3.2.0", + "reflect-metadata": "^0.1.13", "scryptsy": "^2.1.0", "snackbarjs": "^1.1.0", "sortablejs": "^1.15.0", diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 307270d2..075e8d66 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -46,6 +46,8 @@ "From Quoted Printable", "To Punycode", "From Punycode", + "AMF Encode", + "AMF Decode", "To Hex Content", "From Hex Content", "PEM to Hex", diff --git a/src/core/operations/AMFDecode.mjs b/src/core/operations/AMFDecode.mjs new file mode 100644 index 00000000..50a0d551 --- /dev/null +++ b/src/core/operations/AMFDecode.mjs @@ -0,0 +1,52 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import "reflect-metadata"; // Required as a shim for the amf library +import { AMF0, AMF3 } from "@astronautlabs/amf"; + +/** + * AMF Decode operation + */ +class AMFDecode extends Operation { + + /** + * AMFDecode constructor + */ + constructor() { + super(); + + this.name = "AMF Decode"; + this.module = "Encodings"; + this.description = "Action Message Format (AMF) is a binary format used to serialize object graphs such as ActionScript objects and XML, or send messages between an Adobe Flash client and a remote service, usually a Flash Media Server or third party alternatives."; + this.infoURL = "https://wikipedia.org/wiki/Action_Message_Format"; + this.inputType = "ArrayBuffer"; + this.outputType = "JSON"; + this.args = [ + { + name: "Format", + type: "option", + value: ["AMF0", "AMF3"], + defaultIndex: 1 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {JSON} + */ + run(input, args) { + const [format] = args; + const handler = format === "AMF0" ? AMF0 : AMF3; + const encoded = new Uint8Array(input); + return handler.Value.deserialize(encoded); + } + +} + +export default AMFDecode; diff --git a/src/core/operations/AMFEncode.mjs b/src/core/operations/AMFEncode.mjs new file mode 100644 index 00000000..c21ba7dc --- /dev/null +++ b/src/core/operations/AMFEncode.mjs @@ -0,0 +1,52 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import "reflect-metadata"; // Required as a shim for the amf library +import { AMF0, AMF3 } from "@astronautlabs/amf"; + +/** + * AMF Encode operation + */ +class AMFEncode extends Operation { + + /** + * AMFEncode constructor + */ + constructor() { + super(); + + this.name = "AMF Encode"; + this.module = "Encodings"; + this.description = "Action Message Format (AMF) is a binary format used to serialize object graphs such as ActionScript objects and XML, or send messages between an Adobe Flash client and a remote service, usually a Flash Media Server or third party alternatives."; + this.infoURL = "https://wikipedia.org/wiki/Action_Message_Format"; + this.inputType = "JSON"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + name: "Format", + type: "option", + value: ["AMF0", "AMF3"], + defaultIndex: 1 + } + ]; + } + + /** + * @param {JSON} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const [format] = args; + const handler = format === "AMF0" ? AMF0 : AMF3; + const output = handler.Value.any(input).serialize(); + return output.buffer; + } + +} + +export default AMFEncode; From 4b4d3c68453fe85933b4a1345cc7e58c23ae685c Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 9 Dec 2022 11:40:25 +0000 Subject: [PATCH 73/74] Updated CHANGELOG --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0691ac8f..d21fe05d 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.55.0] - 2022-12-09 +- Added 'AMF Encode' and 'AMF Decode' operations [@n1474335] | [760eff4] + ### [9.54.0] - 2022-11-25 - Added 'Rabbit' operation [@mikecat] | [#1450] @@ -339,6 +342,7 @@ All major and minor version changes will be documented in this file. Details of +[9.55.0]: https://github.com/gchq/CyberChef/releases/tag/v9.55.0 [9.54.0]: https://github.com/gchq/CyberChef/releases/tag/v9.54.0 [9.53.0]: https://github.com/gchq/CyberChef/releases/tag/v9.53.0 [9.52.0]: https://github.com/gchq/CyberChef/releases/tag/v9.52.0 @@ -492,6 +496,7 @@ All major and minor version changes will be documented in this file. Details of [dd18e52]: https://github.com/gchq/CyberChef/commit/dd18e529939078b89867297b181a584e8b2cc7da [a895d1d]: https://github.com/gchq/CyberChef/commit/a895d1d82a2f92d440a0c5eca2bc7c898107b737 [31a7f83]: https://github.com/gchq/CyberChef/commit/31a7f83b82e78927f89689f323fcb9185144d6ff +[760eff4]: https://github.com/gchq/CyberChef/commit/760eff49b5307aaa3104c5e5b437ffe62299acd1 [#95]: https://github.com/gchq/CyberChef/pull/299 [#173]: https://github.com/gchq/CyberChef/pull/173 From 2efd07580376e8efd7aa6548e004bda753bf8fa1 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 9 Dec 2022 11:40:35 +0000 Subject: [PATCH 74/74] 9.55.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 b84ba6b0..8b5641c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cyberchef", - "version": "9.54.0", + "version": "9.55.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "9.54.0", + "version": "9.55.0", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index d76f2094..fe93fe52 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "9.54.0", + "version": "9.55.0", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef",