diff --git a/CHANGELOG.md b/CHANGELOG.md index f4d21a41..d21fe05d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,27 @@ 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] + +### [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] + +### [9.51.0] - 2022-11-25 +- Added 'CMAC' operation [@mikecat] | [#1457] + +### [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] + ### [9.48.0] - 2022-10-14 - Added 'LM Hash' and 'NT Hash' operations [@n1474335] [@brun0ne] | [#1427] @@ -321,6 +342,13 @@ 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 +[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 [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 @@ -459,6 +487,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 @@ -466,6 +495,8 @@ 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 +[760eff4]: https://github.com/gchq/CyberChef/commit/760eff49b5307aaa3104c5e5b437ffe62299acd1 [#95]: https://github.com/gchq/CyberChef/pull/299 [#173]: https://github.com/gchq/CyberChef/pull/173 @@ -563,4 +594,9 @@ 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 +[#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 diff --git a/package-lock.json b/package-lock.json index deb1d7a6..f44b6499 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,16 @@ { "name": "cyberchef", - "version": "9.48.0", + "version": "9.55.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "9.48.0", + "version": "9.55.0", "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", @@ -58,6 +59,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", @@ -74,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", @@ -154,6 +157,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", "license": "MIT", @@ -8825,6 +8847,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", "dev": true, @@ -10798,6 +10825,8 @@ }, "node_modules/queue-microtask": { "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true, "funding": [ { @@ -10812,8 +10841,7 @@ "type": "consulting", "url": "https://feross.org/support" } - ], - "license": "MIT" + ] }, "node_modules/randombytes": { "version": "2.1.0", @@ -10908,6 +10936,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", "dev": true, @@ -13115,6 +13148,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", "requires": { @@ -18784,6 +18831,11 @@ "lz-string": { "version": "1.4.4" }, + "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", "dev": true, @@ -20042,6 +20094,8 @@ }, "queue-microtask": { "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "dev": true }, "randombytes": { @@ -20111,6 +20165,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", "dev": true diff --git a/package.json b/package.json index ec5fc6d0..d87de1d9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "9.48.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", @@ -91,6 +91,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", @@ -139,6 +140,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", @@ -155,6 +157,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/Ingredient.mjs b/src/core/Ingredient.mjs index d64bf763..319dfb15 100755 --- a/src/core/Ingredient.mjs +++ b/src/core/Ingredient.mjs @@ -27,6 +27,7 @@ class Ingredient { this.toggleValues = []; this.target = null; this.defaultIndex = 0; + this.maxLength = null; this.min = null; this.max = null; this.step = 1; @@ -53,6 +54,7 @@ class Ingredient { this.toggleValues = ingredientConfig.toggleValues; this.target = typeof ingredientConfig.target !== "undefined" ? ingredientConfig.target : null; this.defaultIndex = typeof ingredientConfig.defaultIndex !== "undefined" ? ingredientConfig.defaultIndex : 0; + this.maxLength = ingredientConfig.maxLength || null; this.min = ingredientConfig.min; this.max = ingredientConfig.max; this.step = ingredientConfig.step; diff --git a/src/core/Operation.mjs b/src/core/Operation.mjs index 32ecff07..24739d3f 100755 --- a/src/core/Operation.mjs +++ b/src/core/Operation.mjs @@ -184,6 +184,7 @@ class Operation { if (ing.disabled) conf.disabled = ing.disabled; if (ing.target) conf.target = ing.target; if (ing.defaultIndex) conf.defaultIndex = ing.defaultIndex; + if (ing.maxLength) conf.maxLength = ing.maxLength; if (typeof ing.min === "number") conf.min = ing.min; if (typeof ing.max === "number") conf.max = ing.max; if (ing.step) conf.step = ing.step; diff --git a/src/core/Utils.mjs b/src/core/Utils.mjs index 30b8ee8c..02c06d96 100755 --- a/src/core/Utils.mjs +++ b/src/core/Utils.mjs @@ -393,6 +393,70 @@ 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. * Treats the string as UTF-8 if any values are over 255. diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 43d5dc4e..075e8d66 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -46,6 +46,8 @@ "From Quoted Printable", "To Punycode", "From Punycode", + "AMF Encode", + "AMF Decode", "To Hex Content", "From Hex Content", "PEM to Hex", @@ -85,6 +87,8 @@ "RC2 Decrypt", "RC4", "RC4 Drop", + "ChaCha", + "Rabbit", "SM4 Encrypt", "SM4 Decrypt", "ROT13", @@ -124,6 +128,8 @@ "JWT Decode", "Citrix CTX1 Encode", "Citrix CTX1 Decode", + "AES Key Wrap", + "AES Key Unwrap", "Pseudo-Random Number Generator", "Enigma", "Bombe", @@ -249,6 +255,7 @@ "To Table", "Reverse", "Sort", + "Shuffle", "Unique", "Split", "Filter", @@ -333,7 +340,9 @@ "LZString Decompress", "LZString Compress", "LZMA Decompress", - "LZMA Compress" + "LZMA Compress", + "LZ4 Decompress", + "LZ4 Compress" ] }, { @@ -365,6 +374,7 @@ "Compare SSDEEP hashes", "Compare CTPH hashes", "HMAC", + "CMAC", "Bcrypt", "Bcrypt compare", "Bcrypt parse", diff --git a/src/core/config/scripts/newMinorVersion.mjs b/src/core/config/scripts/newMinorVersion.mjs index adfdf6f7..67754890 100644 --- a/src/core/config/scripts/newMinorVersion.mjs +++ b/src/core/config/scripts/newMinorVersion.mjs @@ -136,7 +136,7 @@ const getFeature = function() { fs.writeFileSync(path.join(process.cwd(), "CHANGELOG.md"), changelogData); - console.log("Written CHANGELOG.md"); + console.log("Written CHANGELOG.md\nCommit changes and then run `npm version minor`."); } }); }; diff --git a/src/core/lib/Hex.mjs b/src/core/lib/Hex.mjs index b7e8e908..78e1ad58 100644 --- a/src/core/lib/Hex.mjs +++ b/src/core/lib/Hex.mjs @@ -105,13 +105,17 @@ export function fromHex(data, delim="Auto", byteLen=2) { throw new OperationError("Byte length must be a positive integer"); if (delim !== "None") { - const delimRegex = delim === "Auto" ? /[^a-f\d]|(0x)/gi : Utils.regexRep(delim); - data = data.replace(delimRegex, ""); + const delimRegex = delim === "Auto" ? /[^a-f\d]|0x/gi : Utils.regexRep(delim); + data = data.split(delimRegex); + } else { + data = [data]; } const output = []; - for (let i = 0; i < data.length; i += byteLen) { - output.push(parseInt(data.substr(i, byteLen), 16)); + for (let i = 0; i < data.length; i++) { + for (let j = 0; j < data[i].length; j += byteLen) { + output.push(parseInt(data[i].substr(j, byteLen), 16)); + } } return output; } diff --git a/src/core/lib/Sort.mjs b/src/core/lib/Sort.mjs index 46bbebd9..c8998f61 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 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/AESKeyUnwrap.mjs b/src/core/operations/AESKeyUnwrap.mjs new file mode 100644 index 00000000..1558847a --- /dev/null +++ b/src/core/operations/AESKeyUnwrap.mjs @@ -0,0 +1,128 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast } from "../lib/Hex.mjs"; +import forge from "node-forge"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * AES Key Unwrap operation + */ +class AESKeyUnwrap extends Operation { + + /** + * AESKeyUnwrap constructor + */ + constructor() { + super(); + + this.name = "AES Key Unwrap"; + this.module = "Ciphers"; + this.description = "Decryptor for a key wrapping algorithm defined in RFC3394, which is used to protect keys in untrusted storage or communications, using AES.

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

This algorithm uses an AES key (KEK: key-encryption key) and a 64-bit IV to encrypt 64-bit blocks."; + this.infoURL = "https://wikipedia.org/wiki/Key_wrap"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key (KEK)", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "IV", + "type": "toggleString", + "value": "a6a6a6a6a6a6a6a6", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Input", + "type": "option", + "value": ["Hex", "Raw"] + }, + { + "name": "Output", + "type": "option", + "value": ["Hex", "Raw"] + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const kek = Utils.convertToByteString(args[0].string, args[0].option), + iv = Utils.convertToByteString(args[1].string, args[1].option), + inputType = args[2], + outputType = args[3]; + + if (kek.length !== 16 && kek.length !== 24 && kek.length !== 32) { + throw new OperationError("KEK must be either 16, 24, or 32 bytes (currently " + kek.length + " bytes)"); + } + if (iv.length !== 8) { + throw new OperationError("IV must be 8 bytes (currently " + iv.length + " bytes)"); + } + const inputData = Utils.convertToByteString(input, inputType); + if (inputData.length % 8 !== 0 || inputData.length < 16) { + throw new OperationError("input must be 8n (n>=2) bytes (currently " + inputData.length + " bytes)"); + } + + const cipher = forge.cipher.createCipher("AES-ECB", kek); + + let A = iv; + const R = []; + for (let i = 0; i < inputData.length; i += 8) { + R.push(inputData.substring(i, i + 8)); + } + let cntLower = 1, cntUpper = 0; + for (let j = 0; j < 6; j++) { + for (let i = 0; i < R.length; i++) { + cipher.start(); + cipher.update(forge.util.createBuffer(A + R[i])); + cipher.finish(); + const B = cipher.output.getBytes(); + const msbBuffer = Utils.strToArrayBuffer(B.substring(0, 8)); + const msbView = new DataView(msbBuffer); + msbView.setUint32(0, msbView.getUint32(0) ^ cntUpper); + msbView.setUint32(4, msbView.getUint32(4) ^ cntLower); + A = Utils.arrayBufferToStr(msbBuffer, false); + R[i] = B.substring(8, 16); + cntLower++; + if (cntLower > 0xffffffff) { + cntUpper++; + cntLower = 0; + } + } + } + const C = A + R.join(""); + + if (outputType === "Hex") { + return toHexFast(Utils.strToArrayBuffer(C)); + } + return C; + } + +} + +export default AESKeyWrap; diff --git a/src/core/operations/AMFDecode.mjs b/src/core/operations/AMFDecode.mjs new file mode 100644 index 00000000..50a0d551 --- /dev/null +++ b/src/core/operations/AMFDecode.mjs @@ -0,0 +1,52 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import "reflect-metadata"; // Required as a shim for the amf library +import { AMF0, AMF3 } from "@astronautlabs/amf"; + +/** + * AMF Decode operation + */ +class AMFDecode extends Operation { + + /** + * AMFDecode constructor + */ + constructor() { + super(); + + this.name = "AMF Decode"; + this.module = "Encodings"; + this.description = "Action Message Format (AMF) is a binary format used to serialize object graphs such as ActionScript objects and XML, or send messages between an Adobe Flash client and a remote service, usually a Flash Media Server or third party alternatives."; + this.infoURL = "https://wikipedia.org/wiki/Action_Message_Format"; + this.inputType = "ArrayBuffer"; + this.outputType = "JSON"; + this.args = [ + { + name: "Format", + type: "option", + value: ["AMF0", "AMF3"], + defaultIndex: 1 + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {JSON} + */ + run(input, args) { + const [format] = args; + const handler = format === "AMF0" ? AMF0 : AMF3; + const encoded = new Uint8Array(input); + return handler.Value.deserialize(encoded); + } + +} + +export default AMFDecode; diff --git a/src/core/operations/AMFEncode.mjs b/src/core/operations/AMFEncode.mjs new file mode 100644 index 00000000..c21ba7dc --- /dev/null +++ b/src/core/operations/AMFEncode.mjs @@ -0,0 +1,52 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import "reflect-metadata"; // Required as a shim for the amf library +import { AMF0, AMF3 } from "@astronautlabs/amf"; + +/** + * AMF Encode operation + */ +class AMFEncode extends Operation { + + /** + * AMFEncode constructor + */ + constructor() { + super(); + + this.name = "AMF Encode"; + this.module = "Encodings"; + this.description = "Action Message Format (AMF) is a binary format used to serialize object graphs such as ActionScript objects and XML, or send messages between an Adobe Flash client and a remote service, usually a Flash Media Server or third party alternatives."; + this.infoURL = "https://wikipedia.org/wiki/Action_Message_Format"; + this.inputType = "JSON"; + this.outputType = "ArrayBuffer"; + this.args = [ + { + name: "Format", + type: "option", + value: ["AMF0", "AMF3"], + defaultIndex: 1 + } + ]; + } + + /** + * @param {JSON} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const [format] = args; + const handler = format === "AMF0" ? AMF0 : AMF3; + const output = handler.Value.any(input).serialize(); + return output.buffer; + } + +} + +export default AMFEncode; diff --git a/src/core/operations/CMAC.mjs b/src/core/operations/CMAC.mjs new file mode 100644 index 00000000..d6491362 --- /dev/null +++ b/src/core/operations/CMAC.mjs @@ -0,0 +1,149 @@ +/** + * @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 algo = args[1]; + + const info = (function() { + 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)"); + } + 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 !== 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]), + }; + 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, info.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/src/core/operations/ChaCha.mjs b/src/core/operations/ChaCha.mjs new file mode 100644 index 00000000..166c1663 --- /dev/null +++ b/src/core/operations/ChaCha.mjs @@ -0,0 +1,234 @@ +/** + * @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, 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. + +ChaCha uses a nonce of 8 or 12 bytes (64 or 96 bits).`); + } + counterLength = 16 - nonce.length; + } + counter = Utils.intToByteArray(args[2], counterLength, "little"); + + const output = []; + 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/src/core/operations/Fletcher32Checksum.mjs b/src/core/operations/Fletcher32Checksum.mjs index 29c74535..7b41ce55 100644 --- a/src/core/operations/Fletcher32Checksum.mjs +++ b/src/core/operations/Fletcher32Checksum.mjs @@ -35,10 +35,18 @@ class Fletcher32Checksum extends Operation { run(input, args) { let a = 0, b = 0; - input = new Uint8Array(input); + if (ArrayBuffer.isView(input)) { + input = new DataView(input.buffer, input.byteOffset, input.byteLength); + } else { + input = new DataView(input); + } - for (let i = 0; i < input.length; i++) { - a = (a + input[i]) % 0xffff; + for (let i = 0; i < input.byteLength - 1; i += 2) { + a = (a + input.getUint16(i, true)) % 0xffff; + b = (b + a) % 0xffff; + } + if (input.byteLength % 2 !== 0) { + a = (a + input.getUint8(input.byteLength - 1)) % 0xffff; b = (b + a) % 0xffff; } diff --git a/src/core/operations/Fletcher64Checksum.mjs b/src/core/operations/Fletcher64Checksum.mjs index 1d0d5bd9..68328ea3 100644 --- a/src/core/operations/Fletcher64Checksum.mjs +++ b/src/core/operations/Fletcher64Checksum.mjs @@ -35,10 +35,22 @@ class Fletcher64Checksum extends Operation { run(input, args) { let a = 0, b = 0; - input = new Uint8Array(input); + if (ArrayBuffer.isView(input)) { + input = new DataView(input.buffer, input.byteOffset, input.byteLength); + } else { + input = new DataView(input); + } - for (let i = 0; i < input.length; i++) { - a = (a + input[i]) % 0xffffffff; + for (let i = 0; i < input.byteLength - 3; i += 4) { + a = (a + input.getUint32(i, true)) % 0xffffffff; + b = (b + a) % 0xffffffff; + } + if (input.byteLength % 4 !== 0) { + let lastValue = 0; + for (let i = 0; i < input.byteLength % 4; i++) { + lastValue = (lastValue << 8) | input.getUint8(input.byteLength - 1 - i); + } + a = (a + lastValue) % 0xffffffff; b = (b + a) % 0xffffffff; } diff --git a/src/core/operations/FromBase85.mjs b/src/core/operations/FromBase85.mjs index f9b37c74..d0c70da5 100644 --- a/src/core/operations/FromBase85.mjs +++ b/src/core/operations/FromBase85.mjs @@ -7,7 +7,7 @@ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import Utils from "../Utils.mjs"; -import {alphabetName, ALPHABET_OPTIONS} from "../lib/Base85.mjs"; +import {ALPHABET_OPTIONS} from "../lib/Base85.mjs"; /** * From Base85 operation @@ -37,6 +37,12 @@ class FromBase85 extends Operation { type: "boolean", value: true }, + { + name: "All-zero group char", + type: "binaryShortString", + value: "z", + maxLength: 1 + } ]; this.checks = [ { @@ -76,8 +82,8 @@ class FromBase85 extends Operation { */ run(input, args) { const alphabet = Utils.expandAlphRange(args[0]).join(""), - encoding = alphabetName(alphabet), removeNonAlphChars = args[1], + allZeroGroupChar = typeof args[2] === "string" ? args[2].slice(0, 1) : "", result = []; if (alphabet.length !== 85 || @@ -85,14 +91,21 @@ 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]; // Remove non-alphabet characters if (removeNonAlphChars) { - const re = new RegExp("[^" + 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(/^<~(.+?)~>$/); + if (matches !== null) input = matches[1]; } if (input.length === 0) return []; @@ -100,7 +113,7 @@ class FromBase85 extends Operation { let i = 0; let block, blockBytes; while (i < input.length) { - if (encoding === "Standard" && input[i] === "z") { + if (input[i] === allZeroGroupChar) { result.push(0, 0, 0, 0); i++; } else { @@ -110,7 +123,7 @@ class FromBase85 extends Operation { .split("") .map((chr, idx) => { const digit = alphabet.indexOf(chr); - if (digit < 0 || digit > 84) { + if ((digit < 0 || digit > 84) && chr !== allZeroGroupChar) { throw `Invalid character '${chr}' at index ${i + idx}`; } return digit; diff --git a/src/core/operations/GenerateQRCode.mjs b/src/core/operations/GenerateQRCode.mjs index 080a24da..d3e1ee3b 100644 --- a/src/core/operations/GenerateQRCode.mjs +++ b/src/core/operations/GenerateQRCode.mjs @@ -44,7 +44,7 @@ class GenerateQRCode extends Operation { { "name": "Margin (num modules)", "type": "number", - "value": 2, + "value": 4, "min": 0 }, { diff --git a/src/core/operations/LZ4Compress.mjs b/src/core/operations/LZ4Compress.mjs new file mode 100644 index 00000000..1f43785f --- /dev/null +++ b/src/core/operations/LZ4Compress.mjs @@ -0,0 +1,43 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import lz4 from "lz4js"; + +/** + * LZ4 Compress operation + */ +class LZ4Compress extends Operation { + + /** + * LZ4Compress constructor + */ + constructor() { + super(); + + this.name = "LZ4 Compress"; + this.module = "Compression"; + this.description = "LZ4 is a lossless data compression algorithm that is focused on compression and decompression speed. It belongs to the LZ77 family of byte-oriented compression schemes."; + this.infoURL = "https://wikipedia.org/wiki/LZ4_(compression_algorithm)"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const inBuf = new Uint8Array(input); + const compressed = lz4.compress(inBuf); + return compressed.buffer; + } + +} + +export default LZ4Compress; diff --git a/src/core/operations/LZ4Decompress.mjs b/src/core/operations/LZ4Decompress.mjs new file mode 100644 index 00000000..2ba50416 --- /dev/null +++ b/src/core/operations/LZ4Decompress.mjs @@ -0,0 +1,43 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import lz4 from "lz4js"; + +/** + * LZ4 Decompress operation + */ +class LZ4Decompress extends Operation { + + /** + * LZ4Decompress constructor + */ + constructor() { + super(); + + this.name = "LZ4 Decompress"; + this.module = "Compression"; + this.description = "LZ4 is a lossless data compression algorithm that is focused on compression and decompression speed. It belongs to the LZ77 family of byte-oriented compression schemes."; + this.infoURL = "https://wikipedia.org/wiki/LZ4_(compression_algorithm)"; + this.inputType = "ArrayBuffer"; + this.outputType = "ArrayBuffer"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {ArrayBuffer} + */ + run(input, args) { + const inBuf = new Uint8Array(input); + const decompressed = lz4.decompress(inBuf); + return decompressed.buffer; + } + +} + +export default LZ4Decompress; diff --git a/src/core/operations/ParseASN1HexString.mjs b/src/core/operations/ParseASN1HexString.mjs index a19ca70c..14890186 100644 --- a/src/core/operations/ParseASN1HexString.mjs +++ b/src/core/operations/ParseASN1HexString.mjs @@ -45,8 +45,8 @@ class ParseASN1HexString extends Operation { */ run(input, args) { const [index, truncateLen] = args; - return r.ASN1HEX.dump(input.replace(/\s/g, ""), { - "ommitLongOctet": truncateLen + return r.ASN1HEX.dump(input.replace(/\s/g, "").toLowerCase(), { + "ommit_long_octet": truncateLen }, index); } diff --git a/src/core/operations/ParseX509Certificate.mjs b/src/core/operations/ParseX509Certificate.mjs index aeb5f677..88678de9 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, "").toLowerCase(); + 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(), diff --git a/src/core/operations/PseudoRandomNumberGenerator.mjs b/src/core/operations/PseudoRandomNumberGenerator.mjs index 033aa859..53150566 100644 --- a/src/core/operations/PseudoRandomNumberGenerator.mjs +++ b/src/core/operations/PseudoRandomNumberGenerator.mjs @@ -52,8 +52,12 @@ class PseudoRandomNumberGenerator extends Operation { let bytes; if (isWorkerEnvironment() && self.crypto) { - bytes = self.crypto.getRandomValues(new Uint8Array(numBytes)); - bytes = Utils.arrayBufferToStr(bytes.buffer); + bytes = new ArrayBuffer(numBytes); + const CHUNK_SIZE = 65536; + for (let i = 0; i < numBytes; i += CHUNK_SIZE) { + self.crypto.getRandomValues(new Uint8Array(bytes, i, Math.min(numBytes - i, CHUNK_SIZE))); + } + bytes = Utils.arrayBufferToStr(bytes); } else { bytes = forge.random.getBytesSync(numBytes); } diff --git a/src/core/operations/Rabbit.mjs b/src/core/operations/Rabbit.mjs new file mode 100644 index 00000000..91ff24a3 --- /dev/null +++ b/src/core/operations/Rabbit.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 operation + */ +class Rabbit extends Operation { + + /** + * Rabbit constructor + */ + constructor() { + super(); + + this.name = "Rabbit"; + this.module = "Ciphers"; + 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"; + 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 = new Uint32Array(8), C = new Uint32Array(8); + 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; + } + }; + + // 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; + }; + 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); + }; + const nextStateHelper2 = function(v0, v1, v2) { + return v0 + leftRotate(v1, 8) + v2; + }; + const G = new Uint32Array(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 = new Uint16Array(8); + if (littleEndian) { + for (let i = 0; i < 8; i++) { + K[i] = (key[1 + 2 * i] << 8) | key[2 * i]; + } + } else { + for (let i = 0; i < 8; i++) { + K[i] = (key[14 - 2 * i] << 8) | key[15 - 2 * i]; + } + } + for (let j = 0; j < 8; j++) { + if (j % 2 === 0) { + X[j] = (K[(j + 1) % 8] << 16) | K[j]; + C[j] = (K[(j + 4) % 8] << 16) | K[(j + 5) % 8]; + } else { + 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++) { + counterUpdate(); + nextState(); + } + for (let j = 0; j < 8; j++) { + 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]; + } else { + 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); + 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(); + } + } + + // Extraction Scheme + const S = new Uint8Array(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 Rabbit; diff --git a/src/core/operations/Reverse.mjs b/src/core/operations/Reverse.mjs index 895d6723..49c752a8 100644 --- a/src/core/operations/Reverse.mjs +++ b/src/core/operations/Reverse.mjs @@ -5,6 +5,7 @@ */ import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; /** * Reverse operation @@ -26,7 +27,8 @@ class Reverse extends Operation { { "name": "By", "type": "option", - "value": ["Character", "Line"] + "value": ["Byte", "Character", "Line"], + "defaultIndex": 1 } ]; } @@ -57,6 +59,24 @@ class Reverse extends Operation { result.push(0x0a); } return result.slice(0, input.length); + } else if (args[0] === "Character") { + const inputString = Utils.byteArrayToUtf8(input); + let result = ""; + for (let i = inputString.length - 1; i >= 0; i--) { + const c = inputString.charCodeAt(i); + if (i > 0 && 0xdc00 <= c && c <= 0xdfff) { + const c2 = inputString.charCodeAt(i - 1); + if (0xd800 <= c2 && c2 <= 0xdbff) { + // surrogates + result += inputString.charAt(i - 1); + result += inputString.charAt(i); + i--; + continue; + } + } + result += inputString.charAt(i); + } + return Utils.strToUtf8ByteArray(result); } else { return input.reverse(); } diff --git a/src/core/operations/Shuffle.mjs b/src/core/operations/Shuffle.mjs new file mode 100644 index 00000000..14c4ffab --- /dev/null +++ b/src/core/operations/Shuffle.mjs @@ -0,0 +1,78 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import {INPUT_DELIM_OPTIONS} from "../lib/Delim.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 = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Delimiter", + type: "option", + value: INPUT_DELIM_OPTIONS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + 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() { + 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); + }; + + // Split input into shuffleable sections + const toShuffle = input.split(delim); + + // shuffle elements + 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; + } + + return toShuffle.join(delim); + } + +} + +export default Shuffle; diff --git a/src/core/operations/Sort.mjs b/src/core/operations/Sort.mjs index 19e4cbb2..7a4714be 100644 --- a/src/core/operations/Sort.mjs +++ b/src/core/operations/Sort.mjs @@ -7,7 +7,7 @@ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import {INPUT_DELIM_OPTIONS} from "../lib/Delim.mjs"; -import {caseInsensitiveSort, ipSort, numericSort, hexadecimalSort} from "../lib/Sort.mjs"; +import {caseInsensitiveSort, ipSort, numericSort, hexadecimalSort, lengthSort} from "../lib/Sort.mjs"; /** * Sort operation @@ -39,7 +39,7 @@ class Sort extends Operation { { "name": "Order", "type": "option", - "value": ["Alphabetical (case sensitive)", "Alphabetical (case insensitive)", "IP address", "Numeric", "Numeric (hexadecimal)"] + "value": ["Alphabetical (case sensitive)", "Alphabetical (case insensitive)", "IP address", "Numeric", "Numeric (hexadecimal)", "Length"] } ]; } @@ -65,6 +65,8 @@ class Sort extends Operation { sorted = sorted.sort(numericSort); } else if (order === "Numeric (hexadecimal)") { sorted = sorted.sort(hexadecimalSort); + } else if (order === "Length") { + sorted = sorted.sort(lengthSort); } if (sortReverse) sorted.reverse(); diff --git a/src/core/operations/Substitute.mjs b/src/core/operations/Substitute.mjs index 1afe1f42..4a8cdc5d 100644 --- a/src/core/operations/Substitute.mjs +++ b/src/core/operations/Substitute.mjs @@ -34,10 +34,50 @@ 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 @@ -45,17 +85,23 @@ class Substitute extends Operation { */ run(input, args) { const plaintext = Utils.expandAlphRange([...args[0]]), - ciphertext = Utils.expandAlphRange([...args[1]]); - let output = "", - index = -1; + ciphertext = Utils.expandAlphRange([...args[1]]), + ignoreCase = args[2]; + let output = ""; 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++) { + dict[plaintext[i]] = ciphertext[i]; + } + + // map every letter with the conversion function for (const character of input) { - index = plaintext.indexOf(character); - output += index > -1 && index < ciphertext.length ? ciphertext[index] : character; + output += this.cipherSingleChar(character, dict, ignoreCase); } return output; diff --git a/src/core/operations/ToTable.mjs b/src/core/operations/ToTable.mjs index 91b07771..ff69023f 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,9 @@ 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 +186,59 @@ class ToTable extends Operation { return output; } } + + /** + * Outputs an array of data as a Markdown table. + * + * @param {string[][]} tableData + * @returns {string} + */ + 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; + } + + } + } } diff --git a/src/core/operations/TripleDESDecrypt.mjs b/src/core/operations/TripleDESDecrypt.mjs index 7626eeab..8487509f 100644 --- a/src/core/operations/TripleDESDecrypt.mjs +++ b/src/core/operations/TripleDESDecrypt.mjs @@ -70,7 +70,7 @@ class TripleDESDecrypt extends Operation { inputType = args[3], outputType = args[4]; - if (key.length !== 24) { + if (key.length !== 24 && key.length !== 16) { throw new OperationError(`Invalid key length: ${key.length} bytes Triple DES uses a key length of 24 bytes (192 bits). @@ -85,7 +85,8 @@ Make sure you have specified the type correctly (e.g. Hex vs UTF8).`); input = Utils.convertToByteString(input, inputType); - const decipher = forge.cipher.createDecipher("3DES-" + mode, key); + const decipher = forge.cipher.createDecipher("3DES-" + mode, + key.length === 16 ? key + key.substring(0, 8) : key); /* Allow for a "no padding" mode */ if (noPadding) { diff --git a/src/core/operations/TripleDESEncrypt.mjs b/src/core/operations/TripleDESEncrypt.mjs index 237020a2..720d155d 100644 --- a/src/core/operations/TripleDESEncrypt.mjs +++ b/src/core/operations/TripleDESEncrypt.mjs @@ -69,7 +69,7 @@ class TripleDESEncrypt extends Operation { inputType = args[3], outputType = args[4]; - if (key.length !== 24) { + if (key.length !== 24 && key.length !== 16) { throw new OperationError(`Invalid key length: ${key.length} bytes Triple DES uses a key length of 24 bytes (192 bits). @@ -84,7 +84,8 @@ Make sure you have specified the type correctly (e.g. Hex vs UTF8).`); input = Utils.convertToByteString(input, inputType); - const cipher = forge.cipher.createCipher("3DES-" + mode, key); + const cipher = forge.cipher.createCipher("3DES-" + mode, + key.length === 16 ? key + key.substring(0, 8) : key); cipher.start({iv: iv}); cipher.update(forge.util.createBuffer(input)); cipher.finish(); diff --git a/src/core/operations/UNIXTimestampToWindowsFiletime.mjs b/src/core/operations/UNIXTimestampToWindowsFiletime.mjs index 1ccd3298..ad88fb97 100644 --- a/src/core/operations/UNIXTimestampToWindowsFiletime.mjs +++ b/src/core/operations/UNIXTimestampToWindowsFiletime.mjs @@ -79,6 +79,9 @@ class UNIXTimestampToWindowsFiletime extends Operation { flipped += result.charAt(i); flipped += result.charAt(i + 1); } + if (result.length % 2 !== 0) { + flipped += "0" + result.charAt(0); + } result = flipped; } diff --git a/src/core/operations/ViewBitPlane.mjs b/src/core/operations/ViewBitPlane.mjs index 859e6868..e1e5f6df 100644 --- a/src/core/operations/ViewBitPlane.mjs +++ b/src/core/operations/ViewBitPlane.mjs @@ -90,7 +90,7 @@ class ViewBitPlane extends Operation { * @returns {html} */ present(data) { - if (!data.length) return ""; + if (!data.byteLength) return ""; const type = isImage(data); return ``; diff --git a/src/core/operations/WindowsFiletimeToUNIXTimestamp.mjs b/src/core/operations/WindowsFiletimeToUNIXTimestamp.mjs index 7411a6e9..786cbcce 100644 --- a/src/core/operations/WindowsFiletimeToUNIXTimestamp.mjs +++ b/src/core/operations/WindowsFiletimeToUNIXTimestamp.mjs @@ -52,7 +52,10 @@ class WindowsFiletimeToUNIXTimestamp extends Operation { if (format === "Hex (little endian)") { // Swap endianness let result = ""; - for (let i = input.length - 2; i >= 0; i -= 2) { + if (input.length % 2 !== 0) { + result += input.charAt(input.length - 1); + } + for (let i = input.length - input.length % 2 - 2; i >= 0; i -= 2) { result += input.charAt(i); result += input.charAt(i + 1); } diff --git a/src/web/HTMLIngredient.mjs b/src/web/HTMLIngredient.mjs index 2c638405..6d9ccdbf 100755 --- a/src/web/HTMLIngredient.mjs +++ b/src/web/HTMLIngredient.mjs @@ -30,6 +30,7 @@ class HTMLIngredient { this.rows = config.rows || false; this.target = config.target; this.defaultIndex = config.defaultIndex || 0; + this.maxLength = config.maxLength || null; this.toggleValues = config.toggleValues; this.ingId = this.app.nextIngId(); this.id = "ing-" + this.ingId; @@ -63,7 +64,8 @@ class HTMLIngredient { tabindex="${this.tabIndex}" arg-name="${this.name}" value="${this.value}" - ${this.disabled ? "disabled" : ""}> + ${this.disabled ? "disabled" : ""} + ${this.maxLength ? `maxlength="${this.maxLength}"` : ""}> `; break; case "shortString": @@ -78,7 +80,8 @@ class HTMLIngredient { tabindex="${this.tabIndex}" arg-name="${this.name}" value="${this.value}" - ${this.disabled ? "disabled" : ""}> + ${this.disabled ? "disabled" : ""} + ${this.maxLength ? `maxlength="${this.maxLength}"` : ""}> `; break; case "toggleString": @@ -93,7 +96,8 @@ class HTMLIngredient { tabindex="${this.tabIndex}" arg-name="${this.name}" value="${this.value}" - ${this.disabled ? "disabled" : ""}> + ${this.disabled ? "disabled" : ""} + ${this.maxLength ? `maxlength="${this.maxLength}"` : ""}>
diff --git a/tests/node/tests/operations.mjs b/tests/node/tests/operations.mjs index 783bd00a..8611ecb4 100644 --- a/tests/node/tests/operations.mjs +++ b/tests/node/tests/operations.mjs @@ -45,10 +45,10 @@ TestRegister.addApiTests([ const result = chef.ADD("sample input", { key: { string: "some key", - option: "Hex" + option: "utf8" } }); - assert.equal(result.toString(), "aO[^ZS\u000eW\\^cb"); + assert.equal(result.toString(), "\xe6\xd0\xda\xd5\x8c\xd0\x85\xe2\xe1\xdf\xe2\xd9"); }), @@ -121,10 +121,10 @@ Tiger-128`; const result = chef.AND("Scot-free", { key: { string: "Raining Cats and Dogs", - option: "Hex", + option: "utf8", } }); - assert.strictEqual(result.toString(), "\u0000\"M$(D E"); + assert.strictEqual(result.toString(), "Raid)fb A"); }), it("atBash Cipher", () => { @@ -371,10 +371,10 @@ color: white; }, salt: { string: "Market", - option: "Hex", + option: "utf8", }, }); - assert.strictEqual(result.toString(), "7c21a9f5063a4d62fb1050068245c181"); + assert.strictEqual(result.toString(), "4930d5d200e80f18c96b5550d13c6af8"); }), it("Derive PBKDF2 Key", () => { diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 19e70970..7a3361f2 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"; @@ -124,6 +125,11 @@ import "./tests/UnescapeString.mjs"; import "./tests/LS47.mjs"; import "./tests/LZString.mjs"; import "./tests/NTLM.mjs"; +import "./tests/Shuffle.mjs"; +import "./tests/FletcherChecksum.mjs"; +import "./tests/CMAC.mjs"; +import "./tests/AESKeyWrap.mjs"; +import "./tests/Rabbit.mjs"; // Cannot test operations that use the File type yet // import "./tests/SplitColourChannels.mjs"; diff --git a/tests/operations/tests/AESKeyWrap.mjs b/tests/operations/tests/AESKeyWrap.mjs new file mode 100644 index 00000000..bca36a40 --- /dev/null +++ b/tests/operations/tests/AESKeyWrap.mjs @@ -0,0 +1,324 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + "name": "AES Key Wrap: RFC Test Vector, 128-bit data, 128-bit KEK", + "input": "00112233445566778899aabbccddeeff", + "expectedOutput": "1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5", + "recipeConfig": [ + { + "op": "AES Key Wrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Wrap: RFC Test Vector, 128-bit data, 192-bit KEK", + "input": "00112233445566778899aabbccddeeff", + "expectedOutput": "96778b25ae6ca435f92b5b97c050aed2468ab8a17ad84e5d", + "recipeConfig": [ + { + "op": "AES Key Wrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f1011121314151617"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Wrap: RFC Test Vector, 128-bit data, 256-bit KEK", + "input": "00112233445566778899aabbccddeeff", + "expectedOutput": "64e8c3f9ce0f5ba263e9777905818a2a93c8191e7d6e8ae7", + "recipeConfig": [ + { + "op": "AES Key Wrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Wrap: RFC Test Vector, 192-bit data, 192-bit KEK", + "input": "00112233445566778899aabbccddeeff0001020304050607", + "expectedOutput": "031d33264e15d33268f24ec260743edce1c6c7ddee725a936ba814915c6762d2", + "recipeConfig": [ + { + "op": "AES Key Wrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f1011121314151617"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Wrap: RFC Test Vector, 192-bit data, 256-bit KEK", + "input": "00112233445566778899aabbccddeeff0001020304050607", + "expectedOutput": "a8f9bc1612c68b3ff6e6f4fbe30e71e4769c8b80a32cb8958cd5d17d6b254da1", + "recipeConfig": [ + { + "op": "AES Key Wrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Wrap: RFC Test Vector, 256-bit data, 256-bit KEK", + "input": "00112233445566778899aabbccddeeff000102030405060708090a0b0c0d0e0f", + "expectedOutput": "28c9f404c4b810f4cbccb35cfb87f8263f5786e2d80ed326cbc7f0e71a99f43bfb988b9b7a02dd21", + "recipeConfig": [ + { + "op": "AES Key Wrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: RFC Test Vector, 128-bit data, 128-bit KEK", + "input": "1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5", + "expectedOutput": "00112233445566778899aabbccddeeff", + "recipeConfig": [ + { + "op": "AES Key Unwrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: RFC Test Vector, 128-bit data, 192-bit KEK", + "input": "96778b25ae6ca435f92b5b97c050aed2468ab8a17ad84e5d", + "expectedOutput": "00112233445566778899aabbccddeeff", + "recipeConfig": [ + { + "op": "AES Key Unwrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f1011121314151617"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: RFC Test Vector, 128-bit data, 256-bit KEK", + "input": "64e8c3f9ce0f5ba263e9777905818a2a93c8191e7d6e8ae7", + "expectedOutput": "00112233445566778899aabbccddeeff", + "recipeConfig": [ + { + "op": "AES Key Unwrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: RFC Test Vector, 192-bit data, 192-bit KEK", + "input": "031d33264e15d33268f24ec260743edce1c6c7ddee725a936ba814915c6762d2", + "expectedOutput": "00112233445566778899aabbccddeeff0001020304050607", + "recipeConfig": [ + { + "op": "AES Key Unwrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f1011121314151617"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: RFC Test Vector, 192-bit data, 256-bit KEK", + "input": "a8f9bc1612c68b3ff6e6f4fbe30e71e4769c8b80a32cb8958cd5d17d6b254da1", + "expectedOutput": "00112233445566778899aabbccddeeff0001020304050607", + "recipeConfig": [ + { + "op": "AES Key Unwrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: RFC Test Vector, 256-bit data, 256-bit KEK", + "input": "28c9f404c4b810f4cbccb35cfb87f8263f5786e2d80ed326cbc7f0e71a99f43bfb988b9b7a02dd21", + "expectedOutput": "00112233445566778899aabbccddeeff000102030405060708090a0b0c0d0e0f", + "recipeConfig": [ + { + "op": "AES Key Unwrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Wrap: invalid KEK length", + "input": "00112233445566778899aabbccddeeff", + "expectedOutput": "KEK must be either 16, 24, or 32 bytes (currently 10 bytes)", + "recipeConfig": [ + { + "op": "AES Key Wrap", + "args": [ + {"option": "Hex", "string": "00010203040506070809"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Wrap: invalid IV length", + "input": "00112233445566778899aabbccddeeff", + "expectedOutput": "IV must be 8 bytes (currently 6 bytes)", + "recipeConfig": [ + { + "op": "AES Key Wrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Wrap: input length not multiple of 8", + "input": "00112233445566778899aabbccddeeff0102", + "expectedOutput": "input must be 8n (n>=2) bytes (currently 18 bytes)", + "recipeConfig": [ + { + "op": "AES Key Wrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Wrap: input too short", + "input": "0011223344556677", + "expectedOutput": "input must be 8n (n>=2) bytes (currently 8 bytes)", + "recipeConfig": [ + { + "op": "AES Key Wrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: invalid KEK length", + "input": "1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5", + "expectedOutput": "KEK must be either 16, 24, or 32 bytes (currently 10 bytes)", + "recipeConfig": [ + { + "op": "AES Key Unwrap", + "args": [ + {"option": "Hex", "string": "00010203040506070809"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: invalid IV length", + "input": "1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5", + "expectedOutput": "IV must be 8 bytes (currently 6 bytes)", + "recipeConfig": [ + { + "op": "AES Key Unwrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: input length not multiple of 8", + "input": "1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5e621", + "expectedOutput": "input must be 8n (n>=3) bytes (currently 26 bytes)", + "recipeConfig": [ + { + "op": "AES Key Unwrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: input too short", + "input": "1fa68b0a8112b447aef34bd8fb5a7b82", + "expectedOutput": "input must be 8n (n>=3) bytes (currently 16 bytes)", + "recipeConfig": [ + { + "op": "AES Key Unwrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, + { + "name": "AES Key Unwrap: corrupted input", + "input": "1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe6", + "expectedOutput": "IV mismatch", + "recipeConfig": [ + { + "op": "AES Key Unwrap", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "a6a6a6a6a6a6a6a6"}, + "Hex", "Hex" + ], + }, + ], + }, +]); diff --git a/tests/operations/tests/Base85.mjs b/tests/operations/tests/Base85.mjs index 8e1d0a23..44e12ee2 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 = "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([ { name: "To Base85", @@ -45,4 +49,22 @@ TestRegister.addTests([ "args": ["!-u", false] } ] }, + { + name: "To Base85", + input: allZeroExample, + expectedOutput: allZeroOutput, + recipeConfig: [ + { "op": "To Base85", + "args": ["!-u"] } + ] + }, + { + name: "From Base85", + input: allZeroOutput, + expectedOutput: allZeroExample, + recipeConfig: [ + { "op": "From Base85", + "args": ["!-u", true, "z"] } + ] + }, ]); diff --git a/tests/operations/tests/CMAC.mjs b/tests/operations/tests/CMAC.mjs new file mode 100644 index 00000000..00fc7c9a --- /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 16 or 24 bytes (currently 20 bytes)", + "recipeConfig": [ + { + "op": "CMAC", + "args": [{"option": "Hex", "string": "00112233445566778899aabbccddeeff01234567"}, "Triple DES"] + }, + ] + }, +]); diff --git a/tests/operations/tests/ChaCha.mjs b/tests/operations/tests/ChaCha.mjs new file mode 100644 index 00000000..44b39a67 --- /dev/null +++ b/tests/operations/tests/ChaCha.mjs @@ -0,0 +1,151 @@ +/** + * ChaCha tests. + * + * @author joostrijneveld [joost@joostrijneveld.nl] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "ChaCha: no key", + input: "", + expectedOutput: `Invalid key length: 0 bytes. + +ChaCha uses a key of 16 or 32 bytes (128 or 256 bits).`, + recipeConfig: [ + { + "op": "ChaCha", + "args": [ + {"option": "Hex", "string": ""}, + {"option": "Hex", "string": ""}, + 0, "20", "Hex", "Hex", + ] + } + ], + }, + { + name: "ChaCha: no nonce", + input: "", + expectedOutput: `Invalid nonce length: 0 bytes. + +ChaCha uses a nonce of 8 or 12 bytes (64 or 96 bits).`, + recipeConfig: [ + { + "op": "ChaCha", + "args": [ + {"option": "Hex", "string": "00000000000000000000000000000000"}, + {"option": "Hex", "string": ""}, + 0, "20", "Hex", "Hex", + ] + } + ], + }, + { + name: "ChaCha: RFC8439", + input: "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, sunscreen would be it.", + expectedOutput: "6e 2e 35 9a 25 68 f9 80 41 ba 07 28 dd 0d 69 81 e9 7e 7a ec 1d 43 60 c2 0a 27 af cc fd 9f ae 0b f9 1b 65 c5 52 47 33 ab 8f 59 3d ab cd 62 b3 57 16 39 d6 24 e6 51 52 ab 8f 53 0c 35 9f 08 61 d8 07 ca 0d bf 50 0d 6a 61 56 a3 8e 08 8a 22 b6 5e 52 bc 51 4d 16 cc f8 06 81 8c e9 1a b7 79 37 36 5a f9 0b bf 74 a3 5b e6 b4 0b 8e ed f2 78 5e 42 87 4d", + recipeConfig: [ + { + "op": "ChaCha", + "args": [ + {"option": "Hex", "string": "00:01:02:03:04:05:06:07:08:09:0a:0b:0c:0d:0e:0f:10:11:12:13:14:15:16:17:18:19:1a:1b:1c:1d:1e:1f"}, + {"option": "Hex", "string": "00:00:00:00:00:00:00:4a:00:00:00:00"}, + 1, "20", "Raw", "Hex", + ] + } + ], + }, + { + name: "ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.1", + input: "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", + expectedOutput: "29 56 0d 28 0b 45 28 40 0a 8f 4b 79 53 69 fb 3a 01 10 55 99 e9 f1 ed 58 27 9c fc 9e ce 2d c5 f9 9f 1c 2e 52 c9 82 38 f5 42 a5 c0 a8 81 d8 50 b6 15 d3 ac d9 fb db 02 6e 93 68 56 5d a5 0e 0d 49 dd 5b e8 ef 74 24 8b 3e 25 1d 96 5d 8f cb 21 e7 cf e2 04 d4 00 78 06 fb ee 3c e9 4c 74 bf ba d2 c1 1c 62 1b a0 48 14 7c 5c aa 94 d1 82 cc ff 6f d5 cf 44 ad f9 6e 3d 68 28 1b b4 96 76 af 87 e7", + recipeConfig: [ + { + "op": "ChaCha", + "args": [ + {"option": "Hex", "string": "00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff"}, + {"option": "Hex", "string": "0f 1e 2d 3c 4b 5a 69 78"}, + 0, "8", "Hex", "Hex", + ] + } + ], + }, + { + name: "ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.2", + input: "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", + expectedOutput: "5e dd c2 d9 42 8f ce ee c5 0a 52 a9 64 ea e0 ff b0 4b 2d e0 06 a9 b0 4c ff 36 8f fa 92 11 16 b2 e8 e2 64 ba bd 2e fa 0d e4 3e f2 e3 b6 d0 65 e8 f7 c0 a1 78 37 b0 a4 0e b0 e2 c7 a3 74 2c 87 53 ed e5 f3 f6 d1 9b e5 54 67 5e 50 6a 77 5c 63 f0 94 d4 96 5c 31 93 19 dc d7 50 6f 45 7b 11 7b 84 b1 0b 24 6e 95 6c 2d a8 89 8a 65 6c ee f3 f7 b7 16 45 b1 9f 70 1d b8 44 85 ce 51 21 f0 f6 17 ef", + recipeConfig: [ + { + "op": "ChaCha", + "args": [ + {"option": "Hex", "string": "00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff"}, + {"option": "Hex", "string": "0f 1e 2d 3c 4b 5a 69 78"}, + 0, "12", "Hex", "Hex", + ] + } + ], + }, + { + name: "ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.3", + input: "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", + expectedOutput: "d1 ab f6 30 46 7e b4 f6 7f 1c fb 47 cd 62 6a ae 8a fe db be 4f f8 fc 5f e9 cf ae 30 7e 74 ed 45 1f 14 04 42 5a d2 b5 45 69 d5 f1 81 48 93 99 71 ab b8 fa fc 88 ce 4a c7 fe 1c 3d 1f 7a 1e b7 ca e7 6c a8 7b 61 a9 71 35 41 49 77 60 dd 9a e0 59 35 0c ad 0d ce df aa 80 a8 83 11 9a 1a 6f 98 7f d1 ce 91 fd 8e e0 82 80 34 b4 11 20 0a 97 45 a2 85 55 44 75 d1 2a fc 04 88 7f ef 35 16 d1 2a 2c", + recipeConfig: [ + { + "op": "ChaCha", + "args": [ + {"option": "Hex", "string": "00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff"}, + {"option": "Hex", "string": "0f 1e 2d 3c 4b 5a 69 78"}, + 0, "20", "Hex", "Hex", + ] + } + ], + }, + { + name: "ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.4", + input: "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", + expectedOutput: "db 43 ad 9d 1e 84 2d 12 72 e4 53 0e 27 6b 3f 56 8f 88 59 b3 f7 cf 6d 9d 2c 74 fa 53 80 8c b5 15 7a 8e bf 46 ad 3d cc 4b 6c 7d ad de 13 17 84 b0 12 0e 0e 22 f6 d5 f9 ff a7 40 7d 4a 21 b6 95 d9 c5 dd 30 bf 55 61 2f ab 9b dd 11 89 20 c1 98 16 47 0c 7f 5d cd 42 32 5d bb ed 8c 57 a5 62 81 c1 44 cb 0f 03 e8 1b 30 04 62 4e 06 50 a1 ce 5a fa f9 a7 cd 81 63 f6 db d7 26 02 25 7d d9 6e 47 1e", + recipeConfig: [ + { + "op": "ChaCha", + "args": [ + {"option": "Hex", "string": "00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff ff ee dd cc bb aa 99 88 77 66 55 44 33 22 11 00"}, + {"option": "Hex", "string": "0f 1e 2d 3c 4b 5a 69 78"}, + 0, "8", "Hex", "Hex", + ] + } + ], + }, + { + name: "ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.5", + input: "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", + expectedOutput: "7e d1 2a 3a 63 91 2a e9 41 ba 6d 4c 0d 5e 86 2e 56 8b 0e 55 89 34 69 35 50 5f 06 4b 8c 26 98 db f7 d8 50 66 7d 8e 67 be 63 9f 3b 4f 6a 16 f9 2e 65 ea 80 f6 c7 42 94 45 da 1f c2 c1 b9 36 50 40 e3 2e 50 c4 10 6f 3b 3d a1 ce 7c cb 1e 71 40 b1 53 49 3c 0f 3a d9 a9 bc ff 07 7e c4 59 6f 1d 0f 29 bf 9c ba a5 02 82 0f 73 2a f5 a9 3c 49 ee e3 3d 1c 4f 12 af 3b 42 97 af 91 fe 41 ea 9e 94 a2", + recipeConfig: [ + { + "op": "ChaCha", + "args": [ + {"option": "Hex", "string": "00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff ff ee dd cc bb aa 99 88 77 66 55 44 33 22 11 00"}, + {"option": "Hex", "string": "0f 1e 2d 3c 4b 5a 69 78"}, + 0, "12", "Hex", "Hex", + ] + } + ], + }, + { + name: "ChaCha: draft-strombergson-chacha-test-vectors-01 TC7.6", + input: "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00", + expectedOutput: "9f ad f4 09 c0 08 11 d0 04 31 d6 7e fb d8 8f ba 59 21 8d 5d 67 08 b1 d6 85 86 3f ab bb 0e 96 1e ea 48 0f d6 fb 53 2b fd 49 4b 21 51 01 50 57 42 3a b6 0a 63 fe 4f 55 f7 a2 12 e2 16 7c ca b9 31 fb fd 29 cf 7b c1 d2 79 ed df 25 dd 31 6b b8 84 3d 6e de e0 bd 1e f1 21 d1 2f a1 7c bc 2c 57 4c cc ab 5e 27 51 67 b0 8b d6 86 f8 a0 9d f8 7e c3 ff b3 53 61 b9 4e bf a1 3f ec 0e 48 89 d1 8d a5", + recipeConfig: [ + { + "op": "ChaCha", + "args": [ + {"option": "Hex", "string": "00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff ff ee dd cc bb aa 99 88 77 66 55 44 33 22 11 00"}, + {"option": "Hex", "string": "0f 1e 2d 3c 4b 5a 69 78"}, + 0, "20", "Hex", "Hex", + ] + } + ], + }, +]); diff --git a/tests/operations/tests/Compress.mjs b/tests/operations/tests/Compress.mjs index 015277b1..60117c67 100644 --- a/tests/operations/tests/Compress.mjs +++ b/tests/operations/tests/Compress.mjs @@ -75,4 +75,34 @@ TestRegister.addTests([ } ], }, + { + name: "LZ4 Compress", + input: "The cat sat on the mat.", + expectedOutput: "04224d184070df170000805468652063617420736174206f6e20746865206d61742e00000000", + recipeConfig: [ + { + "op": "LZ4 Compress", + "args": [] + }, + { + "op": "To Hex", + "args": ["None", 0] + } + ], + }, + { + name: "LZ4 Decompress", + input: "04224d184070df170000805468652063617420736174206f6e20746865206d61742e00000000", + expectedOutput: "The cat sat on the mat.", + recipeConfig: [ + { + "op": "From Hex", + "args": ["None"] + }, + { + "op": "LZ4 Decompress", + "args": [] + } + ], + }, ]); diff --git a/tests/operations/tests/FletcherChecksum.mjs b/tests/operations/tests/FletcherChecksum.mjs new file mode 100644 index 00000000..de23abdc --- /dev/null +++ b/tests/operations/tests/FletcherChecksum.mjs @@ -0,0 +1,108 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "Fletcher-16 Checksum: abcde", + input: "abcde", + expectedOutput: "c8f0", + recipeConfig: [ + { + op: "Fletcher-16 Checksum", + args: [], + }, + ], + }, + { + name: "Fletcher-16 Checksum: abcdef", + input: "abcdef", + expectedOutput: "2057", + recipeConfig: [ + { + op: "Fletcher-16 Checksum", + args: [], + }, + ], + }, + { + name: "Fletcher-16 Checksum: abcdefgh", + input: "abcdefgh", + expectedOutput: "0627", + recipeConfig: [ + { + op: "Fletcher-16 Checksum", + args: [], + }, + ], + }, + { + name: "Fletcher-32 Checksum: abcde", + input: "abcde", + expectedOutput: "f04fc729", + recipeConfig: [ + { + op: "Fletcher-32 Checksum", + args: [], + }, + ], + }, + { + name: "Fletcher-32 Checksum: abcdef", + input: "abcdef", + expectedOutput: "56502d2a", + recipeConfig: [ + { + op: "Fletcher-32 Checksum", + args: [], + }, + ], + }, + { + name: "Fletcher-32 Checksum: abcdefgh", + input: "abcdefgh", + expectedOutput: "ebe19591", + recipeConfig: [ + { + op: "Fletcher-32 Checksum", + args: [], + }, + ], + }, + { + name: "Fletcher-64 Checksum: abcde", + input: "abcde", + expectedOutput: "c8c6c527646362c6", + recipeConfig: [ + { + op: "Fletcher-64 Checksum", + args: [], + }, + ], + }, + { + name: "Fletcher-64 Checksum: abcdef", + input: "abcdef", + expectedOutput: "c8c72b276463c8c6", + recipeConfig: [ + { + op: "Fletcher-64 Checksum", + args: [], + }, + ], + }, + { + name: "Fletcher-64 Checksum: abcdefgh", + input: "abcdefgh", + expectedOutput: "312e2b28cccac8c6", + recipeConfig: [ + { + op: "Fletcher-64 Checksum", + args: [], + }, + ], + }, +]); diff --git a/tests/operations/tests/GenerateAllHashes.mjs b/tests/operations/tests/GenerateAllHashes.mjs index 28707bde..8e4a849a 100644 --- a/tests/operations/tests/GenerateAllHashes.mjs +++ b/tests/operations/tests/GenerateAllHashes.mjs @@ -58,8 +58,8 @@ CTPH: A:E:E Checksums: Fletcher-8: 3d Fletcher-16: 5dc1 -Fletcher-32: 045901c0 -Fletcher-64: 00000459000001c0 +Fletcher-32: 3f5cd9e7 +Fletcher-64: 7473657474736574 Adler-32: 045d01c1 CRC-8: b9 CRC-16: f82e diff --git a/tests/operations/tests/Rabbit.mjs b/tests/operations/tests/Rabbit.mjs new file mode 100644 index 00000000..ca3156fa --- /dev/null +++ b/tests/operations/tests/Rabbit.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: RFC Test vector, without IV 1", + input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedOutput: "b15754f036a5d6ecf56b45261c4af70288e8d815c59c0c397b696c4789c68aa7f416a1c3700cd451da68d1881673d696", + recipeConfig: [ + { + "op": "Rabbit", + "args": [ + {"option": "Hex", "string": "00000000000000000000000000000000"}, + {"option": "Hex", "string": ""}, + "Big", "Hex", "Hex" + ] + } + ] + }, + { + name: "Rabbit: RFC Test vector, without IV 2", + input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedOutput: "3d2df3c83ef627a1e97fc38487e2519cf576cd61f4405b8896bf53aa8554fc19e5547473fbdb43508ae53b20204d4c5e", + recipeConfig: [ + { + "op": "Rabbit", + "args": [ + {"option": "Hex", "string": "912813292e3d36fe3bfc62f1dc51c3ac"}, + {"option": "Hex", "string": ""}, + "Big", "Hex", "Hex" + ] + } + ] + }, + { + name: "Rabbit: RFC Test vector, without IV 3", + input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedOutput: "0cb10dcda041cdac32eb5cfd02d0609b95fc9fca0f17015a7b7092114cff3ead9649e5de8bfc7f3f924147ad3a947428", + recipeConfig: [ + { + "op": "Rabbit", + "args": [ + {"option": "Hex", "string": "8395741587e0c733e9e9ab01c09b0043"}, + {"option": "Hex", "string": ""}, + "Big", "Hex", "Hex" + ] + } + ] + }, + { + name: "Rabbit: RFC Test vector, with IV 1", + input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedOutput: "c6a7275ef85495d87ccd5d376705b7ed5f29a6ac04f5efd47b8f293270dc4a8d2ade822b29de6c1ee52bdb8a47bf8f66", + recipeConfig: [ + { + "op": "Rabbit", + "args": [ + {"option": "Hex", "string": "00000000000000000000000000000000"}, + {"option": "Hex", "string": "0000000000000000"}, + "Big", "Hex", "Hex" + ] + } + ] + }, + { + name: "Rabbit: RFC Test vector, with IV 2", + input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedOutput: "1fcd4eb9580012e2e0dccc9222017d6da75f4e10d12125017b2499ffed936f2eebc112c393e738392356bdd012029ba7", + recipeConfig: [ + { + "op": "Rabbit", + "args": [ + {"option": "Hex", "string": "00000000000000000000000000000000"}, + {"option": "Hex", "string": "c373f575c1267e59"}, + "Big", "Hex", "Hex" + ] + } + ] + }, + { + name: "Rabbit: RFC Test vector, with IV 3", + input: "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + expectedOutput: "445ad8c805858dbf70b6af23a151104d96c8f27947f42c5baeae67c6acc35b039fcbfc895fa71c17313df034f01551cb", + recipeConfig: [ + { + "op": "Rabbit", + "args": [ + {"option": "Hex", "string": "00000000000000000000000000000000"}, + {"option": "Hex", "string": "a6eb561ad2f41727"}, + "Big", "Hex", "Hex" + ] + } + ] + }, + { + name: "Rabbit: generated stream should be XORed with the input", + input: "cedda96c054e3ddd93da7ed05e2a4b7bdb0c00fe214f03502e2708b2c2bfc77aa2311b0b9af8aa78d119f92b26db0a6b", + expectedOutput: "7f8afd9c33ebeb3166b13bf64260bc7953e4d8ebe4d30f69554e64f54b794ddd5627bac8eaf47e290b7128a330a8dcfd", + recipeConfig: [ + { + "op": "Rabbit", + "args": [ + {"option": "Hex", "string": "00000000000000000000000000000000"}, + {"option": "Hex", "string": ""}, + "Big", "Hex", "Hex" + ] + } + ] + }, + { + name: "Rabbit: least significant bits should be used for the last block", + input: "0000000000000000", + expectedOutput: "f56b45261c4af702", + recipeConfig: [ + { + "op": "Rabbit", + "args": [ + {"option": "Hex", "string": "00000000000000000000000000000000"}, + {"option": "Hex", "string": ""}, + "Big", "Hex", "Hex" + ] + } + ] + }, + { + name: "Rabbit: invalid key length", + input: "", + expectedOutput: "Invalid key length: 8 bytes (expected: 16)", + recipeConfig: [ + { + "op": "Rabbit", + "args": [ + {"option": "Hex", "string": "0000000000000000"}, + {"option": "Hex", "string": ""}, + "Big", "Hex", "Hex" + ] + } + ] + }, + { + name: "Rabbit: invalid IV length", + input: "", + expectedOutput: "Invalid IV length: 4 bytes (expected: 0 or 8)", + recipeConfig: [ + { + "op": "Rabbit", + "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: little-endian mode (Crypto++ compatible)", + input: "Rabbit stream cipher test", + expectedOutput: "1ae2d4edcf9b6063b00fd6fda0b223aded157e77031cf0440b", + recipeConfig: [ + { + "op": "Rabbit", + "args": [ + {"option": "Hex", "string": "23c2731e8b5469fd8dabb5bc592a0f3a"}, + {"option": "Hex", "string": "712906405ef03201"}, + "Little", "Raw", "Hex" + ] + } + ] + }, +]); diff --git a/tests/operations/tests/Shuffle.mjs b/tests/operations/tests/Shuffle.mjs new file mode 100644 index 00000000..21c878f1 --- /dev/null +++ b/tests/operations/tests/Shuffle.mjs @@ -0,0 +1,54 @@ +/** + * @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": ["Comma"] + } + ] + }, + { + "name": "Shuffle bytes", + "input": "12345678", + "expectedOutput": "31 32 33 34 35 36 37 38", + "recipeConfig": [ + { + "op": "Shuffle", + "args": ["Nothing (separate chars)"] + }, + { + "op": "To Hex", + "args": ["Space", 0] + }, + { + "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 feed"] + }, + { + "op": "Sort", + "args": ["Line feed", false, "Alphabetical (case sensitive)"] + } + ] + } +]);