diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d13e56d..93944764 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ All major and minor version changes will be documented in this file. Details of ## Details +### [10.5.0] - 2023-07-14 +- Added GOST Encrypt, Decrypt, Sign, Verify, Key Wrap, and Key Unwrap operations [@n1474335] | [#592] + ### [10.4.0] - 2023-03-24 - Added 'Generate De Bruijn Sequence' operation [@gchq77703] | [#493] @@ -371,6 +374,7 @@ All major and minor version changes will be documented in this file. Details of +[10.5.0]: https://github.com/gchq/CyberChef/releases/tag/v10.5.0 [10.4.0]: https://github.com/gchq/CyberChef/releases/tag/v10.4.0 [10.3.0]: https://github.com/gchq/CyberChef/releases/tag/v10.3.0 [10.2.0]: https://github.com/gchq/CyberChef/releases/tag/v10.2.0 @@ -641,4 +645,5 @@ All major and minor version changes will be documented in this file. Details of [#1528]: https://github.com/gchq/CyberChef/pull/1528 [#661]: https://github.com/gchq/CyberChef/pull/661 [#493]: https://github.com/gchq/CyberChef/pull/493 +[#592]: https://github.com/gchq/CyberChef/issues/592 diff --git a/Gruntfile.js b/Gruntfile.js index 2f1d37be..1603a9ee 100755 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -197,6 +197,7 @@ module.exports = function (grunt) { }, webpack: { options: webpackConfig, + myConfig: webpackConfig, web: webpackProdConf(), }, "webpack-dev-server": { diff --git a/package-lock.json b/package-lock.json index 33ee27da..3cccfa6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,19 @@ { "name": "cyberchef", - "version": "10.4.0", + "version": "10.5.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cyberchef", - "version": "10.4.0", + "version": "10.5.2", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "@astronautlabs/amf": "^0.0.6", "@babel/polyfill": "^7.12.1", "@blu3r4y/lzma": "^2.3.3", + "@wavesenterprise/crypto-gost-js": "^2.1.0-RC1", "argon2-browser": "^1.18.0", "arrive": "^2.4.1", "avsc": "^5.7.7", @@ -111,7 +112,7 @@ "babel-plugin-dynamic-import-node": "^2.3.3", "babel-plugin-transform-builtin-extend": "1.1.2", "base64-loader": "^1.0.0", - "chromedriver": "^113.0.0", + "chromedriver": "^114.0.2", "cli-progress": "^3.12.0", "colors": "^1.4.0", "copy-webpack-plugin": "^11.0.0", @@ -133,7 +134,7 @@ "imports-loader": "^4.0.1", "mini-css-extract-plugin": "2.7.3", "modify-source-webpack-plugin": "^3.0.0", - "nightwatch": "^2.6.19", + "nightwatch": "^2.6.16", "postcss": "^8.4.21", "postcss-css-variables": "^0.18.0", "postcss-import": "^15.1.0", @@ -2537,9 +2538,9 @@ } }, "node_modules/@nightwatch/html-reporter-template": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@nightwatch/html-reporter-template/-/html-reporter-template-0.2.1.tgz", - "integrity": "sha512-GEBeGoXVmTYPtNC4Yq34vfgxf6mlFyEagxpsfH18Qe5BvctF2rprX+wI5dKBm9p5IqHo6ZOcXHCufOeP3cjuOw==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@nightwatch/html-reporter-template/-/html-reporter-template-0.1.4.tgz", + "integrity": "sha512-fVylXypRuNJbyFAwY/5H2QM1A1XVoZWis0zhiMwA5LQN0cxHzpG2aUheb+qP1EfkxhFxwSUHOcrvphFLbPA8ow==", "dev": true }, "node_modules/@nodelib/fs.scandir": { @@ -2820,6 +2821,11 @@ "dev": true, "license": "ISC" }, + "node_modules/@wavesenterprise/crypto-gost-js": { + "version": "2.1.0-RC1", + "resolved": "https://registry.npmjs.org/@wavesenterprise/crypto-gost-js/-/crypto-gost-js-2.1.0-RC1.tgz", + "integrity": "sha512-liAR3/T/vxnEgNUE00Llt+sDvKYqo+sm/L7tqkJorg2ha3SsplOSXAqpH0t4Ya0gRj8qN8zXqO+WwLCxXXuQcw==" + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.1", "dev": true, @@ -3341,9 +3347,9 @@ } }, "node_modules/axios": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.1.tgz", - "integrity": "sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", + "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", "dev": true, "dependencies": { "follow-redirects": "^1.15.0", @@ -4369,15 +4375,15 @@ } }, "node_modules/chromedriver": { - "version": "113.0.0", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-113.0.0.tgz", - "integrity": "sha512-UnQlt2kPicYXVNHPzy9HfcWvEbKJjjKAEaatdcnP/lCIRwuSoZFVLH0HVDAGdbraXp3dNVhfE2Qx7gw8TnHnPw==", + "version": "114.0.2", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-114.0.2.tgz", + "integrity": "sha512-v0qrXRBknbxqmtklG7RWOe3TJ/dLaHhtB0jVxE7BAdYERxUjEaNRyqBwoGgVfQDibHCB0swzvzsj158nnfPgZw==", "dev": true, "hasInstallScript": true, "dependencies": { "@testim/chrome-version": "^1.1.3", - "axios": "^1.2.1", - "compare-versions": "^5.0.1", + "axios": "^1.4.0", + "compare-versions": "^5.0.3", "extract-zip": "^2.0.1", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^1.1.0", @@ -4558,9 +4564,9 @@ "license": "MIT" }, "node_modules/compare-versions": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-5.0.1.tgz", - "integrity": "sha512-v8Au3l0b+Nwkp4G142JcgJFh1/TUhdxut7wzD1Nq1dyp5oa3tXaqb03EXOAB6jS4gMlalkjAUPZBMiAfKUixHQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-5.0.3.tgz", + "integrity": "sha512-4UZlZP8Z99MGEY+Ovg/uJxJuvoXuN4M6B3hKaiackiHrgzQFEe3diJi1mf1PNHbFujM7FvLrK2bpgIaImbtZ1A==", "dev": true }, "node_modules/compressible": { @@ -9623,6 +9629,11 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mkpath": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, "node_modules/mocha": { "version": "9.2.2", "dev": true, @@ -9923,13 +9934,13 @@ } }, "node_modules/nightwatch": { - "version": "2.6.20", - "resolved": "https://registry.npmjs.org/nightwatch/-/nightwatch-2.6.20.tgz", - "integrity": "sha512-XEyxuSGhESdHj4LHqA5snrc/nMgH4tsB/mWrbyGt3EwW1AgjyE7DRzJUbhG7J00Np3Dv3k2nmyJs0Xq0FX/yvQ==", + "version": "2.6.16", + "resolved": "https://registry.npmjs.org/nightwatch/-/nightwatch-2.6.16.tgz", + "integrity": "sha512-U24L11WQlYS6TYdGx1h+xMfxw3jjhmoOjYEIBV3WQNwuWqpWzwIaG0gr3WonCwiEgKvSquLSuXhm5vd1U3JdiQ==", "dev": true, "dependencies": { "@nightwatch/chai": "5.0.2", - "@nightwatch/html-reporter-template": "0.2.1", + "@nightwatch/html-reporter-template": "0.1.4", "ansi-to-html": "0.7.2", "assertion-error": "1.1.0", "boxen": "5.1.2", @@ -9950,6 +9961,7 @@ "lodash.pick": "4.4.0", "minimatch": "3.1.2", "minimist": "1.2.6", + "mkpath": "1.0.0", "mocha": "9.2.2", "nightwatch-axe-verbose": "^2.1.0", "open": "8.4.0", @@ -11247,8 +11259,9 @@ }, "node_modules/proxy-from-env": { "version": "1.1.0", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true }, "node_modules/psl": { "version": "1.9.0", @@ -15531,9 +15544,9 @@ } }, "@nightwatch/html-reporter-template": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@nightwatch/html-reporter-template/-/html-reporter-template-0.2.1.tgz", - "integrity": "sha512-GEBeGoXVmTYPtNC4Yq34vfgxf6mlFyEagxpsfH18Qe5BvctF2rprX+wI5dKBm9p5IqHo6ZOcXHCufOeP3cjuOw==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@nightwatch/html-reporter-template/-/html-reporter-template-0.1.4.tgz", + "integrity": "sha512-fVylXypRuNJbyFAwY/5H2QM1A1XVoZWis0zhiMwA5LQN0cxHzpG2aUheb+qP1EfkxhFxwSUHOcrvphFLbPA8ow==", "dev": true }, "@nodelib/fs.scandir": { @@ -15763,6 +15776,11 @@ "version": "1.1.2", "dev": true }, + "@wavesenterprise/crypto-gost-js": { + "version": "2.1.0-RC1", + "resolved": "https://registry.npmjs.org/@wavesenterprise/crypto-gost-js/-/crypto-gost-js-2.1.0-RC1.tgz", + "integrity": "sha512-liAR3/T/vxnEgNUE00Llt+sDvKYqo+sm/L7tqkJorg2ha3SsplOSXAqpH0t4Ya0gRj8qN8zXqO+WwLCxXXuQcw==" + }, "@webassemblyjs/ast": { "version": "1.11.1", "dev": true, @@ -16139,9 +16157,9 @@ "dev": true }, "axios": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.1.tgz", - "integrity": "sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", + "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", "dev": true, "requires": { "follow-redirects": "^1.15.0", @@ -16828,14 +16846,14 @@ "dev": true }, "chromedriver": { - "version": "113.0.0", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-113.0.0.tgz", - "integrity": "sha512-UnQlt2kPicYXVNHPzy9HfcWvEbKJjjKAEaatdcnP/lCIRwuSoZFVLH0HVDAGdbraXp3dNVhfE2Qx7gw8TnHnPw==", + "version": "114.0.2", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-114.0.2.tgz", + "integrity": "sha512-v0qrXRBknbxqmtklG7RWOe3TJ/dLaHhtB0jVxE7BAdYERxUjEaNRyqBwoGgVfQDibHCB0swzvzsj158nnfPgZw==", "dev": true, "requires": { "@testim/chrome-version": "^1.1.3", - "axios": "^1.2.1", - "compare-versions": "^5.0.1", + "axios": "^1.4.0", + "compare-versions": "^5.0.3", "extract-zip": "^2.0.1", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^1.1.0", @@ -16954,9 +16972,9 @@ "dev": true }, "compare-versions": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-5.0.1.tgz", - "integrity": "sha512-v8Au3l0b+Nwkp4G142JcgJFh1/TUhdxut7wzD1Nq1dyp5oa3tXaqb03EXOAB6jS4gMlalkjAUPZBMiAfKUixHQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-5.0.3.tgz", + "integrity": "sha512-4UZlZP8Z99MGEY+Ovg/uJxJuvoXuN4M6B3hKaiackiHrgzQFEe3diJi1mf1PNHbFujM7FvLrK2bpgIaImbtZ1A==", "dev": true }, "compressible": { @@ -20369,6 +20387,10 @@ "minimist": "^1.2.6" } }, + "mkpath": { + "version": "1.0.0", + "dev": true + }, "mocha": { "version": "9.2.2", "dev": true, @@ -20569,13 +20591,13 @@ "version": "0.6.3" }, "nightwatch": { - "version": "2.6.20", - "resolved": "https://registry.npmjs.org/nightwatch/-/nightwatch-2.6.20.tgz", - "integrity": "sha512-XEyxuSGhESdHj4LHqA5snrc/nMgH4tsB/mWrbyGt3EwW1AgjyE7DRzJUbhG7J00Np3Dv3k2nmyJs0Xq0FX/yvQ==", + "version": "2.6.16", + "resolved": "https://registry.npmjs.org/nightwatch/-/nightwatch-2.6.16.tgz", + "integrity": "sha512-U24L11WQlYS6TYdGx1h+xMfxw3jjhmoOjYEIBV3WQNwuWqpWzwIaG0gr3WonCwiEgKvSquLSuXhm5vd1U3JdiQ==", "dev": true, "requires": { "@nightwatch/chai": "5.0.2", - "@nightwatch/html-reporter-template": "0.2.1", + "@nightwatch/html-reporter-template": "0.1.4", "ansi-to-html": "0.7.2", "assertion-error": "1.1.0", "boxen": "5.1.2", @@ -20596,6 +20618,7 @@ "lodash.pick": "4.4.0", "minimatch": "3.1.2", "minimist": "1.2.6", + "mkpath": "1.0.0", "mocha": "9.2.2", "nightwatch-axe-verbose": "^2.1.0", "open": "8.4.0", @@ -21432,6 +21455,8 @@ }, "proxy-from-env": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "dev": true }, "psl": { diff --git a/package.json b/package.json index 9447e88f..45328dd1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cyberchef", - "version": "10.4.0", + "version": "10.5.2", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "author": "n1474335 ", "homepage": "https://gchq.github.io/CyberChef", @@ -55,7 +55,7 @@ "babel-plugin-dynamic-import-node": "^2.3.3", "babel-plugin-transform-builtin-extend": "1.1.2", "base64-loader": "^1.0.0", - "chromedriver": "^113.0.0", + "chromedriver": "^114.0.2", "cli-progress": "^3.12.0", "colors": "^1.4.0", "copy-webpack-plugin": "^11.0.0", @@ -77,7 +77,7 @@ "imports-loader": "^4.0.1", "mini-css-extract-plugin": "2.7.3", "modify-source-webpack-plugin": "^3.0.0", - "nightwatch": "^2.6.19", + "nightwatch": "^2.6.16", "postcss": "^8.4.21", "postcss-css-variables": "^0.18.0", "postcss-import": "^15.1.0", @@ -95,6 +95,7 @@ "@astronautlabs/amf": "^0.0.6", "@babel/polyfill": "^7.12.1", "@blu3r4y/lzma": "^2.3.3", + "@wavesenterprise/crypto-gost-js": "^2.1.0-RC1", "argon2-browser": "^1.18.0", "arrive": "^2.4.1", "avsc": "^5.7.7", @@ -181,7 +182,7 @@ "build": "npx grunt prod", "node": "npx grunt node", "repl": "node --experimental-modules --experimental-json-modules --experimental-specifier-resolution=node --no-experimental-fetch --no-warnings src/node/repl.mjs", - "test": "npx grunt configTests && node --experimental-modules --experimental-json-modules --no-warnings --no-deprecation --openssl-legacy-provider --no-experimental-fetch tests/node/index.mjs && node --experimental-modules --experimental-json-modules --no-warnings --no-deprecation --openssl-legacy-provider --no-experimental-fetch tests/operations/index.mjs", + "test": "npx grunt configTests && node --experimental-modules --experimental-json-modules --no-warnings --no-deprecation --openssl-legacy-provider --no-experimental-fetch tests/node/index.mjs && node --experimental-modules --experimental-json-modules --no-warnings --no-deprecation --openssl-legacy-provider --no-experimental-fetch --trace-uncaught tests/operations/index.mjs", "testnodeconsumer": "npx grunt testnodeconsumer", "testui": "npx grunt testui", "testuidev": "npx nightwatch --env=dev", diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index ce2f01f5..cf4d91be 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -91,6 +91,12 @@ "Rabbit", "SM4 Encrypt", "SM4 Decrypt", + "GOST Encrypt", + "GOST Decrypt", + "GOST Sign", + "GOST Verify", + "GOST Key Wrap", + "GOST Key Unwrap", "ROT13", "ROT13 Brute Force", "ROT47", @@ -370,7 +376,7 @@ "Snefru", "BLAKE2b", "BLAKE2s", - "GOST hash", + "GOST Hash", "Streebog", "SSDEEP", "CTPH", diff --git a/src/core/operations/GOSTDecrypt.mjs b/src/core/operations/GOSTDecrypt.mjs new file mode 100644 index 00000000..8259a0d4 --- /dev/null +++ b/src/core/operations/GOSTDecrypt.mjs @@ -0,0 +1,138 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast, fromHex } from "../lib/Hex.mjs"; +import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; + +/** + * GOST Decrypt operation + */ +class GOSTDecrypt extends Operation { + + /** + * GOSTDecrypt constructor + */ + constructor() { + super(); + + this.name = "GOST Decrypt"; + this.module = "Ciphers"; + this.description = "The GOST block cipher (Magma), defined in the standard GOST 28147-89 (RFC 5830), is a Soviet and Russian government standard symmetric key block cipher with a block size of 64 bits. The original standard, published in 1989, did not give the cipher any name, but the most recent revision of the standard, GOST R 34.12-2015 (RFC 7801, RFC 8891), specifies that it may be referred to as Magma. The GOST hash function is based on this cipher. The new standard also specifies a new 128-bit block cipher called Kuznyechik.

Developed in the 1970s, the standard had been marked 'Top Secret' and then downgraded to 'Secret' in 1990. Shortly after the dissolution of the USSR, it was declassified and it was released to the public in 1994. GOST 28147 was a Soviet alternative to the United States standard algorithm, DES. Thus, the two are very similar in structure."; + this.infoURL = "https://wikipedia.org/wiki/GOST_(block_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: "Input type", + type: "option", + value: ["Hex", "Raw"] + }, + { + name: "Output type", + type: "option", + value: ["Raw", "Hex"] + }, + { + name: "Algorithm", + type: "argSelector", + value: [ + { + name: "GOST 28147 (Magma, 1989)", + off: [5], + on: [6] + }, + { + name: "GOST R 34.12 (Kuznyechik, 2015)", + on: [5], + off: [6] + } + ] + }, + { + name: "Block length", + type: "option", + value: ["64", "128"] + }, + { + name: "sBox", + type: "option", + value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] + }, + { + name: "Block mode", + type: "option", + value: ["ECB", "CFB", "OFB", "CTR", "CBC"] + }, + { + name: "Key meshing mode", + type: "option", + value: ["NO", "CP"] + }, + { + name: "Padding", + type: "option", + value: ["NO", "PKCS5", "ZERO", "RANDOM", "BIT"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [keyObj, ivObj, inputType, outputType, version, length, sBox, blockMode, keyMeshing, padding] = args; + + const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); + const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option)); + input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input)); + + const versionNum = version === "GOST 28147 (Magma, 1989)" ? 1989 : 2015; + const blockLength = versionNum === 1989 ? 64 : parseInt(length, 10); + const sBoxVal = versionNum === 1989 ? sBox : null; + + const algorithm = { + version: versionNum, + length: blockLength, + mode: "ES", + sBox: sBoxVal, + block: blockMode, + keyMeshing: keyMeshing, + padding: padding + }; + + try { + const Hex = CryptoGost.coding.Hex; + if (iv) algorithm.iv = Hex.decode(iv); + + const cipher = GostEngine.getGostCipher(algorithm); + const out = Hex.encode(cipher.decrypt(Hex.decode(key), Hex.decode(input))); + + return outputType === "Hex" ? out : Utils.byteArrayToChars(fromHex(out)); + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default GOSTDecrypt; diff --git a/src/core/operations/GOSTEncrypt.mjs b/src/core/operations/GOSTEncrypt.mjs new file mode 100644 index 00000000..ce92ecda --- /dev/null +++ b/src/core/operations/GOSTEncrypt.mjs @@ -0,0 +1,138 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast, fromHex } from "../lib/Hex.mjs"; +import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; + +/** + * GOST Encrypt operation + */ +class GOSTEncrypt extends Operation { + + /** + * GOSTEncrypt constructor + */ + constructor() { + super(); + + this.name = "GOST Encrypt"; + this.module = "Ciphers"; + this.description = "The GOST block cipher (Magma), defined in the standard GOST 28147-89 (RFC 5830), is a Soviet and Russian government standard symmetric key block cipher with a block size of 64 bits. The original standard, published in 1989, did not give the cipher any name, but the most recent revision of the standard, GOST R 34.12-2015 (RFC 7801, RFC 8891), specifies that it may be referred to as Magma. The GOST hash function is based on this cipher. The new standard also specifies a new 128-bit block cipher called Kuznyechik.

Developed in the 1970s, the standard had been marked 'Top Secret' and then downgraded to 'Secret' in 1990. Shortly after the dissolution of the USSR, it was declassified and it was released to the public in 1994. GOST 28147 was a Soviet alternative to the United States standard algorithm, DES. Thus, the two are very similar in structure."; + this.infoURL = "https://wikipedia.org/wiki/GOST_(block_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: "Input type", + type: "option", + value: ["Raw", "Hex"] + }, + { + name: "Output type", + type: "option", + value: ["Hex", "Raw"] + }, + { + name: "Algorithm", + type: "argSelector", + value: [ + { + name: "GOST 28147 (Magma, 1989)", + off: [5], + on: [6] + }, + { + name: "GOST R 34.12 (Kuznyechik, 2015)", + on: [5], + off: [6] + } + ] + }, + { + name: "Block length", + type: "option", + value: ["64", "128"] + }, + { + name: "sBox", + type: "option", + value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] + }, + { + name: "Block mode", + type: "option", + value: ["ECB", "CFB", "OFB", "CTR", "CBC"] + }, + { + name: "Key meshing mode", + type: "option", + value: ["NO", "CP"] + }, + { + name: "Padding", + type: "option", + value: ["NO", "PKCS5", "ZERO", "RANDOM", "BIT"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [keyObj, ivObj, inputType, outputType, version, length, sBox, blockMode, keyMeshing, padding] = args; + + const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); + const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option)); + input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input)); + + const versionNum = version === "GOST 28147 (Magma, 1989)" ? 1989 : 2015; + const blockLength = versionNum === 1989 ? 64 : parseInt(length, 10); + const sBoxVal = versionNum === 1989 ? sBox : null; + + const algorithm = { + version: versionNum, + length: blockLength, + mode: "ES", + sBox: sBoxVal, + block: blockMode, + keyMeshing: keyMeshing, + padding: padding + }; + + try { + const Hex = CryptoGost.coding.Hex; + if (iv) algorithm.iv = Hex.decode(iv); + + const cipher = GostEngine.getGostCipher(algorithm); + const out = Hex.encode(cipher.encrypt(Hex.decode(key), Hex.decode(input))); + + return outputType === "Hex" ? out : Utils.byteArrayToChars(fromHex(out)); + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default GOSTEncrypt; diff --git a/src/core/operations/GOSTHash.mjs b/src/core/operations/GOSTHash.mjs index d67a594c..5c8cc6f7 100644 --- a/src/core/operations/GOSTHash.mjs +++ b/src/core/operations/GOSTHash.mjs @@ -7,7 +7,7 @@ import Operation from "../Operation.mjs"; import OperationError from "../errors/OperationError.mjs"; import GostDigest from "../vendor/gost/gostDigest.mjs"; -import {toHexFast} from "../lib/Hex.mjs"; +import { toHexFast } from "../lib/Hex.mjs"; /** * GOST hash operation @@ -20,7 +20,7 @@ class GOSTHash extends Operation { constructor() { super(); - this.name = "GOST hash"; + this.name = "GOST Hash"; this.module = "Hashing"; this.description = "The GOST hash function, defined in the standards GOST R 34.11-94 and GOST 34.311-95 is a 256-bit cryptographic hash function. It was initially defined in the Russian national standard GOST R 34.11-94 Information Technology – Cryptographic Information Security – Hash Function. The equivalent standard used by other member-states of the CIS is GOST 34.311-95.

This function must not be confused with a different Streebog hash function, which is defined in the new revision of the standard GOST R 34.11-2012.

The GOST hash function is based on the GOST block cipher."; this.infoURL = "https://wikipedia.org/wiki/GOST_(hash_function)"; @@ -28,20 +28,30 @@ class GOSTHash extends Operation { this.outputType = "string"; this.args = [ { - "name": "S-Box", - "type": "option", - "value": [ - "D-A", - "D-SC", - "E-TEST", - "E-A", - "E-B", - "E-C", - "E-D", - "E-SC", - "E-Z", - "D-TEST" + name: "Algorithm", + type: "argSelector", + value: [ + { + name: "GOST 28147 (1994)", + off: [1], + on: [2] + }, + { + name: "GOST R 34.11 (Streebog, 2012)", + on: [1], + off: [2] + } ] + }, + { + name: "Digest length", + type: "option", + value: ["256", "512"] + }, + { + name: "sBox", + type: "option", + value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] } ]; } @@ -52,13 +62,23 @@ class GOSTHash extends Operation { * @returns {string} */ run(input, args) { + const [version, length, sBox] = args; + + const versionNum = version === "GOST 28147 (1994)" ? 1994 : 2012; + const algorithm = { + name: versionNum === 1994 ? "GOST 28147" : "GOST R 34.10", + version: versionNum, + mode: "HASH" + }; + + if (versionNum === 1994) { + algorithm.sBox = sBox; + } else { + algorithm.length = parseInt(length, 10); + } + try { - const sBox = args[1]; - const gostDigest = new GostDigest({ - name: "GOST R 34.11", - version: 1994, - sBox: sBox - }); + const gostDigest = new GostDigest(algorithm); return toHexFast(gostDigest.digest(input)); } catch (err) { diff --git a/src/core/operations/GOSTKeyUnwrap.mjs b/src/core/operations/GOSTKeyUnwrap.mjs new file mode 100644 index 00000000..afcd6287 --- /dev/null +++ b/src/core/operations/GOSTKeyUnwrap.mjs @@ -0,0 +1,129 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast, fromHex } from "../lib/Hex.mjs"; +import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; + +/** + * GOST Key Unwrap operation + */ +class GOSTKeyUnwrap extends Operation { + + /** + * GOSTKeyUnwrap constructor + */ + constructor() { + super(); + + this.name = "GOST Key Unwrap"; + this.module = "Ciphers"; + this.description = "A decryptor for keys wrapped using one of the GOST block ciphers."; + this.infoURL = "https://wikipedia.org/wiki/GOST_(block_cipher)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Key", + type: "toggleString", + value: "", + toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + name: "User Key Material", + type: "toggleString", + value: "", + toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + name: "Input type", + type: "option", + value: ["Hex", "Raw"] + }, + { + name: "Output type", + type: "option", + value: ["Raw", "Hex"] + }, + { + name: "Algorithm", + type: "argSelector", + value: [ + { + name: "GOST 28147 (Magma, 1989)", + off: [5], + on: [6] + }, + { + name: "GOST R 34.12 (Kuznyechik, 2015)", + on: [5], + off: [6] + } + ] + }, + { + name: "Block length", + type: "option", + value: ["64", "128"] + }, + { + name: "sBox", + type: "option", + value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] + }, + { + name: "Key wrapping", + type: "option", + value: ["NO", "CP", "SC"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [keyObj, ukmObj, inputType, outputType, version, length, sBox, keyWrapping] = args; + + const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); + const ukm = toHexFast(Utils.convertToByteArray(ukmObj.string, ukmObj.option)); + input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input)); + + const versionNum = version === "GOST 28147 (Magma, 1989)" ? 1989 : 2015; + const blockLength = versionNum === 1989 ? 64 : parseInt(length, 10); + const sBoxVal = versionNum === 1989 ? sBox : null; + + const algorithm = { + version: versionNum, + length: blockLength, + mode: "KW", + sBox: sBoxVal, + keyWrapping: keyWrapping + }; + + try { + const Hex = CryptoGost.coding.Hex; + algorithm.ukm = Hex.decode(ukm); + + const cipher = GostEngine.getGostCipher(algorithm); + const out = Hex.encode(cipher.unwrapKey(Hex.decode(key), Hex.decode(input))); + + return outputType === "Hex" ? out : Utils.byteArrayToChars(fromHex(out)); + } catch (err) { + if (err.toString().includes("Invalid typed array length")) { + throw new OperationError("Incorrect input length. Must be a multiple of the block size."); + } + throw new OperationError(err); + } + } + +} + +export default GOSTKeyUnwrap; diff --git a/src/core/operations/GOSTKeyWrap.mjs b/src/core/operations/GOSTKeyWrap.mjs new file mode 100644 index 00000000..5a3fd4e6 --- /dev/null +++ b/src/core/operations/GOSTKeyWrap.mjs @@ -0,0 +1,129 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast, fromHex } from "../lib/Hex.mjs"; +import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; + +/** + * GOST Key Wrap operation + */ +class GOSTKeyWrap extends Operation { + + /** + * GOSTKeyWrap constructor + */ + constructor() { + super(); + + this.name = "GOST Key Wrap"; + this.module = "Ciphers"; + this.description = "A key wrapping algorithm for protecting keys in untrusted storage using one of the GOST block cipers."; + this.infoURL = "https://wikipedia.org/wiki/GOST_(block_cipher)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Key", + type: "toggleString", + value: "", + toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + name: "User Key Material", + type: "toggleString", + value: "", + toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + name: "Input type", + type: "option", + value: ["Raw", "Hex"] + }, + { + name: "Output type", + type: "option", + value: ["Hex", "Raw"] + }, + { + name: "Algorithm", + type: "argSelector", + value: [ + { + name: "GOST 28147 (Magma, 1989)", + off: [5], + on: [6] + }, + { + name: "GOST R 34.12 (Kuznyechik, 2015)", + on: [5], + off: [6] + } + ] + }, + { + name: "Block length", + type: "option", + value: ["64", "128"] + }, + { + name: "sBox", + type: "option", + value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] + }, + { + name: "Key wrapping", + type: "option", + value: ["NO", "CP", "SC"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [keyObj, ukmObj, inputType, outputType, version, length, sBox, keyWrapping] = args; + + const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); + const ukm = toHexFast(Utils.convertToByteArray(ukmObj.string, ukmObj.option)); + input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input)); + + const versionNum = version === "GOST 28147 (Magma, 1989)" ? 1989 : 2015; + const blockLength = versionNum === 1989 ? 64 : parseInt(length, 10); + const sBoxVal = versionNum === 1989 ? sBox : null; + + const algorithm = { + version: versionNum, + length: blockLength, + mode: "KW", + sBox: sBoxVal, + keyWrapping: keyWrapping + }; + + try { + const Hex = CryptoGost.coding.Hex; + algorithm.ukm = Hex.decode(ukm); + + const cipher = GostEngine.getGostCipher(algorithm); + const out = Hex.encode(cipher.wrapKey(Hex.decode(key), Hex.decode(input))); + + return outputType === "Hex" ? out : Utils.byteArrayToChars(fromHex(out)); + } catch (err) { + if (err.toString().includes("Invalid typed array length")) { + throw new OperationError("Incorrect input length. Must be a multiple of the block size."); + } + throw new OperationError(err); + } + } + +} + +export default GOSTKeyWrap; diff --git a/src/core/operations/GOSTSign.mjs b/src/core/operations/GOSTSign.mjs new file mode 100644 index 00000000..9195f469 --- /dev/null +++ b/src/core/operations/GOSTSign.mjs @@ -0,0 +1,129 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast, fromHex } from "../lib/Hex.mjs"; +import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; + +/** + * GOST Sign operation + */ +class GOSTSign extends Operation { + + /** + * GOSTSign constructor + */ + constructor() { + super(); + + this.name = "GOST Sign"; + this.module = "Ciphers"; + this.description = "Sign a plaintext message using one of the GOST block ciphers."; + this.infoURL = "https://wikipedia.org/wiki/GOST_(block_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: "Input type", + type: "option", + value: ["Raw", "Hex"] + }, + { + name: "Output type", + type: "option", + value: ["Hex", "Raw"] + }, + { + name: "Algorithm", + type: "argSelector", + value: [ + { + name: "GOST 28147 (Magma, 1989)", + off: [5], + on: [6] + }, + { + name: "GOST R 34.12 (Kuznyechik, 2015)", + on: [5], + off: [6] + } + ] + }, + { + name: "Block length", + type: "option", + value: ["64", "128"] + }, + { + name: "sBox", + type: "option", + value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] + }, + { + name: "MAC length", + type: "number", + value: 32, + min: 8, + max: 64, + step: 8 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [keyObj, ivObj, inputType, outputType, version, length, sBox, macLength] = args; + + const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); + const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option)); + input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input)); + + const versionNum = version === "GOST 28147 (Magma, 1989)" ? 1989 : 2015; + const blockLength = versionNum === 1989 ? 64 : parseInt(length, 10); + const sBoxVal = versionNum === 1989 ? sBox : null; + + const algorithm = { + version: versionNum, + length: blockLength, + mode: "MAC", + sBox: sBoxVal, + macLength: macLength + }; + + try { + const Hex = CryptoGost.coding.Hex; + if (iv) algorithm.iv = Hex.decode(iv); + + const cipher = GostEngine.getGostCipher(algorithm); + const out = Hex.encode(cipher.sign(Hex.decode(key), Hex.decode(input))); + + return outputType === "Hex" ? out : Utils.byteArrayToChars(fromHex(out)); + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default GOSTSign; diff --git a/src/core/operations/GOSTVerify.mjs b/src/core/operations/GOSTVerify.mjs new file mode 100644 index 00000000..a270e7c5 --- /dev/null +++ b/src/core/operations/GOSTVerify.mjs @@ -0,0 +1,123 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast } from "../lib/Hex.mjs"; +import { CryptoGost, GostEngine } from "@wavesenterprise/crypto-gost-js/index.js"; + +/** + * GOST Verify operation + */ +class GOSTVerify extends Operation { + + /** + * GOSTVerify constructor + */ + constructor() { + super(); + + this.name = "GOST Verify"; + this.module = "Ciphers"; + this.description = "Verify the signature of a plaintext message using one of the GOST block ciphers. Enter the signature in the MAC field."; + this.infoURL = "https://wikipedia.org/wiki/GOST_(block_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: "MAC", + type: "toggleString", + value: "", + toggleValues: ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + name: "Input type", + type: "option", + value: ["Raw", "Hex"] + }, + { + name: "Algorithm", + type: "argSelector", + value: [ + { + name: "GOST 28147 (Magma, 1989)", + off: [5], + on: [6] + }, + { + name: "GOST R 34.12 (Kuznyechik, 2015)", + on: [5], + off: [6] + } + ] + }, + { + name: "Block length", + type: "option", + value: ["64", "128"] + }, + { + name: "sBox", + type: "option", + value: ["E-TEST", "E-A", "E-B", "E-C", "E-D", "E-SC", "E-Z", "D-TEST", "D-A", "D-SC"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + async run(input, args) { + const [keyObj, ivObj, macObj, inputType, version, length, sBox] = args; + + const key = toHexFast(Utils.convertToByteArray(keyObj.string, keyObj.option)); + const iv = toHexFast(Utils.convertToByteArray(ivObj.string, ivObj.option)); + const mac = toHexFast(Utils.convertToByteArray(macObj.string, macObj.option)); + input = inputType === "Hex" ? input : toHexFast(Utils.strToArrayBuffer(input)); + + const versionNum = version === "GOST 28147 (Magma, 1989)" ? 1989 : 2015; + const blockLength = versionNum === 1989 ? 64 : parseInt(length, 10); + const sBoxVal = versionNum === 1989 ? sBox : null; + + const algorithm = { + version: versionNum, + length: blockLength, + mode: "MAC", + sBox: sBoxVal, + macLength: mac.length * 4 + }; + + try { + const Hex = CryptoGost.coding.Hex; + if (iv) algorithm.iv = Hex.decode(iv); + + const cipher = GostEngine.getGostCipher(algorithm); + const out = cipher.verify(Hex.decode(key), Hex.decode(mac), Hex.decode(input)); + + return out ? "The signature matches" : "The signature does not match"; + } catch (err) { + throw new OperationError(err); + } + } + +} + +export default GOSTVerify; diff --git a/src/core/operations/GenerateAllHashes.mjs b/src/core/operations/GenerateAllHashes.mjs index 2a4a2b1a..d9af8065 100644 --- a/src/core/operations/GenerateAllHashes.mjs +++ b/src/core/operations/GenerateAllHashes.mjs @@ -108,7 +108,7 @@ class GenerateAllHashes extends Operation { {name: "BLAKE2s-256", algo: (new BLAKE2s), inputType: "arrayBuffer", params: ["256", "Hex", {string: "", option: "UTF8"}]}, {name: "Streebog-256", algo: (new Streebog), inputType: "arrayBuffer", params: ["256"]}, {name: "Streebog-512", algo: (new Streebog), inputType: "arrayBuffer", params: ["512"]}, - {name: "GOST", algo: (new GOSTHash), inputType: "arrayBuffer", params: ["D-A"]}, + {name: "GOST", algo: (new GOSTHash), inputType: "arrayBuffer", params: ["GOST 28147 (1994)", "256", "D-A"]}, {name: "LM Hash", algo: (new LMHash), inputType: "str", params: []}, {name: "NT Hash", algo: (new NTHash), inputType: "str", params: []}, {name: "SSDEEP", algo: (new SSDEEP()), inputType: "str"}, diff --git a/src/core/operations/Streebog.mjs b/src/core/operations/Streebog.mjs index c5e5bb89..b65accf6 100644 --- a/src/core/operations/Streebog.mjs +++ b/src/core/operations/Streebog.mjs @@ -28,7 +28,7 @@ class Streebog extends Operation { this.outputType = "string"; this.args = [ { - "name": "Size", + "name": "Digest length", "type": "option", "value": ["256", "512"] } @@ -41,13 +41,16 @@ class Streebog extends Operation { * @returns {string} */ run(input, args) { + const [length] = args; + + const algorithm = { + version: 2012, + mode: "HASH", + length: parseInt(length, 10) + }; + try { - const length = parseInt(args[0], 10); - const gostDigest = new GostDigest({ - name: "GOST R 34.11", - version: 2012, - length: length - }); + const gostDigest = new GostDigest(algorithm); return toHexFast(gostDigest.digest(input)); } catch (err) { diff --git a/tests/browser/desktop-ui/00_nightwatch.js b/tests/browser/00_nightwatch.js similarity index 100% rename from tests/browser/desktop-ui/00_nightwatch.js rename to tests/browser/00_nightwatch.js diff --git a/tests/browser/desktop-ui/01_io.js b/tests/browser/01_io.js similarity index 100% rename from tests/browser/desktop-ui/01_io.js rename to tests/browser/01_io.js diff --git a/tests/browser/desktop-ui/02_ops.js b/tests/browser/02_ops.js similarity index 100% rename from tests/browser/desktop-ui/02_ops.js rename to tests/browser/02_ops.js diff --git a/tests/browser/mobile-device/TODO.md b/tests/browser/mobile-device/TODO.md deleted file mode 100644 index 0f04e744..00000000 --- a/tests/browser/mobile-device/TODO.md +++ /dev/null @@ -1 +0,0 @@ -actual mobile device tests go here diff --git a/tests/browser/mobile-ui/00_nightwatch.js b/tests/browser/mobile-ui/00_nightwatch.js deleted file mode 100644 index 3ba2a865..00000000 --- a/tests/browser/mobile-ui/00_nightwatch.js +++ /dev/null @@ -1,261 +0,0 @@ -/** - * Tests to ensure that the app loads correctly in a reasonable time and that operations can be run. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2018 - * @license Apache-2.0 - */ - -const utils = require("./browserUtils.js"); - -module.exports = { - before: browser => { - browser - .resizeWindow(1280, 800) - .url(browser.launchUrl); - }, - - "Loading screen": browser => { - // Check that the loading screen appears and then disappears within a reasonable time - browser - .waitForElementVisible("#preloader", 300) - .waitForElementNotPresent("#preloader", 10000); - }, - - "App loaded": browser => { - browser.useCss(); - // Check that various important elements are loaded - browser.expect.element("#operations").to.be.visible; - browser.expect.element("#recipe").to.be.visible; - browser.expect.element("#input").to.be.present; - browser.expect.element("#output").to.be.present; - browser.expect.element(".op-list").to.be.present; - browser.expect.element("#rec-list").to.be.visible; - browser.expect.element("#controls").to.be.visible; - browser.expect.element("#input-text").to.be.visible; - browser.expect.element("#output-text").to.be.visible; - }, - - "Operations loaded": browser => { - browser.useXpath(); - // Check that an operation in every category has been populated - browser.expect.element("//li[contains(@class, 'operation') and text()='To Base64']").to.be.present; - browser.expect.element("//li[contains(@class, 'operation') and text()='To Binary']").to.be.present; - browser.expect.element("//li[contains(@class, 'operation') and text()='AES Decrypt']").to.be.present; - browser.expect.element("//li[contains(@class, 'operation') and text()='PEM to Hex']").to.be.present; - browser.expect.element("//li[contains(@class, 'operation') and text()='Power Set']").to.be.present; - browser.expect.element("//li[contains(@class, 'operation') and text()='Parse IP range']").to.be.present; - browser.expect.element("//li[contains(@class, 'operation') and text()='Remove Diacritics']").to.be.present; - browser.expect.element("//li[contains(@class, 'operation') and text()='Sort']").to.be.present; - browser.expect.element("//li[contains(@class, 'operation') and text()='To UNIX Timestamp']").to.be.present; - browser.expect.element("//li[contains(@class, 'operation') and text()='Extract dates']").to.be.present; - browser.expect.element("//li[contains(@class, 'operation') and text()='Gzip']").to.be.present; - browser.expect.element("//li[contains(@class, 'operation') and text()='Keccak']").to.be.present; - browser.expect.element("//li[contains(@class, 'operation') and text()='JSON Beautify']").to.be.present; - browser.expect.element("//li[contains(@class, 'operation') and text()='Detect File Type']").to.be.present; - browser.expect.element("//li[contains(@class, 'operation') and text()='Play Media']").to.be.present; - browser.expect.element("//li[contains(@class, 'operation') and text()='Disassemble x86']").to.be.present; - browser.expect.element("//li[contains(@class, 'operation') and text()='Register']").to.be.present; - }, - - "Recipe can be run": browser => { - const toHex = "//li[contains(@class, 'operation') and text()='To Hex']"; - const op = "#rec-list .operation .op-title"; - - // Check that operation is visible - browser - .useXpath() - .expect.element(toHex).to.be.visible; - - // Add it to the recipe by double clicking - browser - .useXpath() - .moveToElement(toHex, 10, 10) - .useCss() - .waitForElementVisible(".popover-body", 1000) - .doubleClick("xpath", toHex); - - // Confirm that it has been added to the recipe - browser - .useCss() - .waitForElementVisible(op, 100) - .expect.element(op).text.to.contain("To Hex"); - - // Enter input - browser - .useCss() - .sendKeys("#input-text .cm-content", "Don't Panic.") - .pause(1000) - .click("#bake"); - - // Check output - browser - .useCss() - .waitForElementNotVisible("#stale-indicator", 1000) - .expect.element("#output-text .cm-content").text.that.equals("44 6f 6e 27 74 20 50 61 6e 69 63 2e"); - - // Clear recipe - browser - .useCss() - .moveToElement(op, 10, 10) - .waitForElementNotPresent(".popover-body", 1000) - .click("#clr-recipe") - .waitForElementNotPresent(op); - }, - - "Test every module": browser => { - browser.useCss(); - - // BSON - loadOp("BSON deserialise", browser) - .waitForElementNotVisible("#output-loader", 5000); - - // Charts - loadOp("Entropy", browser) - .waitForElementNotVisible("#output-loader", 5000); - - // Ciphers - loadOp("AES Encrypt", browser) - .waitForElementNotVisible("#output-loader", 5000); - - // Code - loadOp("XPath expression", browser) - .waitForElementNotVisible("#output-loader", 5000); - - // Compression - loadOp("Gzip", browser) - .waitForElementNotVisible("#output-loader", 5000); - - // Crypto - loadOp("MD5", browser) - .waitForElementNotVisible("#output-loader", 5000); - - // Default - loadOp("Fork", browser) - .waitForElementNotVisible("#output-loader", 5000); - - // Diff - loadOp("Diff", browser) - .waitForElementNotVisible("#output-loader", 5000); - - // Encodings - loadOp("Encode text", browser) - .waitForElementNotVisible("#output-loader", 5000); - - // Hashing - loadOp("Streebog", browser) - .waitForElementNotVisible("#output-loader", 5000); - - // Image - loadOp("Extract EXIF", browser) - .waitForElementNotVisible("#output-loader", 5000); - - // PGP - loadOp("PGP Encrypt", browser) - .waitForElementNotVisible("#output-loader", 5000); - - // PublicKey - loadOp("Hex to PEM", browser) - .waitForElementNotVisible("#output-loader", 5000); - - // Regex - loadOp("Strings", browser) - .waitForElementNotVisible("#output-loader", 5000); - - // Shellcode - loadOp("Disassemble x86", browser) - .waitForElementNotVisible("#output-loader", 5000); - - // URL - loadOp("URL Encode", browser) - .waitForElementNotVisible("#output-loader", 5000); - - // UserAgent - loadOp("Parse User Agent", browser) - .waitForElementNotVisible("#output-loader", 5000); - - // YARA - loadOp("YARA Rules", browser) - .waitForElementNotVisible("#output-loader", 5000); - - browser.click("#clr-recipe"); - }, - - "Move around the UI": browser => { - const otherCat = "//a[contains(@class, 'category-title') and contains(@data-target, '#catOther')]", - genUUID = "//li[contains(@class, 'operation') and text()='Generate UUID']"; - - browser.useXpath(); - - // Scroll to a lower category - browser - .getLocationInView(otherCat) - .expect.element(otherCat).to.be.visible; - - // Open category - browser - .click(otherCat) - .expect.element(genUUID).to.be.visible; - - // Add op to recipe - /* mouseButtonUp drops wherever the actual cursor is, not necessarily in the right place, - so we can't test Sortable.js properly using Nightwatch. html-dnd doesn't work either. - Instead of relying on drag and drop, we double click on the op to load it. */ - browser - .getLocationInView(genUUID) - .moveToElement(genUUID, 10, 10) - .doubleClick("xpath", genUUID) - .useCss() - .waitForElementVisible(".operation .op-title", 1000) - .waitForElementNotVisible("#stale-indicator", 1000) - .expect.element("#output-text .cm-content").text.which.matches(/[\da-f-]{36}/); - - browser.click("#clr-recipe"); - }, - - "Search": browser => { - // Search for an op - browser - .useCss() - .clearValue("#search") - .setValue("#search", "md5") - .useXpath() - .waitForElementVisible("//ul[@id='search-results']//b[text()='MD5']", 1000); - }, - - "Alert bar": browser => { - // Bake nothing to create an empty output which can be copied - utils.clear(browser); - utils.bake(browser); - - // Alert bar shows and contains correct content - browser - .click("#copy-output") - .waitForElementVisible("#snackbar-container") - .waitForElementVisible("#snackbar-container .snackbar-content") - .expect.element("#snackbar-container .snackbar-content").text.to.equal("Copied raw output successfully."); - - // Alert bar disappears after the correct amount of time - // Should disappear after 2000ms - browser - .waitForElementNotPresent("#snackbar-container .snackbar-content", 2500) - .waitForElementNotVisible("#snackbar-container"); - }, - - after: browser => { - browser.end(); - } -}; - -/** - * Clears the current recipe and loads a new operation. - * - * @param {string} opName - * @param {Browser} browser - */ -function loadOp(opName, browser) { - return browser - .useCss() - .click("#clr-recipe") - .urlHash("op=" + opName); -} diff --git a/tests/browser/mobile-ui/01_io.js b/tests/browser/mobile-ui/01_io.js deleted file mode 100644 index 67d1fdff..00000000 --- a/tests/browser/mobile-ui/01_io.js +++ /dev/null @@ -1,704 +0,0 @@ -/** - * Tests for input and output of various types to ensure the editors work as expected - * and retain data integrity, especially when it comes to special characters. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2023 - * @license Apache-2.0 - */ - -// import { -// clear, -// utils.setInput, -// bake, -// setChrEnc, -// setEOLSeq, -// copy, -// paste, -// loadRecipe, -// expectOutput, -// uploadFile, -// uploadFolder -// } from "./browserUtils.js"; - -const utils = require("./browserUtils.js"); - -const SPECIAL_CHARS = [ - "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\u000a\u000b\u000c\u000d\u000e\u000f", - "\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f", - "\u007f", - "\u0080\u0081\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089\u008a\u008b\u008c\u008d\u008e\u008f", - "\u0090\u0091\u0092\u0093\u0094\u0095\u0096\u0097\u0098\u0099\u009a\u009b\u009c\u009d\u009e\u009f", - "\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\u2066\u2067\u2069\ufeff\ufff9\ufffa\ufffb\ufffc" -].join(""); - -const ALL_BYTES = [ - "\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", -].join(""); - -const PUA_CHARS = "\ue000\ue001\uf8fe\uf8ff"; - -const MULTI_LINE_STRING =`"You know," said Arthur, "it's at times like this, when I'm trapped in a Vogon airlock with a man from Betelgeuse, and about to die of asphyxiation in deep space that I really wish I'd listened to what my mother told me when I was young." -"Why, what did she tell you?" -"I don't know, I didn't listen."`; - -const SELECTABLE_STRING = `ONE -two -ONE -three -ONE -four -ONE`; - -// Descriptions for named control characters -const CONTROL_CHAR_NAMES = { - 0: "null", - 7: "bell", - 8: "backspace", - 10: "line feed", - 11: "vertical tab", - 13: "carriage return", - 27: "escape", - 8203: "zero width space", - 8204: "zero width non-joiner", - 8205: "zero width joiner", - 8206: "left-to-right mark", - 8207: "right-to-left mark", - 8232: "line separator", - 8237: "left-to-right override", - 8238: "right-to-left override", - 8294: "left-to-right isolate", - 8295: "right-to-left isolate", - 8297: "pop directional isolate", - 8233: "paragraph separator", - 65279: "zero width no-break space", - 65532: "object replacement" -}; - -module.exports = { - before: browser => { - browser - .resizeWindow(1280, 800) - .url(browser.launchUrl) - .useCss() - .waitForElementNotPresent("#preloader", 10000) - .click("#auto-bake-label"); - }, - - "CodeMirror has loaded correctly": browser => { - /* Editor has initialised */ - browser - .useCss() - // Input - .waitForElementVisible("#input-text") - .waitForElementVisible("#input-text .cm-editor") - .waitForElementVisible("#input-text .cm-editor .cm-scroller") - .waitForElementVisible("#input-text .cm-editor .cm-scroller .cm-content") - .waitForElementVisible("#input-text .cm-editor .cm-scroller .cm-content .cm-line") - // Output - .waitForElementVisible("#output-text") - .waitForElementVisible("#output-text .cm-editor") - .waitForElementVisible("#output-text .cm-editor .cm-scroller") - .waitForElementVisible("#output-text .cm-editor .cm-scroller .cm-content") - .waitForElementVisible("#output-text .cm-editor .cm-scroller .cm-content .cm-line"); - - /* Status bar is showing and has correct values */ - browser // Input - .waitForElementVisible("#input-text .cm-status-bar") - .waitForElementVisible("#input-text .cm-status-bar .stats-length-value") - .expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("0"); - browser.waitForElementVisible("#input-text .cm-status-bar .stats-lines-value") - .expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("1"); - browser.waitForElementVisible("#input-text .cm-status-bar .chr-enc-value") - .expect.element("#input-text .cm-status-bar .chr-enc-value").text.to.equal("Raw Bytes"); - browser.waitForElementVisible("#input-text .cm-status-bar .eol-value") - .expect.element("#input-text .cm-status-bar .eol-value").text.to.equal("LF"); - - browser // Output - .waitForElementVisible("#output-text .cm-status-bar") - .waitForElementVisible("#output-text .cm-status-bar .stats-length-value") - .expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("0"); - browser.waitForElementVisible("#output-text .cm-status-bar .stats-lines-value") - .expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("1"); - browser.waitForElementVisible("#output-text .cm-status-bar .baking-time-info") - .expect.element("#output-text .cm-status-bar .baking-time-info").text.to.contain("ms"); - browser.waitForElementVisible("#output-text .cm-status-bar .chr-enc-value") - .expect.element("#output-text .cm-status-bar .chr-enc-value").text.to.equal("Raw Bytes"); - browser.waitForElementVisible("#output-text .cm-status-bar .eol-value") - .expect.element("#output-text .cm-status-bar .eol-value").text.to.equal("LF"); - }, - - "Adding content": browser => { - /* Status bar updates correctly */ - utils.setInput(browser, MULTI_LINE_STRING); - - browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("301"); - browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("3"); - browser.expect.element("#input-text .cm-status-bar .chr-enc-value").text.to.equal("Raw Bytes"); - browser.expect.element("#input-text .cm-status-bar .eol-value").text.to.equal("LF"); - - browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("0"); - browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("1"); - browser.expect.element("#output-text .cm-status-bar .baking-time-info").text.to.contain("ms"); - browser.expect.element("#output-text .cm-status-bar .chr-enc-value").text.to.equal("Raw Bytes"); - browser.expect.element("#output-text .cm-status-bar .eol-value").text.to.equal("LF"); - - /* Output updates correctly */ - utils.bake(browser); - browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("301"); - browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("3"); - browser.expect.element("#output-text .cm-status-bar .baking-time-info").text.to.contain("ms"); - browser.expect.element("#output-text .cm-status-bar .chr-enc-value").text.to.equal("Raw Bytes"); - browser.expect.element("#output-text .cm-status-bar .eol-value").text.to.equal("LF"); - }, - - "Special content": browser => { - /* Special characters are rendered correctly */ - utils.setInput(browser, SPECIAL_CHARS, false); - - // First line - for (let i = 0x0; i <= 0x8; i++) { - browser.expect.element(`#input-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(${i+1})`) - .to.have.property("title").equals(`Control character ${CONTROL_CHAR_NAMES[i] || "0x" + i.toString(16)}`); - browser.expect.element(`#input-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(${i+1})`) - .text.to.equal(String.fromCharCode(0x2400 + i)); - } - - // Tab \u0009 - browser.expect.element(`#input-text .cm-line:nth-of-type(1)`).to.have.property("textContent").match(/\u0009$/); - - // Line feed \u000a - browser.expect.element(`#input-text .cm-line:nth-of-type(1)`).to.have.property("textContent").match(/^.{10}$/); - browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2"); - - // Second line - for (let i = 0x0b; i < SPECIAL_CHARS.length; i++) { - const index = SPECIAL_CHARS.charCodeAt(i); - const name = CONTROL_CHAR_NAMES[index] || "0x" + index.toString(16); - const value = index >= 32 ? "\u2022" : String.fromCharCode(0x2400 + index); - - browser.expect.element(`#input-text .cm-line:nth-of-type(2) .cm-specialChar:nth-of-type(${i-10})`) - .to.have.property("title").equals(`Control character ${name}`); - browser.expect.element(`#input-text .cm-line:nth-of-type(2) .cm-specialChar:nth-of-type(${i-10})`) - .text.to.equal(value); - } - - /* Output renders correctly */ - utils.setChrEnc(browser, "output", "UTF-8"); - utils.bake(browser); - - // First line - for (let i = 0x0; i <= 0x8; i++) { - browser.expect.element(`#output-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(${i+1})`) - .to.have.property("title").equals(`Control character ${CONTROL_CHAR_NAMES[i] || "0x" + i.toString(16)}`); - browser.expect.element(`#output-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(${i+1})`) - .text.to.equal(String.fromCharCode(0x2400 + i)); - } - - // Tab \u0009 - browser.expect.element(`#output-text .cm-line:nth-of-type(1)`).to.have.property("textContent").match(/\u0009$/); - - // Line feed \u000a - browser.expect.element(`#output-text .cm-line:nth-of-type(1)`).to.have.property("textContent").match(/^.{10}$/); - browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("2"); - - // Second line - for (let i = 0x0b; i < SPECIAL_CHARS.length; i++) { - const index = SPECIAL_CHARS.charCodeAt(i); - const name = CONTROL_CHAR_NAMES[index] || "0x" + index.toString(16); - const value = index >= 32 ? "\u2022" : String.fromCharCode(0x2400 + index); - - browser.expect.element(`#output-text .cm-content .cm-line:nth-of-type(2) .cm-specialChar:nth-of-type(${i-10})`) - .to.have.property("title").equals(`Control character ${name}`); - browser.expect.element(`#output-text .cm-content .cm-line:nth-of-type(2) .cm-specialChar:nth-of-type(${i-10})`) - .text.to.equal(value); - } - - /* Bytes are rendered correctly */ - utils.setInput(browser, ALL_BYTES, false); - // Expect length to be 255, since one character is creating a newline - browser.expect.element(`#input-text .cm-content`).to.have.property("textContent").match(/^.{255}$/); - browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("256"); - browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2"); - - - /* PUA \ue000-\uf8ff */ - utils.setInput(browser, PUA_CHARS, false); - utils.setChrEnc(browser, "output", "UTF-8"); - utils.bake(browser); - - // Confirm input and output as expected - /* In order to render whitespace characters as control character pictures in the output, even - when they are the designated line separator, CyberChef sometimes chooses to represent them - internally using the Unicode Private Use Area (https://en.wikipedia.org/wiki/Private_Use_Areas). - See `Utils.escapeWhitespace()` for an example of this. - Therefore, PUA characters should be rendered normally in the Input but as control character - pictures in the output. - */ - browser.expect.element(`#input-text .cm-content`).to.have.property("textContent").match(/^\ue000\ue001\uf8fe\uf8ff$/); - browser.expect.element(`#output-text .cm-content`).to.have.property("textContent").match(/^\u2400\u2401\u3cfe\u3cff$/); - - /* Can be copied */ - utils.setInput(browser, SPECIAL_CHARS, false); - utils.setChrEnc(browser, "output", "UTF-8"); - utils.bake(browser); - - // Manual copy - browser - .doubleClick("#output-text .cm-content .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(1)") - .waitForElementVisible("#output-text .cm-selectionBackground"); - utils.copy(browser); - utils.paste(browser, "#search"); // Paste into search box as this won't mess with the values - - // Ensure that the values are as expected - browser.expect.element("#search").to.have.value.that.equals("\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008"); - browser.clearValue("#search"); - - // Raw copy - browser - .click("#copy-output") - .pause(100); - utils.paste(browser, "#search"); // Paste into search box as this won't mess with the values - - // Ensure that the values are as expected - browser.expect.element("#search").to.have.value.that.matches(/^\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009/); - browser.clearValue("#search"); - }, - - "HTML output": browser => { - /* Displays correctly */ - utils.loadRecipe(browser, "Entropy", ALL_BYTES); - utils.bake(browser); - - browser - .waitForElementVisible("#output-html") - .waitForElementVisible("#output-html #chart-area"); - - /* Status bar widgets are disabled */ - browser.expect.element("#output-text .cm-status-bar .disabled .stats-length-value").to.be.visible; - browser.expect.element("#output-text .cm-status-bar .disabled .stats-lines-value").to.be.visible; - browser.expect.element("#output-text .cm-status-bar .disabled .chr-enc-value").to.be.visible; - browser.expect.element("#output-text .cm-status-bar .disabled .eol-value").to.be.visible; - - /* Displays special chars correctly */ - utils.loadRecipe(browser, "To Table", ",\u0000\u0001\u0002\u0003\u0004", [",", "\\r\\n", false, "HTML"]); - utils.bake(browser); - - for (let i = 0x0; i <= 0x4; i++) { - browser.expect.element(`#output-html .cm-specialChar:nth-of-type(${i+1})`) - .to.have.property("title").equals(`Control character ${CONTROL_CHAR_NAMES[i] || "0x" + i.toString(16)}`); - browser.expect.element(`#output-html .cm-specialChar:nth-of-type(${i+1})`) - .text.to.equal(String.fromCharCode(0x2400 + i)); - } - - /* Can be copied */ - // Raw copy - browser - .click("#copy-output") - .pause(100); - utils.paste(browser, "#search"); // Paste into search box as this won't mess with the values - - // Ensure that the values are as expected - browser.expect.element("#search").to.have.value.that.matches(/\u0000\u0001\u0002\u0003\u0004/); - browser.clearValue("#search"); - }, - - "Highlighting": browser => { - utils.setInput(browser, SELECTABLE_STRING); - utils.bake(browser); - - /* Selecting input text also selects other instances in input and output */ - browser // Input - .click("#auto-bake-label") - .doubleClick("#input-text .cm-content .cm-line:nth-of-type(1)") - .waitForElementVisible("#input-text .cm-selectionLayer .cm-selectionBackground") - .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(1) .cm-selectionMatch") - .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(2) .cm-selectionMatch") - .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(3) .cm-selectionMatch") - .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(4) .cm-selectionMatch") - .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(5) .cm-selectionMatch") - .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(6) .cm-selectionMatch") - .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(7) .cm-selectionMatch"); - - browser // Output - .waitForElementVisible("#output-text .cm-selectionLayer .cm-selectionBackground") - .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(1) .cm-selectionMatch") - .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(2) .cm-selectionMatch") - .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(3) .cm-selectionMatch") - .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(4) .cm-selectionMatch") - .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(5) .cm-selectionMatch") - .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(6) .cm-selectionMatch") - .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(7) .cm-selectionMatch"); - - /* Selecting output text highlights in input */ - browser // Output - .click("#output-text") - .waitForElementNotPresent("#input-text .cm-selectionLayer .cm-selectionBackground") - .waitForElementNotPresent("#output-text .cm-selectionLayer .cm-selectionBackground") - .waitForElementNotPresent("#input-text .cm-content .cm-line .cm-selectionMatch") - .waitForElementNotPresent("#output-text .cm-content .cm-line .cm-selectionMatch") - .doubleClick("#output-text .cm-content .cm-line:nth-of-type(7)") - .waitForElementVisible("#output-text .cm-selectionLayer .cm-selectionBackground") - .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(1) .cm-selectionMatch") - .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(2) .cm-selectionMatch") - .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(3) .cm-selectionMatch") - .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(4) .cm-selectionMatch") - .waitForElementVisible("#output-text .cm-content .cm-line:nth-of-type(5) .cm-selectionMatch") - .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(6) .cm-selectionMatch") - .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(7) .cm-selectionMatch"); - - browser // Input - .waitForElementVisible("#input-text .cm-selectionLayer .cm-selectionBackground") - .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(1) .cm-selectionMatch") - .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(2) .cm-selectionMatch") - .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(3) .cm-selectionMatch") - .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(4) .cm-selectionMatch") - .waitForElementVisible("#input-text .cm-content .cm-line:nth-of-type(5) .cm-selectionMatch") - .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(6) .cm-selectionMatch") - .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(7) .cm-selectionMatch"); - - // Turn autobake off again - browser.click("#auto-bake-label"); - }, - - "Character encoding": browser => { - const CHINESE_CHARS = "不要恐慌。"; - /* Dropup works */ - /* Selecting changes output correctly */ - utils.setInput(browser, CHINESE_CHARS, false); - utils.setChrEnc(browser, "input", "UTF-8"); - utils.bake(browser); - utils.expectOutput(browser, "\u00E4\u00B8\u008D\u00E8\u00A6\u0081\u00E6\u0081\u0090\u00E6\u0085\u008C\u00E3\u0080\u0082"); - - /* Changing output to match input works as expected */ - utils.setChrEnc(browser, "output", "UTF-8"); - utils.bake(browser); - utils.expectOutput(browser, CHINESE_CHARS); - - /* Encodings appear in the URL */ - browser.assert.urlContains("ienc=65001"); - browser.assert.urlContains("oenc=65001"); - - /* Preserved when changing tabs */ - browser - .click("#btn-new-tab") - .waitForElementVisible("#input-tabs li:nth-of-type(2).active-input-tab"); - browser.expect.element("#input-text .chr-enc-value").text.that.equals("Raw Bytes"); - browser.expect.element("#output-text .chr-enc-value").text.that.equals("Raw Bytes"); - - utils.setChrEnc(browser, "input", "UTF-7"); - utils.setChrEnc(browser, "output", "UTF-7"); - - browser - .click("#input-tabs li:nth-of-type(1)") - .waitForElementVisible("#input-tabs li:nth-of-type(1).active-input-tab"); - browser.expect.element("#input-text .chr-enc-value").text.that.equals("UTF-8"); - browser.expect.element("#output-text .chr-enc-value").text.that.equals("UTF-8"); - - /* Try various encodings */ - // These are not meant to be realistic encodings for this data - utils.setInput(browser, CHINESE_CHARS, false); - utils.setChrEnc(browser, "input", "UTF-8"); - utils.setChrEnc(browser, "output", "UTF-16LE"); - utils.bake(browser); - utils.expectOutput(browser, "\uB8E4\uE88D\u81A6\u81E6\uE690\u8C85\u80E3"); - - utils.setChrEnc(browser, "output", "Simplified Chinese GBK"); - utils.bake(browser); - utils.expectOutput(browser, "\u6D93\u5D88\uFDFF\u93AD\u612D\u53A1\u9286\u0000"); - - utils.setChrEnc(browser, "input", "UTF-7"); - utils.bake(browser); - utils.expectOutput(browser, "+Tg0-+iYE-+YFA-+YUw-"); - - utils.setChrEnc(browser, "input", "Traditional Chinese Big5"); - utils.bake(browser); - utils.expectOutput(browser, "\u3043\u74B6\uFDFF\u7A3A\uFDFF"); - - utils.setChrEnc(browser, "output", "Windows-1251 Cyrillic"); - utils.bake(browser); - utils.expectOutput(browser, "\u00A4\u0408\u00ADn\u00AE\u0408\u00B7W\u040EC"); - }, - - "Line endings": browser => { - /* Dropup works */ - /* Selecting changes view in input */ - utils.setInput(browser, MULTI_LINE_STRING); - - // Line endings: LF - - // Input - browser - .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(3)") - .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(4)") - .waitForElementNotPresent("#input-text .cm-content .cm-specialChar"); - browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("301"); - browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("3"); - - // Output - utils.bake(browser); - browser - .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(3)") - .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(4)") - .waitForElementNotPresent("#output-text .cm-content .cm-specialChar"); - browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("301"); - browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("3"); - - // Input EOL: VT - utils.setEOLSeq(browser, "input", "VT"); - - // Input - browser - .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(1)") - .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(2)") - .waitForElementPresent("#input-text .cm-content .cm-specialChar"); - browser.expect.element("#input-text .cm-content .cm-specialChar").text.to.equal("␊"); - browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("301"); - browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("1"); - - // Output - utils.bake(browser); - browser - .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(3)") - .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(4)") - .waitForElementNotPresent("#output-text .cm-content .cm-specialChar"); - browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("301"); - browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("3"); - - // Output EOL: VT - utils.setEOLSeq(browser, "output", "VT"); - - // Input - browser - .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(1)") - .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(2)") - .waitForElementPresent("#input-text .cm-content .cm-specialChar"); - browser.expect.element("#input-text .cm-content .cm-specialChar").text.to.equal("␊"); - browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("301"); - browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("1"); - - // Output - browser - .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(1)") - .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(2)") - .waitForElementPresent("#output-text .cm-content .cm-specialChar"); - browser.expect.element("#output-text .cm-content .cm-specialChar").text.to.equal("␊"); - browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("301"); - browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("1"); - - /* Adding new line ending changes output correctly */ - browser.sendKeys("#input-text .cm-content", browser.Keys.RETURN); - - // Input - browser - .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(2)") - .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(3)"); - browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("302"); - browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2"); - - // Output - utils.bake(browser); - browser - .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(2)") - .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(3)"); - browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("302"); - browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("2"); - - // Input EOL: CRLF - utils.setEOLSeq(browser, "input", "CRLF"); - // Output EOL: CR - utils.setEOLSeq(browser, "output", "CR"); - browser.sendKeys("#input-text .cm-content", browser.Keys.RETURN); - - // Input - browser - .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(2)") - .waitForElementNotPresent("#input-text .cm-content .cm-line:nth-of-type(3)") - .waitForElementPresent("#input-text .cm-content .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(3)"); - browser.expect.element("#input-text .cm-content .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(3)").text.to.equal("␋"); - browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("304"); - browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2"); - - // Output - utils.bake(browser); - browser - .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(2)") - .waitForElementNotPresent("#output-text .cm-content .cm-line:nth-of-type(3)") - .waitForElementPresent("#output-text .cm-content .cm-line:nth-of-type(2) .cm-specialChar"); - browser.expect.element("#output-text .cm-content .cm-line:nth-of-type(2) .cm-specialChar").text.to.equal("␊"); - browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("304"); - browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("2"); - - /* Line endings appear in the URL */ - browser.assert.urlContains("ieol=%0D%0A"); - browser.assert.urlContains("oeol=%0D"); - - /* Preserved when changing tabs */ - browser - .click("#btn-new-tab") - .waitForElementVisible("#input-tabs li:nth-of-type(2).active-input-tab"); - browser.expect.element("#input-text .eol-value").text.that.equals("LF"); - browser.expect.element("#output-text .eol-value").text.that.equals("LF"); - - utils.setEOLSeq(browser, "input", "FF"); - utils.setEOLSeq(browser, "output", "LS"); - - browser - .click("#input-tabs li:nth-of-type(1)") - .waitForElementVisible("#input-tabs li:nth-of-type(1).active-input-tab"); - browser.expect.element("#input-text .eol-value").text.that.equals("CRLF"); - browser.expect.element("#output-text .eol-value").text.that.equals("CR"); - }, - - "File inputs": browser => { - utils.clear(browser); - - /* Side panel displays correct info */ - utils.uploadFile(browser, "files/TowelDay.jpeg"); - - browser - .waitForElementVisible("#input-text .cm-file-details") - .waitForElementVisible("#input-text .cm-file-details .file-details-toggle-shown") - .waitForElementVisible("#input-text .cm-file-details .file-details-thumbnail") - .waitForElementVisible("#input-text .cm-file-details .file-details-name") - .waitForElementVisible("#input-text .cm-file-details .file-details-size") - .waitForElementVisible("#input-text .cm-file-details .file-details-type") - .waitForElementVisible("#input-text .cm-file-details .file-details-loaded"); - browser.expect.element("#input-text .cm-file-details .file-details-name").text.that.equals("TowelDay.jpeg"); - browser.expect.element("#input-text .cm-file-details .file-details-size").text.that.equals("61,379 bytes"); - browser.expect.element("#input-text .cm-file-details .file-details-type").text.that.equals("image/jpeg"); - browser.expect.element("#input-text .cm-file-details .file-details-loaded").text.that.equals("100%"); - - /* Side panel can be hidden */ - browser - .click("#input-text .cm-file-details .file-details-toggle-shown") - .waitForElementNotPresent("#input-text .cm-file-details .file-details-toggle-shown") - .waitForElementVisible("#input-text .cm-file-details .file-details-toggle-hidden") - .expect.element("#input-text .cm-file-details").to.have.css("width").which.equals("1px"); - - browser - .click("#input-text .cm-file-details .file-details-toggle-hidden") - .waitForElementNotPresent("#input-text .cm-file-details .file-details-toggle-hidden") - .waitForElementVisible("#input-text .cm-file-details .file-details-toggle-shown") - .expect.element("#input-text .cm-file-details").to.have.css("width").which.equals("200px"); - }, - - "Folder inputs": browser => { - utils.clear(browser); - - /* Side panel displays correct info */ - utils.uploadFolder(browser, "files"); - - // Loop through tabs - for (let i = 1; i < 3; i++) { - browser - .click(`#input-tabs li:nth-of-type(${i})`) - .waitForElementVisible(`#input-tabs li:nth-of-type(${i}).active-input-tab`); - - browser - .waitForElementVisible("#input-text .cm-file-details") - .waitForElementVisible("#input-text .cm-file-details .file-details-toggle-shown") - .waitForElementVisible("#input-text .cm-file-details .file-details-thumbnail") - .waitForElementVisible("#input-text .cm-file-details .file-details-name") - .waitForElementVisible("#input-text .cm-file-details .file-details-size") - .waitForElementVisible("#input-text .cm-file-details .file-details-type") - .waitForElementVisible("#input-text .cm-file-details .file-details-loaded"); - - browser.getText("#input-text .cm-file-details .file-details-name", function(result) { - switch (result.value) { - case "TowelDay.jpeg": - browser.expect.element("#input-text .cm-file-details .file-details-name").text.that.equals("TowelDay.jpeg"); - browser.expect.element("#input-text .cm-file-details .file-details-size").text.that.equals("61,379 bytes"); - browser.expect.element("#input-text .cm-file-details .file-details-type").text.that.equals("image/jpeg"); - browser.expect.element("#input-text .cm-file-details .file-details-loaded").text.that.equals("100%"); - break; - case "Hitchhikers_Guide.jpeg": - browser.expect.element("#input-text .cm-file-details .file-details-name").text.that.equals("Hitchhikers_Guide.jpeg"); - browser.expect.element("#input-text .cm-file-details .file-details-size").text.that.equals("36,595 bytes"); - browser.expect.element("#input-text .cm-file-details .file-details-type").text.that.equals("image/jpeg"); - browser.expect.element("#input-text .cm-file-details .file-details-loaded").text.that.equals("100%"); - break; - default: - break; - } - }); - } - }, - - "Loading from URL": browser => { - /* Complex deep link populates the input correctly (encoding, eol, input) */ - browser - .urlHash("recipe=To_Base64('A-Za-z0-9%2B/%3D')&input=VGhlIHNoaXBzIGh1bmcgaW4gdGhlIHNreSBpbiBtdWNoIHRoZSBzYW1lIHdheSB0aGF0IGJyaWNrcyBkb24ndC4M&ienc=21866&oenc=1201&ieol=%0C&oeol=%E2%80%A9") - .waitForElementVisible("#rec-list li.operation"); - - browser.expect.element(`#input-text .cm-content`).to.have.property("textContent").match(/^.{65}$/); - browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("66"); - browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2"); - - browser.expect.element("#input-text .chr-enc-value").text.that.equals("KOI8-U Ukrainian Cyrillic"); - browser.expect.element("#output-text .chr-enc-value").text.that.equals("UTF-16BE"); - - browser.expect.element("#input-text .eol-value").text.that.equals("FF"); - browser.expect.element("#output-text .eol-value").text.that.equals("PS"); - - utils.bake(browser); - - browser.expect.element(`#output-text .cm-content`).to.have.property("textContent").match(/^.{44}$/); - browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("44"); - browser.expect.element("#output-text .cm-status-bar .stats-lines-value").text.to.equal("1"); - }, - - "Replace input with output": browser => { - /* Input is correctly populated */ - utils.loadRecipe(browser, "XOR", "The ships hung in the sky in much the same way that bricks don't.", [{ "option": "Hex", "string": "65" }, "Standard", false]); - utils.setChrEnc(browser, "input", "UTF-32LE"); - utils.setChrEnc(browser, "output", "UTF-7"); - utils.setEOLSeq(browser, "input", "CRLF"); - utils.setEOLSeq(browser, "output", "LS"); - - browser - .sendKeys("#input-text .cm-content", browser.Keys.RETURN) - .expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2"); - utils.bake(browser); - - browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("67"); - browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("2"); - browser.expect.element("#input-text .chr-enc-value").text.that.equals("UTF-32LE"); - browser.expect.element("#input-text .eol-value").text.that.equals("CRLF"); - browser.expect.element("#output-text .cm-status-bar .stats-length-value").text.to.equal("268"); - - browser - .click("#switch") - .waitForElementVisible("#stale-indicator"); - - browser.expect.element("#input-text .cm-status-bar .stats-length-value").text.to.equal("268"); - - /* Special characters, encodings and line endings all as expected */ - browser.expect.element("#input-text .cm-status-bar .stats-lines-value").text.to.equal("1"); - browser.expect.element("#input-text .chr-enc-value").text.that.equals("UTF-7"); - browser.expect.element("#input-text .eol-value").text.that.equals("LS"); - browser.expect.element("#input-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(1)").text.to.equal("␍"); - browser.expect.element("#input-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(49)").text.to.equal("␑"); - browser.waitForElementNotPresent("#input-text .cm-line:nth-of-type(1) .cm-specialChar:nth-of-type(50)"); - }, - - - after: browser => { - browser.end(); - } -}; diff --git a/tests/browser/mobile-ui/02_ops.js b/tests/browser/mobile-ui/02_ops.js deleted file mode 100644 index e2c8a219..00000000 --- a/tests/browser/mobile-ui/02_ops.js +++ /dev/null @@ -1,502 +0,0 @@ -/** - * Tests for operations. - * The primary purpose for these test is to ensure that the operations - * output something vaguely expected (i.e. they aren't completely broken - * after a dependency update or changes to the UI), rather than to confirm - * that this output is actually accurate. Accuracy of output and testing - * of edge cases should be carried out in the operations test suite found - * in /tests/operations as this is much faster and easier to configure - * than the UI tests found here. - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2021 - * @license Apache-2.0 - */ - -const utils = require("./browserUtils.js"); - -module.exports = { - before: browser => { - browser - .resizeWindow(1280, 800) - .url(browser.launchUrl) - .useCss() - .waitForElementNotPresent("#preloader", 10000) - .click("#auto-bake-label"); - }, - - "Sanity check operations": async browser => { - const Images = await import("../samples/Images.mjs"); - testOp(browser, "A1Z26 Cipher Decode", "20 5 19 20 15 21 20 16 21 20", "testoutput"); - testOp(browser, "A1Z26 Cipher Encode", "test input", "20 5 19 20 9 14 16 21 20"); - testOp(browser, "ADD", "test input", "Ê»ÉÊv¿ÄÆËÊ", [{ "option": "Hex", "string": "56" }]); - testOp(browser, "AES Decrypt", "b443f7f7c16ac5396a34273f6f639caa", "test output", [{ "option": "Hex", "string": "00112233445566778899aabbccddeeff" }, { "option": "Hex", "string": "00000000000000000000000000000000" }, "CBC", "Hex", "Raw", { "option": "Hex", "string": "" }]); - testOp(browser, "AES Encrypt", "test input", "e42eb8fbfb7a98fff061cd2c1a794d92", [{"option": "Hex", "string": "00112233445566778899aabbccddeeff"}, {"option": "Hex", "string": "00000000000000000000000000000000"}, "CBC", "Raw", "Hex"]); - testOp(browser, "AND", "test input", "4$04 $044", [{ "option": "Hex", "string": "34" }]); - testOp(browser, "Add line numbers", "test input", "1 test input"); - testOp(browser, ["From Hex", "Add Text To Image", "To Base64"], Images.PNG_HEX, Images.PNG_CHEF_B64, [[], ["Chef", "Center", "Middle", 0, 0, 16], []]); - testOp(browser, "Adler-32 Checksum", "test input", "16160411"); - testOp(browser, "Affine Cipher Decode", "test input", "rcqr glnsr", [1, 2]); - testOp(browser, "Affine Cipher Encode", "test input", "njln rbfpn", [2, 1]); - testOp(browser, "AMF Decode", "\u000A\u0013\u0001\u0003a\u0006\u0009test", /"\$value": "test"/); - testOp(browser, "AMF Encode", '{"a": "test"}', "\u000A\u0013\u0001\u0003a\u0006\u0009test"); - testOp(browser, "Analyse hash", "0123456789abcdef", /CRC-64/); - testOp(browser, "Atbash Cipher", "test input", "gvhg rmkfg"); - // testOp(browser, "Avro to JSON", "test input", "test_output"); - testOp(browser, "BLAKE2b", "test input", "33ebdc8f38177f3f3f334eeb117a84e11f061bbca4db6b8923e5cec85103f59f415551a5d5a933fdb6305dc7bf84671c2540b463dbfa08ee1895cfaa5bd780b5", ["512", "Hex", { "option": "UTF8", "string": "pass" }]); - testOp(browser, "BLAKE2s", "test input", "defe73d61dfa6e5807e4f9643e159a09ccda6be3c26dcd65f8a9bb38bfc973a7", ["256", "Hex", { "option": "UTF8", "string": "pass" }]); - testOp(browser, "BSON deserialise", "\u0011\u0000\u0000\u0000\u0002a\u0000\u0005\u0000\u0000\u0000test\u0000\u0000", '{\u000A "a": "test"\u000A}'); - testOp(browser, "BSON serialise", '{"a":"test"}', "\u0011\u0000\u0000\u0000\u0002a\u0000\u0005\u0000\u0000\u0000test\u0000\u0000"); - // testOp(browser, "Bacon Cipher Decode", "test input", "test_output"); - // testOp(browser, "Bacon Cipher Encode", "test input", "test_output"); - testOp(browser, "Bcrypt", "test input", /^\$2a\$06\$.{53}$/, [6]); - testOp(browser, "Bcrypt compare", "test input", "Match: test input", ["$2a$05$FCfBSVX7OeRkK.9kQVFCiOYu9XtwtIbePqUiroD1lkASW9q5QClzG"]); - testOp(browser, "Bcrypt parse", "$2a$05$kXWtAIGB/R8VEzInoM5ocOTBtyc0m2YTIwFiBU/0XoW032f9QrkWW", /Rounds: 5/); - testOp(browser, "Bifid Cipher Decode", "qblb tfovy", "test input", ["pass"]); - testOp(browser, "Bifid Cipher Encode", "test input", "qblb tfovy", ["pass"]); - testOp(browser, "Bit shift left", "test input", "\u00E8\u00CA\u00E6\u00E8@\u00D2\u00DC\u00E0\u00EA\u00E8"); - testOp(browser, "Bit shift right", "test input", ":29:\u0010478::"); - testOp(browser, "Blowfish Decrypt", "10884e15427dd84ec35204e9c8e921ae", "test_output", [{"option": "Hex", "string": "1234567801234567"}, {"option": "Hex", "string": "0011223344556677"}, "CBC", "Hex", "Raw"]); - testOp(browser, "Blowfish Encrypt", "test input", "f0fadbd1d90d774f714248cf26b96410", [{"option": "Hex", "string": "1234567801234567"}, {"option": "Hex", "string": "0011223344556677"}, "CBC", "Raw", "Hex"]); - testOp(browser, ["From Hex", "Blur Image", "To Base64"], Images.PNG_HEX, Images.PNG_BLUR_B64); - testOpHtml(browser, "Bombe", "XTSYN WAEUG EZALY NRQIM AMLZX MFUOD AWXLY LZCUZ QOQBQ JLCPK NDDRW F", "table tr:last-child td:first-child", "ECG", ["3-rotor", "LEYJVCNIXWPBQMDRTAKZGFUHOS", "BDFHJLCPRTXVZNYEIWGAKMUSQO { - browser.end(); - } -}; - - -/** @function - * Clears the current recipe and bakes a new operation. - * - * @param {Browser} browser - Nightwatch client - * @param {string|Array} opName - name of operation to be tested, array for multiple ops - * @param {string} input - input text for test - * @param {Array|Array>} args - arguments, nested if multiple ops - */ -function bakeOp(browser, opName, input, args=[]) { - browser.perform(function() { - console.log(`Current test: ${opName}`); - }); - utils.loadRecipe(browser, opName, input, args); - browser.waitForElementVisible("#stale-indicator", 5000); - utils.bake(browser); -} - -/** @function - * Clears the current recipe and tests a new operation. - * - * @param {Browser} browser - Nightwatch client - * @param {string|Array} opName - name of operation to be tested, array for multiple ops - * @param {string} input - input text - * @param {string} output - expected output - * @param {Array|Array>} args - arguments, nested if multiple ops - */ -function testOp(browser, opName, input, output, args=[]) { - bakeOp(browser, opName, input, args); - utils.expectOutput(browser, output); -} - -/** @function - * Clears the current recipe and tests a new operation with HTML output. - * - * @param {Browser} browser - Nightwatch client - * @param {string|Array} opName - name of operation to be tested array for multiple ops - * @param {string} input - input text - * @param {string} cssSelector - CSS selector for HTML output - * @param {string} output - expected output - * @param {Array|Array>} args - arguments, nested if multiple ops - */ -function testOpHtml(browser, opName, input, cssSelector, output, args=[]) { - bakeOp(browser, opName, input, args); - - if (typeof output === "string") { - browser.expect.element("#output-html " + cssSelector).text.that.equals(output); - } else if (output instanceof RegExp) { - browser.expect.element("#output-html " + cssSelector).text.that.matches(output); - } -} - -/** @function - * Clears the current recipe and tests a new Image-based operation. - * - * @param {Browser} browser - Nightwatch client - * @param {string|Array} opName - name of operation to be tested array for multiple ops - * @param {string} filename - filename of image file from samples directory - * @param {Array|Array>} args - arguments, nested if multiple ops - */ -function testOpImage(browser, opName, filename, args) { - browser.perform(function() { - console.log(`Current test: ${opName}`); - }); - utils.loadRecipe(browser, opName, "", args); - utils.uploadFile(browser, filename); - browser.waitForElementVisible("#stale-indicator", 5000); - utils.bake(browser); - - browser - .waitForElementVisible("#output-html img") - .expect.element("#output-html img").to.have.css("width").which.matches(/^[^0]\d*px/); -} - -/** @function - * Clears the current recipe and tests a new File-based operation. - * - * @param {Browser} browser - Nightwatch client - * @param {string|Array} opName - name of operation to be tested array for multiple ops - * @param {string} filename - filename of file from samples directory - * @param {string} cssSelector - CSS selector for HTML output - * @param {string} output - expected output - * @param {Array|Array>} args - arguments, nested if multiple ops - */ -function testOpFile(browser, opName, filename, cssSelector, output, args) { - browser.perform(function() { - console.log(`Current test: ${opName}`); - }); - utils.loadRecipe(browser, opName, "", args); - utils.uploadFile(browser, filename); - browser.pause(100).waitForElementVisible("#stale-indicator", 5000); - utils.bake(browser); - - if (typeof output === "string") { - browser.expect.element("#output-html " + cssSelector).text.that.equals(output); - } else if (output instanceof RegExp) { - browser.expect.element("#output-html " + cssSelector).text.that.matches(output); - } -} diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 56f432e0..570fbb6f 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -134,6 +134,7 @@ import "./tests/LevenshteinDistance.mjs"; import "./tests/SwapCase.mjs"; import "./tests/HKDF.mjs"; import "./tests/GenerateDeBruijnSequence.mjs"; +import "./tests/GOST.mjs"; // Cannot test operations that use the File type yet // import "./tests/SplitColourChannels.mjs"; diff --git a/tests/operations/tests/GOST.mjs b/tests/operations/tests/GOST.mjs new file mode 100644 index 00000000..f332051b --- /dev/null +++ b/tests/operations/tests/GOST.mjs @@ -0,0 +1,183 @@ +/** + * GOST tests. + * + * The GOST library already includes a range of tests for the correctness of + * the algorithms. These tests are intended only to confirm that the library + * has been correctly integrated into CyberChef. + * + * @author n1474335 [n1474335@gmail.com] + * + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "GOST Encrypt: Magma", + input: "Hello, World!", + expectedOutput: "f124ac5c0853870906dbaf9b56", + recipeConfig: [ + { + op: "GOST Encrypt", + args: [ + { "option": "Hex", "string": "00112233" }, + { "option": "Hex", "string": "0011223344556677" }, + "Raw", + "Hex", + "GOST 28147 (Magma, 1989)", + "64", + "E-SC", + "OFB", + "CP", + "ZERO" + ] + } + ], + }, + { + name: "GOST Encrypt: Kuznyechik", + input: "Hello, World!", + expectedOutput: "8673d490dfa4a66d5e3ff00ba316724f", + recipeConfig: [ + { + op: "GOST Encrypt", + args: [ + { "option": "Hex", "string": "00112233" }, + { "option": "Hex", "string": "00112233445566778899aabbccddeeff" }, + "Raw", + "Hex", + "GOST R 34.12 (Kuznyechik, 2015)", + "128", + "E-SC", + "CBC", + "CP", + "PKCS5" + ] + } + ], + }, + { + name: "GOST Decrypt: Magma", + input: "f124ac5c0853870906dbaf9b56", + expectedOutput: "Hello, World!", + recipeConfig: [ + { + op: "GOST Decrypt", + args: [ + { "option": "Hex", "string": "00112233" }, + { "option": "Hex", "string": "0011223344556677" }, + "Hex", + "Raw", + "GOST 28147 (Magma, 1989)", + "128", + "E-SC", + "OFB", + "CP", + "ZERO" + ] + } + ], + }, + { + name: "GOST Decrypt: Kuznyechik", + input: "8673d490dfa4a66d5e3ff00ba316724f", + expectedOutput: "Hello, World!\0\0\0", + recipeConfig: [ + { + op: "GOST Decrypt", + args: [ + { "option": "Hex", "string": "00112233" }, + { "option": "Hex", "string": "00112233445566778899aabbccddeeff" }, + "Hex", + "Raw", + "GOST R 34.12 (Kuznyechik, 2015)", + "128", + "E-TEST", + "CBC", + "CP", + "PKCS5" + ] + } + ], + }, + { + name: "GOST Sign", + input: "Hello, World!", + expectedOutput: "810d0c40e965", + recipeConfig: [ + { + op: "GOST Sign", + args: [ + { "option": "Hex", "string": "00112233" }, + { "option": "Hex", "string": "0011223344556677" }, + "Raw", + "Hex", + "GOST 28147 (Magma, 1989)", + "64", + "E-C", + 48 + ] + } + ], + }, + { + name: "GOST Verify", + input: "Hello, World!", + expectedOutput: "The signature matches", + recipeConfig: [ + { + op: "GOST Verify", + args: [ + { "option": "Hex", "string": "00112233" }, + { "option": "Hex", "string": "00112233445566778899aabbccddeeff" }, + { "option": "Hex", "string": "42b77fb3d6f6bf04" }, + "Raw", + "GOST R 34.12 (Kuznyechik, 2015)", + "128", + "E-TEST" + ] + } + ], + }, + { + name: "GOST Key Wrap", + input: "Hello, World!123", + expectedOutput: "0bb706e92487fceef97589911faeb28200000000000000000000000000000000\r\n6b7bfd16", + recipeConfig: [ + { + op: "GOST Key Wrap", + args: [ + { "option": "Hex", "string": "00112233" }, + { "option": "Hex", "string": "0011223344556677" }, + "Raw", + "Hex", + "GOST R 34.12 (Kuznyechik, 2015)", + "64", + "E-TEST", + "CP" + ] + } + ], + }, + { + name: "GOST Key Unwrap", + input: "c8e58458a42d21974d50103d59b469f2c8e58458a42d21974d50103d59b469f2\r\na32a1575", + expectedOutput: "0123456789abcdef0123456789abcdef", + recipeConfig: [ + { + op: "GOST Key Unwrap", + args: [ + { "option": "Hex", "string": "" }, + { "option": "Latin1", "string": "00112233" }, + "Hex", + "Raw", + "GOST 28147 (Magma, 1989)", + "64", + "E-Z", + "CP" + ] + } + ], + }, +]); diff --git a/tests/operations/tests/Hash.mjs b/tests/operations/tests/Hash.mjs index 4480f65a..ba502934 100644 --- a/tests/operations/tests/Hash.mjs +++ b/tests/operations/tests/Hash.mjs @@ -1094,8 +1094,8 @@ TestRegister.addTests([ expectedOutput: "981e5f3ca30c841487830f84fb433e13ac1101569b9c13584ac483234cd656c0", recipeConfig: [ { - op: "GOST hash", - args: ["D-A"] + op: "GOST Hash", + args: ["GOST 28147 (1994)", "256", "D-A"] } ] }, @@ -1105,8 +1105,8 @@ TestRegister.addTests([ expectedOutput: "2cefc2f7b7bdc514e18ea57fa74ff357e7fa17d652c75f69cb1be7893ede48eb", recipeConfig: [ { - op: "GOST hash", - args: ["D-A"] + op: "GOST Hash", + args: ["GOST 28147 (1994)", "256", "D-A"] } ] },