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 91b077718..114d5e215 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 114d5e215..ff69023ff 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 080a24daa..d3e1ee3ba 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 a19ca70cf..b3e0f6523 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 43d5dc4e1..430f61967 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 000000000..b106cb4d1 --- /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 19e709709..2d303dcb2 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 000000000..fd22ffbdf --- /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 b106cb4d1..7d030a6ff 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 aeb5f6771..9a0eb272f 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 46bbebd9b..adb30d719 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 19e4cbb21..7a4714be7 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 43d5dc4e1..0567edd96 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 000000000..1558847af --- /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 000000000..388671561 --- /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 19e709709..f6eacddd5 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 000000000..bca36a402 --- /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 43d5dc4e1..79f3c19c7 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 000000000..8c365362d --- /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 19e709709..18a1b59f9 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 000000000..e5e3b40b3 --- /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 859e68685..e1e5f6df8 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 29c74535c..7b41ce555 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 1d0d5bd97..68328ea32 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 19e709709..fe1c4e00d 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 000000000..de23abdcf --- /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 28707bded..8e4a849a5 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 895d67235..49c752a84 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 1ccd3298c..ad88fb97c 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 7411a6e99..786cbcce0 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 b7e8e9089..78e1ad58c 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 783bd00af..8611ecb4c 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 7626eeabe..8487509f8 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 237020a2a..720d155d3 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 8c365362d..12c4080cf 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 e5e3b40b3..92224968a 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 a19ca70cf..5e1753a45 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 aeb5f6771..28c9569f0 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 31e17d1a9..2457c5b72 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 43d5dc4e1..3a23a9207 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 000000000..1f253c686 --- /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 19e709709..43fbfd82d 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 000000000..44b39a67f --- /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 43d5dc4e1..c629c0856 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 000000000..99cbd072d --- /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 19e709709..2d9f7cf0b 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 000000000..eadc615eb --- /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 cbb61cc9e..300fc6a23 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 9f7ab0f7e..3c34ebcc1 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 43d5dc4e1..211eae644 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 000000000..1f43785fb --- /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 000000000..2ba50416a --- /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 015277b16..60117c675 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 f4d21a41a..37f7297f3 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 300fc6a23..487b3b27f 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 3c34ebcc1..d993047c1 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 1afe1f427..1dac1c535 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 1dac1c535..a8bf2291a 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 033aa8598..53150566f 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 f9b37c742..0ffae9672 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 0ffae9672..3904479dc 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 3904479dc..aaa1dc940 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 aaa1dc940..afbd6af3f 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 487b3b27f..c35598b2e 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 d993047c1..ae6464401 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 a8bf2291a..4a8cdc5d8 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 c35598b2e..cbf7c6e2b 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 ae6464401..09fb53030 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 99cbd072d..14c4ffab8 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 eadc615eb..21c878f19 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 37f7297f3..4d5c3c198 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 cbf7c6e2b..dfca428e1 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 09fb53030..75baa3821 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 adfdf6f77..677548903 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 dfca428e1..cf13395db 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 75baa3821..d4b159ef8 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 cf13395db..4a9d2b992 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 d4b159ef8..68b87da14 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 4a9d2b992..bf504fb23 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 68b87da14..5a2b7f048 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 bf504fb23..933c8368e 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 5a2b7f048..d9c6108ea 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 933c8368e..862afebe4 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 d9c6108ea..898dd0d18 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 862afebe4..6b16e0abd 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 898dd0d18..dcd69f10f 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 6b16e0abd..da82e3971 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 dcd69f10f..fb0f4e15d 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 da82e3971..2e3182ed5 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 fb0f4e15d..2b727c354 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 2e3182ed5..0b3d5acb8 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 2b727c354..4231950f3 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 0b3d5acb8..5fdab2b08 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 4231950f3..860a4f62b 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 adb30d719..c8998f610 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 5fdab2b08..4e264ca3c 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 860a4f62b..59f02769a 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 4e264ca3c..976db25d5 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 59f02769a..af43b84d0 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 12c4080cf..d6491362b 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 4d5c3c198..2df010282 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 976db25d5..e769d8ba4 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 af43b84d0..9280d3420 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 afbd6af3f..aef166389 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 8e1d0a236..ffb8cd3ad 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 ffb8cd3ad..acdbd3cbd 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 acdbd3cbd..2405f63fc 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 2405f63fc..36c65f4c4 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 36c65f4c4..44e12ee28 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 92224968a..00fc7c9a0 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 2457c5b72..fe3b1a881 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 1f253c686..166c1663d 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 2df010282..de0e3da9b 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 e769d8ba4..5a7c29df8 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 9280d3420..1a4fed798 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 d64bf763b..319dfb158 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 32ecff07c..24739d3f7 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 aef166389..d0c70da5a 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 2c638405c..6d9ccdbfd 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 5a7c29df8..5067e1389 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 1a4fed798..ddc64f9c4 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 bad0bdded..4adaa5e88 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 de0e3da9b..4ee1f67bb 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 5067e1389..4a7500f24 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 ddc64f9c4..53ff50b71 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 43c23c62b..307270d26 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 7d030a6ff..91ff24a3f 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 cb410915e..7a3361f24 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 fd22ffbdf..ca3156fa1 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 4ee1f67bb..0691ac8f5 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 4a7500f24..f8ed2965f 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 53ff50b71..ddbacd8fc 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 f8ed2965f..b84ba6b03 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 ddbacd8fc..d76f2094b 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 307270d26..075e8d666 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 000000000..50a0d551f --- /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 000000000..c21ba7dc7 --- /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 0691ac8f5..d21fe05d7 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 b84ba6b03..8b5641c9e 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 d76f2094b..fe93fe52b 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",